

// DO NOT EDIT !
// This file is generated using the MantaFlow preprocessor (prep generate).

/******************************************************************************
 *
 * MantaFlow fluid solver framework
 * Copyright 2011 Tobias Pfaff, Nils Thuerey
 *
 * This program is free software, distributed under the terms of the
 * Apache License, Version 2.0
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Meshes
 *
 * note: this is only a temporary solution, details are bound to change
 *        long term goal is integration with Split&Merge code by Wojtan et al.
 *
 ******************************************************************************/

#include "mesh.h"
#include "integrator.h"
#include "mantaio.h"
#include "kernel.h"
#include "shapes.h"
#include "noisefield.h"
//#include "grid.h"
#include <stack>
#include <cstring>

using namespace std;
namespace Manta {

Mesh::Mesh(FluidSolver *parent) : PbClass(parent)
{
}

Mesh::~Mesh()
{
  for (IndexInt i = 0; i < (IndexInt)mMeshData.size(); ++i)
    mMeshData[i]->setMesh(nullptr);

  if (mFreeMdata) {
    for (IndexInt i = 0; i < (IndexInt)mMeshData.size(); ++i)
      delete mMeshData[i];
  }
}

Mesh *Mesh::clone()
{
  Mesh *nm = new Mesh(mParent);
  *nm = *this;
  nm->setName(getName());
  return nm;
}

void Mesh::deregister(MeshDataBase *mdata)
{
  bool done = false;
  // remove pointer from mesh data list
  for (IndexInt i = 0; i < (IndexInt)mMeshData.size(); ++i) {
    if (mMeshData[i] == mdata) {
      if (i < (IndexInt)mMeshData.size() - 1)
        mMeshData[i] = mMeshData[mMeshData.size() - 1];
      mMeshData.pop_back();
      done = true;
    }
  }
  if (!done)
    errMsg("Invalid pointer given, not registered!");
}

// create and attach a new mdata field to this mesh
PbClass *Mesh::create(PbType t, PbTypeVec T, const string &name)
{
#if NOPYTHON != 1
  _args.add("nocheck", true);
  if (t.str() == "")
    errMsg("Specify mesh data type to create");
  // debMsg( "Mdata creating '"<< t.str <<" with size "<< this->getSizeSlow(), 5 );

  PbClass *pyObj = PbClass::createPyObject(t.str() + T.str(), name, _args, this->getParent());

  MeshDataBase *mdata = dynamic_cast<MeshDataBase *>(pyObj);
  if (!mdata) {
    errMsg(
        "Unable to get mesh data pointer from newly created object. Only create MeshData type "
        "with a Mesh.creat() call, eg, MdataReal, MdataVec3 etc.");
    delete pyObj;
    return nullptr;
  }
  else {
    this->registerMdata(mdata);
  }

  // directly init size of new mdata field:
  mdata->resize(this->getSizeSlow());
#else
  PbClass *pyObj = nullptr;
#endif
  return pyObj;
}

void Mesh::registerMdata(MeshDataBase *mdata)
{
  mdata->setMesh(this);
  mMeshData.push_back(mdata);

  if (mdata->getType() == MeshDataBase::TypeReal) {
    MeshDataImpl<Real> *pd = dynamic_cast<MeshDataImpl<Real> *>(mdata);
    if (!pd)
      errMsg("Invalid mdata object posing as real!");
    this->registerMdataReal(pd);
  }
  else if (mdata->getType() == MeshDataBase::TypeInt) {
    MeshDataImpl<int> *pd = dynamic_cast<MeshDataImpl<int> *>(mdata);
    if (!pd)
      errMsg("Invalid mdata object posing as int!");
    this->registerMdataInt(pd);
  }
  else if (mdata->getType() == MeshDataBase::TypeVec3) {
    MeshDataImpl<Vec3> *pd = dynamic_cast<MeshDataImpl<Vec3> *>(mdata);
    if (!pd)
      errMsg("Invalid mdata object posing as vec3!");
    this->registerMdataVec3(pd);
  }
}
void Mesh::registerMdataReal(MeshDataImpl<Real> *pd)
{
  mMdataReal.push_back(pd);
}
void Mesh::registerMdataVec3(MeshDataImpl<Vec3> *pd)
{
  mMdataVec3.push_back(pd);
}
void Mesh::registerMdataInt(MeshDataImpl<int> *pd)
{
  mMdataInt.push_back(pd);
}

void Mesh::addAllMdata()
{
  for (IndexInt i = 0; i < (IndexInt)mMeshData.size(); ++i) {
    mMeshData[i]->addEntry();
  }
}

Real Mesh::computeCenterOfMass(Vec3 &cm) const
{

  // use double precision for summation, otherwise too much error accumulation
  double vol = 0;
  Vector3D<double> cmd(0.0);
  for (size_t tri = 0; tri < mTris.size(); tri++) {
    Vector3D<double> p1(toVec3d(getNode(tri, 0)));
    Vector3D<double> p2(toVec3d(getNode(tri, 1)));
    Vector3D<double> p3(toVec3d(getNode(tri, 2)));

    double cvol = dot(cross(p1, p2), p3) / 6.0;
    cmd += (p1 + p2 + p3) * (cvol / 4.0);
    vol += cvol;
  }
  if (vol != 0.0)
    cmd /= vol;

  cm = toVec3(cmd);
  return (Real)vol;
}

void Mesh::clear()
{
  mNodes.clear();
  mTris.clear();
  mCorners.clear();
  m1RingLookup.clear();
  for (size_t i = 0; i < mNodeChannels.size(); i++)
    mNodeChannels[i]->resize(0);
  for (size_t i = 0; i < mTriChannels.size(); i++)
    mTriChannels[i]->resize(0);

  // clear mdata fields as well
  for (size_t i = 0; i < mMdataReal.size(); i++)
    mMdataReal[i]->resize(0);
  for (size_t i = 0; i < mMdataVec3.size(); i++)
    mMdataVec3[i]->resize(0);
  for (size_t i = 0; i < mMdataInt.size(); i++)
    mMdataInt[i]->resize(0);
}

Mesh &Mesh::operator=(const Mesh &o)
{
  // wipe current data
  clear();
  if (mNodeChannels.size() != o.mNodeChannels.size() ||
      mTriChannels.size() != o.mTriChannels.size())
    errMsg("can't copy mesh, channels not identical");
  mNodeChannels.clear();
  mTriChannels.clear();

  // copy corner, nodes, tris
  mCorners = o.mCorners;
  mNodes = o.mNodes;
  mTris = o.mTris;
  m1RingLookup = o.m1RingLookup;

  // copy channels
  for (size_t i = 0; i < mNodeChannels.size(); i++)
    mNodeChannels[i] = o.mNodeChannels[i];
  for (size_t i = 0; i < o.mTriChannels.size(); i++)
    mTriChannels[i] = o.mTriChannels[i];

  return *this;
}

int Mesh::load(string name, bool append)
{
  if (name.find_last_of('.') == string::npos)
    errMsg("file '" + name + "' does not have an extension");
  string ext = name.substr(name.find_last_of('.'));
  if (ext == ".gz")  // assume bobj gz
    return readBobjFile(name, this, append);
  else if (ext == ".obj")
    return readObjFile(name, this, append);
  else
    errMsg("file '" + name + "' filetype not supported");

  // dont always rebuild...
  // rebuildCorners();
  // rebuildLookup();
  return 0;
}

int Mesh::save(string name)
{
  if (name.find_last_of('.') == string::npos)
    errMsg("file '" + name + "' does not have an extension");
  string ext = name.substr(name.find_last_of('.'));
  if (ext == ".obj")
    return writeObjFile(name, this);
  else if (ext == ".gz")
    return writeBobjFile(name, this);
  else
    errMsg("file '" + name + "' filetype not supported");
  return 0;
}

void Mesh::fromShape(Shape &shape, bool append)
{
  if (!append)
    clear();
  shape.generateMesh(this);
}

void Mesh::resizeTris(int numTris)
{
  mTris.resize(numTris);
  rebuildChannels();
}
void Mesh::resizeNodes(int numNodes)
{
  mNodes.resize(numNodes);
  rebuildChannels();
}

//! do a quick check whether a rebuild is necessary, and if yes do rebuild
void Mesh::rebuildQuickCheck()
{
  if (mCorners.size() != 3 * mTris.size())
    rebuildCorners();
  if (m1RingLookup.size() != mNodes.size())
    rebuildLookup();
}

void Mesh::rebuildCorners(int from, int to)
{
  mCorners.resize(3 * mTris.size());
  if (to < 0)
    to = mTris.size();

  // fill in basic info
  for (int tri = from; tri < to; tri++) {
    for (int c = 0; c < 3; c++) {
      const int idx = tri * 3 + c;
      mCorners[idx].tri = tri;
      mCorners[idx].node = mTris[tri].c[c];
      mCorners[idx].next = 3 * tri + ((c + 1) % 3);
      mCorners[idx].prev = 3 * tri + ((c + 2) % 3);
      mCorners[idx].opposite = -1;
    }
  }

  // set opposite info
  int maxc = to * 3;
  for (int c = from * 3; c < maxc; c++) {
    int next = mCorners[mCorners[c].next].node;
    int prev = mCorners[mCorners[c].prev].node;

    // find corner with same next/prev nodes
    for (int c2 = c + 1; c2 < maxc; c2++) {
      int next2 = mCorners[mCorners[c2].next].node;
      if (next2 != next && next2 != prev)
        continue;
      int prev2 = mCorners[mCorners[c2].prev].node;
      if (prev2 != next && prev2 != prev)
        continue;

      // found
      mCorners[c].opposite = c2;
      mCorners[c2].opposite = c;
      break;
    }
    if (mCorners[c].opposite < 0) {
      // didn't find opposite
      errMsg("can't rebuild corners, index without an opposite");
    }
  }

  rebuildChannels();
}

void Mesh::rebuildLookup(int from, int to)
{
  if (from == 0 && to < 0)
    m1RingLookup.clear();
  m1RingLookup.resize(mNodes.size());
  if (to < 0)
    to = mTris.size();
  from *= 3;
  to *= 3;
  for (int i = from; i < to; i++) {
    const int node = mCorners[i].node;
    m1RingLookup[node].nodes.insert(mCorners[mCorners[i].next].node);
    m1RingLookup[node].nodes.insert(mCorners[mCorners[i].prev].node);
    m1RingLookup[node].tris.insert(mCorners[i].tri);
  }
}

void Mesh::rebuildChannels()
{
  for (size_t i = 0; i < mTriChannels.size(); i++)
    mTriChannels[i]->resize(mTris.size());
  for (size_t i = 0; i < mNodeChannels.size(); i++)
    mNodeChannels[i]->resize(mNodes.size());
}

struct _KnAdvectMeshInGrid : public KernelBase {
  _KnAdvectMeshInGrid(const KernelBase &base,
                      vector<Node> &nodes,
                      const FlagGrid &flags,
                      const MACGrid &vel,
                      const Real dt,
                      vector<Vec3> &u)
      : KernelBase(base), nodes(nodes), flags(flags), vel(vel), dt(dt), u(u)
  {
  }
  inline void op(IndexInt idx,
                 vector<Node> &nodes,
                 const FlagGrid &flags,
                 const MACGrid &vel,
                 const Real dt,
                 vector<Vec3> &u) const
  {
    if (nodes[idx].flags & Mesh::NfFixed)
      u[idx] = 0.0;
    else if (!flags.isInBounds(nodes[idx].pos, 1))
      u[idx] = 0.0;
    else
      u[idx] = vel.getInterpolated(nodes[idx].pos) * dt;
  }
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, nodes, flags, vel, dt, u);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  vector<Node> &nodes;
  const FlagGrid &flags;
  const MACGrid &vel;
  const Real dt;
  vector<Vec3> &u;
};
struct KnAdvectMeshInGrid : public KernelBase {
  KnAdvectMeshInGrid(vector<Node> &nodes, const FlagGrid &flags, const MACGrid &vel, const Real dt)
      : KernelBase(nodes.size()),
        _inner(KernelBase(nodes.size()), nodes, flags, vel, dt, u),
        nodes(nodes),
        flags(flags),
        vel(vel),
        dt(dt),
        u((size))
  {
    runMessage();
    run();
  }
  void run()
  {
    _inner.run();
  }
  inline operator vector<Vec3>()
  {
    return u;
  }
  inline vector<Vec3> &getRet()
  {
    return u;
  }
  inline vector<Node> &getArg0()
  {
    return nodes;
  }
  typedef vector<Node> type0;
  inline const FlagGrid &getArg1()
  {
    return flags;
  }
  typedef FlagGrid type1;
  inline const MACGrid &getArg2()
  {
    return vel;
  }
  typedef MACGrid type2;
  inline const Real &getArg3()
  {
    return dt;
  }
  typedef Real type3;
  void runMessage()
  {
    debMsg("Executing kernel KnAdvectMeshInGrid ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  _KnAdvectMeshInGrid _inner;
  vector<Node> &nodes;
  const FlagGrid &flags;
  const MACGrid &vel;
  const Real dt;
  vector<Vec3> u;
};

// advection plugin
void Mesh::advectInGrid(FlagGrid &flags, MACGrid &vel, int integrationMode)
{
  KnAdvectMeshInGrid kernel(mNodes, flags, vel, getParent()->getDt());
  integratePointSet(kernel, integrationMode);
}

void Mesh::scale(Vec3 s)
{
  for (size_t i = 0; i < mNodes.size(); i++)
    mNodes[i].pos *= s;
}

void Mesh::offset(Vec3 o)
{
  for (size_t i = 0; i < mNodes.size(); i++)
    mNodes[i].pos += o;
}

void Mesh::rotate(Vec3 thetas)
{
  // rotation thetas are in radians (e.g. pi is equal to 180 degrees)
  auto rotate = [&](Real theta, unsigned int first_axis, unsigned int second_axis) {
    if (theta == 0.0f)
      return;

    Real sin_t = sin(theta);
    Real cos_t = cos(theta);

    Real sin_sign = first_axis == 0u && second_axis == 2u ? -1.0f : 1.0f;
    sin_t *= sin_sign;

    size_t length = mNodes.size();
    for (size_t n = 0; n < length; ++n) {
      Vec3 &node = mNodes[n].pos;
      Real first_axis_val = node[first_axis];
      Real second_axis_val = node[second_axis];
      node[first_axis] = first_axis_val * cos_t - second_axis_val * sin_t;
      node[second_axis] = second_axis_val * cos_t + first_axis_val * sin_t;
    }
  };

  // rotate x
  rotate(thetas[0], 1u, 2u);
  // rotate y
  rotate(thetas[1], 0u, 2u);
  // rotate z
  rotate(thetas[2], 0u, 1u);
}

void Mesh::computeVelocity(Mesh &oldMesh, MACGrid &vel)
{
  // Early return if sizes do not match
  if (oldMesh.mNodes.size() != mNodes.size())
    return;

  // temp grid
  Grid<Vec3> veloMeanCounter(getParent());
  veloMeanCounter.setConst(0.0f);

  bool bIs2D = getParent()->is2D();

  // calculate velocities from previous to current frame (per vertex)
  for (size_t i = 0; i < mNodes.size(); ++i) {
    // skip vertices that are not needed for 2D
    if (bIs2D && (mNodes[i].pos.z < -0.5f || mNodes[i].pos.z > 0.5f))
      continue;

    Vec3 velo = mNodes[i].pos - oldMesh.mNodes[i].pos;
    vel.setInterpolated(mNodes[i].pos, velo, &(veloMeanCounter[0]));
  }

  // discretize the vertex velocities by averaging them on the grid
  vel.safeDivide(veloMeanCounter);
}

void Mesh::removeTri(int tri)
{
  // delete triangles by overwriting them with elements from the end of the array.
  if (tri != (int)mTris.size() - 1) {
    // if this is the last element, and it is marked for deletion,
    // don't waste cycles transfering data to itself,
    // and DEFINITELY don't transfer .opposite data to other, untainted triangles.

    // old corners hold indices on the end of the corners array
    // new corners holds indices in the new spot in the middle of the array
    Corner *oldcorners[3];
    Corner *newcorners[3];
    int oldtri = mTris.size() - 1;
    for (int c = 0; c < 3; c++) {
      oldcorners[c] = &corners(oldtri, c);
      newcorners[c] = &corners(tri, c);
    }

    // move the position of the triangle
    mTris[tri] = mTris[oldtri];

    // 1) update c.node, c.opposite (c.next and c.prev should be fine as they are)
    for (int c = 0; c < 3; c++) {
      newcorners[c]->node = mTris[tri].c[c];
      newcorners[c]->opposite = oldcorners[c]->opposite;
    }

    //  2) c.opposite.opposite = c
    for (int c = 0; c < 3; c++) {
      if (newcorners[c]->opposite >= 0)
        mCorners[newcorners[c]->opposite].opposite = 3 * tri + c;
    }

    // update tri lookup
    for (int c = 0; c < 3; c++) {
      int node = mTris[tri].c[c];
      m1RingLookup[node].tris.erase(oldtri);
      m1RingLookup[node].tris.insert(tri);
    }
  }

  // transfer tri props
  for (size_t p = 0; p < mTriChannels.size(); p++)
    mTriChannels[p]->remove(tri);

  // pop the triangle and corners out of the vector
  mTris.pop_back();
  mCorners.resize(mTris.size() * 3);
}

void Mesh::removeNodes(const vector<int> &deletedNodes)
{
  // After we delete the nodes that are marked for removal,
  // the size of mNodes will be the current size - the size of the deleted array.
  // We are going to move the elements at the end of the array
  // (everything with an index >= newsize)
  // to the deleted spots.
  // We have to map all references to the last few nodes to their new locations.
  int newsize = (int)(mNodes.size() - deletedNodes.size());

  vector<int> new_index(deletedNodes.size());
  int di, ni;
  for (ni = 0; ni < (int)new_index.size(); ni++)
    new_index[ni] = 0;
  for (di = 0; di < (int)deletedNodes.size(); di++) {
    if (deletedNodes[di] >= newsize)
      new_index[deletedNodes[di] - newsize] = -1;  // tag this node as invalid
  }
  for (di = 0, ni = 0; ni < (int)new_index.size(); ni++, di++) {
    // we need to find a valid node to move
    // we marked invalid nodes in the earlier loop with a (-1),
    // so pick anything but those
    while (ni < (int)new_index.size() && new_index[ni] == -1)
      ni++;

    if (ni >= (int)new_index.size())
      break;

    // next we need to find a valid spot to move the node to.
    // we iterate through deleted[] until we find a valid spot
    while (di < (int)new_index.size() && deletedNodes[di] >= newsize)
      di++;

    // now we assign the valid node to the valid spot
    new_index[ni] = deletedNodes[di];
  }

  // Now we have a map of valid indices.
  // we move node[newsize+i] to location new_index[i].
  // We ignore the nodes with a -1 index, because they should not be moved.
  for (int i = 0; i < (int)new_index.size(); i++) {
    if (new_index[i] != -1)
      mNodes[new_index[i]] = mNodes[newsize + i];
  }
  mNodes.resize(newsize);

  // handle vertex properties
  for (size_t i = 0; i < mNodeChannels.size(); i++)
    mNodeChannels[i]->renumber(new_index, newsize);

  // finally, we reconnect everything that used to point to this vertex.
  for (size_t tri = 0, n = 0; tri < mTris.size(); tri++) {
    for (int c = 0; c < 3; c++, n++) {
      if (mCorners[n].node >= newsize) {
        int newindex = new_index[mCorners[n].node - newsize];
        mCorners[n].node = newindex;
        mTris[mCorners[n].tri].c[c] = newindex;
      }
    }
  }

  // renumber 1-ring
  for (int i = 0; i < (int)new_index.size(); i++) {
    if (new_index[i] != -1) {
      m1RingLookup[new_index[i]].nodes.swap(m1RingLookup[newsize + i].nodes);
      m1RingLookup[new_index[i]].tris.swap(m1RingLookup[newsize + i].tris);
    }
  }
  m1RingLookup.resize(newsize);
  vector<int> reStack(new_index.size());
  for (int i = 0; i < newsize; i++) {
    set<int> &cs = m1RingLookup[i].nodes;
    int reNum = 0;
    // find all nodes > newsize
    set<int>::reverse_iterator itend = cs.rend();
    for (set<int>::reverse_iterator it = cs.rbegin(); it != itend; ++it) {
      if (*it < newsize)
        break;
      reStack[reNum++] = *it;
    }
    // kill them and insert shifted values
    if (reNum > 0) {
      cs.erase(cs.find(reStack[reNum - 1]), cs.end());
      for (int j = 0; j < reNum; j++) {
        cs.insert(new_index[reStack[j] - newsize]);
#ifdef DEBUG
        if (new_index[reStack[j] - newsize] == -1)
          errMsg("invalid node present in 1-ring set");
#endif
      }
    }
  }
}

void Mesh::mergeNode(int node, int delnode)
{
  set<int> &ring = m1RingLookup[delnode].nodes;
  for (set<int>::iterator it = ring.begin(); it != ring.end(); ++it) {
    m1RingLookup[*it].nodes.erase(delnode);
    if (*it != node) {
      m1RingLookup[*it].nodes.insert(node);
      m1RingLookup[node].nodes.insert(*it);
    }
  }
  set<int> &ringt = m1RingLookup[delnode].tris;
  for (set<int>::iterator it = ringt.begin(); it != ringt.end(); ++it) {
    const int t = *it;
    for (int c = 0; c < 3; c++) {
      if (mCorners[3 * t + c].node == delnode) {
        mCorners[3 * t + c].node = node;
        mTris[t].c[c] = node;
      }
    }
    m1RingLookup[node].tris.insert(t);
  }
  for (size_t i = 0; i < mNodeChannels.size(); i++) {
    // weight is fixed to 1/2 for now
    mNodeChannels[i]->mergeWith(node, delnode, 0.5);
  }
}

void Mesh::removeTriFromLookup(int tri)
{
  for (int c = 0; c < 3; c++) {
    int node = mTris[tri].c[c];
    m1RingLookup[node].tris.erase(tri);
  }
}

void Mesh::addCorner(Corner a)
{
  mCorners.push_back(a);
}

int Mesh::addTri(Triangle a)
{
  mTris.push_back(a);
  for (int c = 0; c < 3; c++) {
    int node = a.c[c];
    int nextnode = a.c[(c + 1) % 3];
    if ((int)m1RingLookup.size() <= node)
      m1RingLookup.resize(node + 1);
    if ((int)m1RingLookup.size() <= nextnode)
      m1RingLookup.resize(nextnode + 1);
    m1RingLookup[node].nodes.insert(nextnode);
    m1RingLookup[nextnode].nodes.insert(node);
    m1RingLookup[node].tris.insert(mTris.size() - 1);
  }
  return mTris.size() - 1;
}

int Mesh::addNode(Node a)
{
  mNodes.push_back(a);
  if (m1RingLookup.size() < mNodes.size())
    m1RingLookup.resize(mNodes.size());

  // if mdata exists, add zero init for every node
  addAllMdata();

  return mNodes.size() - 1;
}

void Mesh::computeVertexNormals()
{
  for (size_t i = 0; i < mNodes.size(); i++) {
    mNodes[i].normal = 0.0;
  }
  for (size_t t = 0; t < mTris.size(); t++) {
    Vec3 p0 = getNode(t, 0), p1 = getNode(t, 1), p2 = getNode(t, 2);
    Vec3 n0 = p0 - p1, n1 = p1 - p2, n2 = p2 - p0;
    Real l0 = normSquare(n0), l1 = normSquare(n1), l2 = normSquare(n2);

    Vec3 nm = cross(n0, n1);

    mNodes[mTris[t].c[0]].normal += nm * (1.0 / (l0 * l2));
    mNodes[mTris[t].c[1]].normal += nm * (1.0 / (l0 * l1));
    mNodes[mTris[t].c[2]].normal += nm * (1.0 / (l1 * l2));
  }
  for (size_t i = 0; i < mNodes.size(); i++) {
    normalize(mNodes[i].normal);
  }
}

void Mesh::fastNodeLookupRebuild(int corner)
{
  int node = mCorners[corner].node;
  m1RingLookup[node].nodes.clear();
  m1RingLookup[node].tris.clear();
  int start = mCorners[corner].prev;
  int current = start;
  do {
    m1RingLookup[node].nodes.insert(mCorners[current].node);
    m1RingLookup[node].tris.insert(mCorners[current].tri);
    current = mCorners[mCorners[current].opposite].next;
    if (current < 0)
      errMsg("Can't use fastNodeLookupRebuild on incomplete surfaces");
  } while (current != start);
}

void Mesh::sanityCheck(bool strict, vector<int> *deletedNodes, map<int, bool> *taintedTris)
{
  const int nodes = numNodes(), tris = numTris(), corners = 3 * tris;
  for (size_t i = 0; i < mNodeChannels.size(); i++) {
    if (mNodeChannels[i]->size() != nodes)
      errMsg("Node channel size mismatch");
  }
  for (size_t i = 0; i < mTriChannels.size(); i++) {
    if (mTriChannels[i]->size() != tris)
      errMsg("Tri channel size mismatch");
  }
  if ((int)m1RingLookup.size() != nodes)
    errMsg("1Ring size wrong");
  for (size_t t = 0; t < mTris.size(); t++) {
    if (taintedTris && taintedTris->find(t) != taintedTris->end())
      continue;
    for (int c = 0; c < 3; c++) {
      int corner = t * 3 + c;
      int node = mTris[t].c[c];
      int next = mTris[t].c[(c + 1) % 3];
      int prev = mTris[t].c[(c + 2) % 3];
      int rnext = mCorners[corner].next;
      int rprev = mCorners[corner].prev;
      int ro = mCorners[corner].opposite;
      if (node < 0 || node >= nodes || next < 0 || next >= nodes || prev < 0 || prev >= nodes)
        errMsg("invalid node entry");
      if (mCorners[corner].node != node || mCorners[corner].tri != (int)t)
        errMsg("invalid basic corner entry");
      if (rnext < 0 || rnext >= corners || rprev < 0 || rprev >= corners || ro >= corners)
        errMsg("invalid corner links");
      if (mCorners[rnext].node != next || mCorners[rprev].node != prev)
        errMsg("invalid corner next/prev");
      if (strict && ro < 0)
        errMsg("opposite missing");
      if (mCorners[ro].opposite != corner)
        errMsg("invalid opposite ref");
      set<int> &rnodes = m1RingLookup[node].nodes;
      set<int> &rtris = m1RingLookup[node].tris;
      if (rnodes.find(next) == rnodes.end() || rnodes.find(prev) == rnodes.end()) {
        debMsg("Tri " << t << " " << node << " " << next << " " << prev, 1);
        for (set<int>::iterator it = rnodes.begin(); it != rnodes.end(); ++it)
          debMsg(*it, 1);
        errMsg("node missing in 1ring");
      }
      if (rtris.find(t) == rtris.end()) {
        debMsg("Tri " << t << " " << node, 1);
        errMsg("tri missing in 1ring");
      }
    }
  }
  for (int n = 0; n < nodes; n++) {
    bool docheck = true;
    if (deletedNodes)
      for (size_t e = 0; e < deletedNodes->size(); e++)
        if ((*deletedNodes)[e] == n)
          docheck = false;
    ;

    if (docheck) {
      set<int> &sn = m1RingLookup[n].nodes;
      set<int> &st = m1RingLookup[n].tris;
      set<int> sn2;

      for (set<int>::iterator it = st.begin(); it != st.end(); ++it) {
        bool found = false;
        for (int c = 0; c < 3; c++) {
          if (mTris[*it].c[c] == n)
            found = true;
          else
            sn2.insert(mTris[*it].c[c]);
        }
        if (!found) {
          cout << *it << " " << n << endl;
          for (int c = 0; c < 3; c++)
            cout << mTris[*it].c[c] << endl;
          errMsg("invalid triangle in 1ring");
        }
        if (taintedTris && taintedTris->find(*it) != taintedTris->end()) {
          cout << *it << endl;
          errMsg("tainted tri still is use");
        }
      }
      if (sn.size() != sn2.size())
        errMsg("invalid nodes in 1ring");
      for (set<int>::iterator it = sn.begin(), it2 = sn2.begin(); it != sn.end(); ++it, ++it2) {
        if (*it != *it2) {
          cout << "Node " << n << ": " << *it << " vs " << *it2 << endl;
          errMsg("node ring mismatch");
        }
      }
    }
  }
}

//*****************************************************************************
// rasterization

void meshSDF(Mesh &mesh, LevelsetGrid &levelset, Real sigma, Real cutoff = 0.);

//! helper vec3 array container
struct CVec3Ptr {
  Real *x, *y, *z;
  inline Vec3 get(int i) const
  {
    return Vec3(x[i], y[i], z[i]);
  };
  inline void set(int i, const Vec3 &v)
  {
    x[i] = v.x;
    y[i] = v.y;
    z[i] = v.z;
  };
};
//! helper vec3 array, for CUDA compatibility, remove at some point
struct CVec3Array {
  CVec3Array(int sz)
  {
    x.resize(sz);
    y.resize(sz);
    z.resize(sz);
  }
  CVec3Array(const std::vector<Vec3> &v)
  {
    x.resize(v.size());
    y.resize(v.size());
    z.resize(v.size());
    for (size_t i = 0; i < v.size(); i++) {
      x[i] = v[i].x;
      y[i] = v[i].y;
      z[i] = v[i].z;
    }
  }
  CVec3Ptr data()
  {
    CVec3Ptr a = {x.data(), y.data(), z.data()};
    return a;
  }
  inline const Vec3 operator[](int idx) const
  {
    return Vec3((Real)x[idx], (Real)y[idx], (Real)z[idx]);
  }
  inline void set(int idx, const Vec3 &v)
  {
    x[idx] = v.x;
    y[idx] = v.y;
    z[idx] = v.z;
  }
  inline int size()
  {
    return x.size();
  }
  std::vector<Real> x, y, z;
};

// void SDFKernel(const int* partStart, const int* partLen, CVec3Ptr pos, CVec3Ptr normal, Real*
// sdf, Vec3i gridRes, int intRadius, Real safeRadius2, Real cutoff2, Real isigma2);
//! helper for rasterization
static void SDFKernel(Grid<int> &partStart,
                      Grid<int> &partLen,
                      CVec3Ptr pos,
                      CVec3Ptr normal,
                      LevelsetGrid &sdf,
                      Vec3i gridRes,
                      int intRadius,
                      Real safeRadius2,
                      Real cutoff2,
                      Real isigma2)
{
  for (int cnt_x(0); cnt_x < gridRes[0]; ++cnt_x) {
    for (int cnt_y(0); cnt_y < gridRes[1]; ++cnt_y) {
      for (int cnt_z(0); cnt_z < gridRes[2]; ++cnt_z) {
        // cell index, center
        Vec3i cell = Vec3i(cnt_x, cnt_y, cnt_z);
        if (cell.x >= gridRes.x || cell.y >= gridRes.y || cell.z >= gridRes.z)
          return;
        Vec3 cpos = Vec3(cell.x + 0.5f, cell.y + 0.5f, cell.z + 0.5f);
        Real sum = 0.0f;
        Real dist = 0.0f;

        // query cells within block radius
        Vec3i minBlock = Vec3i(
            max(cell.x - intRadius, 0), max(cell.y - intRadius, 0), max(cell.z - intRadius, 0));
        Vec3i maxBlock = Vec3i(min(cell.x + intRadius, gridRes.x - 1),
                               min(cell.y + intRadius, gridRes.y - 1),
                               min(cell.z + intRadius, gridRes.z - 1));
        for (int i = minBlock.x; i <= maxBlock.x; i++)
          for (int j = minBlock.y; j <= maxBlock.y; j++)
            for (int k = minBlock.z; k <= maxBlock.z; k++) {
              // test if block is within radius
              Vec3 d = Vec3(cell.x - i, cell.y - j, cell.z - k);
              Real normSqr = d[0] * d[0] + d[1] * d[1] + d[2] * d[2];
              if (normSqr > safeRadius2)
                continue;

              // find source cell, and divide it into thread blocks
              int block = i + gridRes.x * (j + gridRes.y * k);
              int slen = partLen[block];
              if (slen == 0)
                continue;
              int start = partStart[block];

              // process sources
              for (int s = 0; s < slen; s++) {

                // actual sdf kernel
                Vec3 r = cpos - pos.get(start + s);
                Real normSqr = r[0] * r[0] + r[1] * r[1] + r[2] * r[2];
                Real r2 = normSqr;
                if (r2 < cutoff2) {
                  Real w = expf(-r2 * isigma2);
                  sum += w;
                  dist += dot(normal.get(start + s), r) * w;
                }
              }
            }
        // writeback
        if (sum > 0.0f) {
          // sdf[cell.x + gridRes.x * (cell.y + gridRes.y * cell.z)] = dist / sum;
          sdf(cell.x, cell.y, cell.z) = dist / sum;
        }
      }
    }
  }
}

static inline IndexInt _cIndex(const Vec3 &pos, const Vec3i &s)
{
  Vec3i p = toVec3i(pos);
  if (p.x < 0 || p.y < 0 || p.z < 0 || p.x >= s.x || p.y >= s.y || p.z >= s.z)
    return -1;
  return p.x + s.x * (p.y + s.y * p.z);
}

//! Kernel: Apply a shape to a grid, setting value inside

template<class T> struct ApplyMeshToGrid : public KernelBase {
  ApplyMeshToGrid(Grid<T> *grid, Grid<Real> &sdf, T value, FlagGrid *respectFlags)
      : KernelBase(grid, 0), grid(grid), sdf(sdf), value(value), respectFlags(respectFlags)
  {
    runMessage();
    run();
  }
  inline void op(
      int i, int j, int k, Grid<T> *grid, Grid<Real> &sdf, T value, FlagGrid *respectFlags) const
  {
    if (respectFlags && respectFlags->isObstacle(i, j, k))
      return;
    if (sdf(i, j, k) < 0) {
      (*grid)(i, j, k) = value;
    }
  }
  inline Grid<T> *getArg0()
  {
    return grid;
  }
  typedef Grid<T> type0;
  inline Grid<Real> &getArg1()
  {
    return sdf;
  }
  typedef Grid<Real> type1;
  inline T &getArg2()
  {
    return value;
  }
  typedef T type2;
  inline FlagGrid *getArg3()
  {
    return respectFlags;
  }
  typedef FlagGrid type3;
  void runMessage()
  {
    debMsg("Executing kernel ApplyMeshToGrid ", 3);
    debMsg("Kernel range"
               << " x " << maxX << " y " << maxY << " z " << minZ << " - " << maxZ << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    const int _maxX = maxX;
    const int _maxY = maxY;
    if (maxZ > 1) {
      for (int k = __r.begin(); k != (int)__r.end(); k++)
        for (int j = 0; j < _maxY; j++)
          for (int i = 0; i < _maxX; i++)
            op(i, j, k, grid, sdf, value, respectFlags);
    }
    else {
      const int k = 0;
      for (int j = __r.begin(); j != (int)__r.end(); j++)
        for (int i = 0; i < _maxX; i++)
          op(i, j, k, grid, sdf, value, respectFlags);
    }
  }
  void run()
  {
    if (maxZ > 1)
      tbb::parallel_for(tbb::blocked_range<IndexInt>(minZ, maxZ), *this);
    else
      tbb::parallel_for(tbb::blocked_range<IndexInt>(0, maxY), *this);
  }
  Grid<T> *grid;
  Grid<Real> &sdf;
  T value;
  FlagGrid *respectFlags;
};

void Mesh::applyMeshToGrid(GridBase *grid, FlagGrid *respectFlags, Real cutoff, Real meshSigma)
{
  FluidSolver dummy(grid->getSize());
  LevelsetGrid mesh_sdf(&dummy, false);
  meshSDF(*this, mesh_sdf, meshSigma, cutoff);  // meshSigma=2 fixed here

#if NOPYTHON != 1
  if (grid->getType() & GridBase::TypeInt)
    ApplyMeshToGrid<int>((Grid<int> *)grid, mesh_sdf, _args.get<int>("value"), respectFlags);
  else if (grid->getType() & GridBase::TypeReal)
    ApplyMeshToGrid<Real>((Grid<Real> *)grid, mesh_sdf, _args.get<Real>("value"), respectFlags);
  else if (grid->getType() & GridBase::TypeVec3)
    ApplyMeshToGrid<Vec3>((Grid<Vec3> *)grid, mesh_sdf, _args.get<Vec3>("value"), respectFlags);
  else
    errMsg("Shape::applyToGrid(): unknown grid type");
#else
  errMsg("Not yet supported...");
#endif
}

void Mesh::computeLevelset(LevelsetGrid &levelset, Real sigma, Real cutoff)
{
  meshSDF(*this, levelset, sigma, cutoff);
}

LevelsetGrid Mesh::getLevelset(Real sigma, Real cutoff)
{
  LevelsetGrid phi(getParent());
  meshSDF(*this, phi, sigma, cutoff);
  return phi;
}

void meshSDF(Mesh &mesh, LevelsetGrid &levelset, Real sigma, Real cutoff)
{
  if (cutoff < 0)
    cutoff = 2 * sigma;
  Real maxEdgeLength = 0.75;
  Real numSamplesPerCell = 0.75;

  Vec3i gridRes = levelset.getSize();
  Vec3 mult = toVec3(gridRes) / toVec3(mesh.getParent()->getGridSize());

  // prepare center values
  std::vector<Vec3> center;
  std::vector<Vec3> normals;
  short bigEdges(0);
  std::vector<Vec3> samplePoints;
  for (int i = 0; i < mesh.numTris(); i++) {
    center.push_back(Vec3(mesh.getFaceCenter(i) * mult));
    normals.push_back(mesh.getFaceNormal(i));
    // count big, stretched edges
    bigEdges = 0;
    for (short edge(0); edge < 3; ++edge) {
      if (norm(mesh.getEdge(i, edge)) > maxEdgeLength) {
        bigEdges += 1 << edge;
      }
    }
    if (bigEdges > 0) {
      samplePoints.clear();
      short iterA, pointA, iterB, pointB;
      int numSamples0 = norm(mesh.getEdge(i, 1)) * numSamplesPerCell;
      int numSamples1 = norm(mesh.getEdge(i, 2)) * numSamplesPerCell;
      int numSamples2 = norm(mesh.getEdge(i, 0)) * numSamplesPerCell;
      if (!(bigEdges & (1 << 0))) {
        // loop through 0,1
        iterA = numSamples1;
        pointA = 0;
        iterB = numSamples2;
        pointB = 1;
      }
      else if (!(bigEdges & (1 << 1))) {
        // loop through 1,2
        iterA = numSamples2;
        pointA = 1;
        iterB = numSamples0;
        pointB = 2;
      }
      else {
        // loop through 2,0
        iterA = numSamples0;
        pointA = 2;
        iterB = numSamples1;
        pointB = 0;
      }

      Real u(0.), v(0.), w(0.);  // barycentric uvw coords
      Vec3 samplePoint, normal;
      for (int sample0(0); sample0 < iterA; ++sample0) {
        u = Real(1. * sample0 / iterA);
        for (int sample1(0); sample1 < iterB; ++sample1) {
          v = Real(1. * sample1 / iterB);
          w = 1 - u - v;
          if (w < 0.)
            continue;
          samplePoint = mesh.getNode(i, pointA) * mult * u + mesh.getNode(i, pointB) * mult * v +
                        mesh.getNode(i, (3 - pointA - pointB)) * mult * w;
          samplePoints.push_back(samplePoint);
          normal = mesh.getFaceNormal(i);
          normals.push_back(normal);
        }
      }
      center.insert(center.end(), samplePoints.begin(), samplePoints.end());
    }
  }

  // prepare grid
  levelset.setConst(-cutoff);

  // 1. count sources per cell
  Grid<int> srcPerCell(levelset.getParent());
  for (size_t i = 0; i < center.size(); i++) {
    IndexInt idx = _cIndex(center[i], gridRes);
    if (idx >= 0)
      srcPerCell[idx]++;
  }

  // 2. create start index lookup
  Grid<int> srcCellStart(levelset.getParent());
  int cnt = 0;
  FOR_IJK(srcCellStart)
  {
    IndexInt idx = srcCellStart.index(i, j, k);
    srcCellStart[idx] = cnt;
    cnt += srcPerCell[idx];
  }

  // 3. reorder nodes
  CVec3Array reorderPos(center.size());
  CVec3Array reorderNormal(center.size());
  {
    Grid<int> curSrcCell(levelset.getParent());
    for (int i = 0; i < (int)center.size(); i++) {
      IndexInt idx = _cIndex(center[i], gridRes);
      if (idx < 0)
        continue;
      IndexInt idx2 = srcCellStart[idx] + curSrcCell[idx];
      reorderPos.set(idx2, center[i]);
      reorderNormal.set(idx2, normals[i]);
      curSrcCell[idx]++;
    }
  }

  // construct parameters
  Real safeRadius = cutoff + sqrt(3.0) * 0.5;
  Real safeRadius2 = safeRadius * safeRadius;
  Real cutoff2 = cutoff * cutoff;
  Real isigma2 = 1.0 / (sigma * sigma);
  int intRadius = (int)(cutoff + 0.5);

  SDFKernel(srcCellStart,
            srcPerCell,
            reorderPos.data(),
            reorderNormal.data(),
            levelset,
            gridRes,
            intRadius,
            safeRadius2,
            cutoff2,
            isigma2);

  // floodfill outside
  std::stack<Vec3i> outside;
  FOR_IJK(levelset)
  {
    if (levelset(i, j, k) >= cutoff - 1.0f)
      outside.push(Vec3i(i, j, k));
  }
  while (!outside.empty()) {
    Vec3i c = outside.top();
    outside.pop();
    levelset(c) = cutoff;
    if (c.x > 0 && levelset(c.x - 1, c.y, c.z) < 0)
      outside.push(Vec3i(c.x - 1, c.y, c.z));
    if (c.y > 0 && levelset(c.x, c.y - 1, c.z) < 0)
      outside.push(Vec3i(c.x, c.y - 1, c.z));
    if (c.z > 0 && levelset(c.x, c.y, c.z - 1) < 0)
      outside.push(Vec3i(c.x, c.y, c.z - 1));
    if (c.x < levelset.getSizeX() - 1 && levelset(c.x + 1, c.y, c.z) < 0)
      outside.push(Vec3i(c.x + 1, c.y, c.z));
    if (c.y < levelset.getSizeY() - 1 && levelset(c.x, c.y + 1, c.z) < 0)
      outside.push(Vec3i(c.x, c.y + 1, c.z));
    if (c.z < levelset.getSizeZ() - 1 && levelset(c.x, c.y, c.z + 1) < 0)
      outside.push(Vec3i(c.x, c.y, c.z + 1));
  };
}

// Blender data pointer accessors
std::string Mesh::getNodesDataPointer()
{
  std::ostringstream out;
  out << &mNodes;
  return out.str();
}
std::string Mesh::getTrisDataPointer()
{
  std::ostringstream out;
  out << &mTris;
  return out.str();
}

// mesh data

MeshDataBase::MeshDataBase(FluidSolver *parent) : PbClass(parent), mMesh(nullptr)
{
}

MeshDataBase::~MeshDataBase()
{
  // notify parent of deletion
  if (mMesh)
    mMesh->deregister(this);
}

// actual data implementation

template<class T>
MeshDataImpl<T>::MeshDataImpl(FluidSolver *parent)
    : MeshDataBase(parent), mpGridSource(nullptr), mGridSourceMAC(false)
{
}

template<class T>
MeshDataImpl<T>::MeshDataImpl(FluidSolver *parent, MeshDataImpl<T> *other)
    : MeshDataBase(parent), mpGridSource(nullptr), mGridSourceMAC(false)
{
  this->mData = other->mData;
  setName(other->getName());
}

template<class T> MeshDataImpl<T>::~MeshDataImpl()
{
}

template<class T> IndexInt MeshDataImpl<T>::getSizeSlow() const
{
  return mData.size();
}
template<class T> void MeshDataImpl<T>::addEntry()
{
  // add zero'ed entry
  T tmp = T(0.);
  // for debugging, force init:
  // tmp = T(0.02 * mData.size()); // increasing
  // tmp = T(1.); // constant 1
  return mData.push_back(tmp);
}
template<class T> void MeshDataImpl<T>::resize(IndexInt s)
{
  mData.resize(s);
}
template<class T> void MeshDataImpl<T>::copyValueSlow(IndexInt from, IndexInt to)
{
  this->copyValue(from, to);
}
template<class T> MeshDataBase *MeshDataImpl<T>::clone()
{
  MeshDataImpl<T> *npd = new MeshDataImpl<T>(getParent(), this);
  return npd;
}

template<class T> void MeshDataImpl<T>::setSource(Grid<T> *grid, bool isMAC)
{
  mpGridSource = grid;
  mGridSourceMAC = isMAC;
  if (grid && isMAC)
    assertMsg(grid->getType() & GridBase::TypeMAC, "Given grid is not a valid MAC grid");
}

template<class T> void MeshDataImpl<T>::initNewValue(IndexInt idx, Vec3 pos)
{
  if (!mpGridSource)
    mData[idx] = 0;
  else {
    mData[idx] = mpGridSource->getInterpolated(pos);
  }
}

// special handling needed for velocities
template<> void MeshDataImpl<Vec3>::initNewValue(IndexInt idx, Vec3 pos)
{
  if (!mpGridSource)
    mData[idx] = 0;
  else {
    if (!mGridSourceMAC)
      mData[idx] = mpGridSource->getInterpolated(pos);
    else
      mData[idx] = ((MACGrid *)mpGridSource)->getInterpolated(pos);
  }
}

//! update additional mesh data
void Mesh::updateDataFields()
{
  for (size_t i = 0; i < mNodes.size(); ++i) {
    Vec3 pos = mNodes[i].pos;
    for (IndexInt md = 0; md < (IndexInt)mMdataReal.size(); ++md)
      mMdataReal[md]->initNewValue(i, pos);
    for (IndexInt md = 0; md < (IndexInt)mMdataVec3.size(); ++md)
      mMdataVec3[md]->initNewValue(i, pos);
    for (IndexInt md = 0; md < (IndexInt)mMdataInt.size(); ++md)
      mMdataInt[md]->initNewValue(i, pos);
  }
}

template<typename T> int MeshDataImpl<T>::load(string name)
{
  if (name.find_last_of('.') == string::npos)
    errMsg("file '" + name + "' does not have an extension");
  string ext = name.substr(name.find_last_of('.'));
  if (ext == ".uni")
    return readMdataUni<T>(name, this);
  else if (ext == ".raw")  // raw = uni for now
    return readMdataUni<T>(name, this);
  else
    errMsg("mesh data '" + name + "' filetype not supported for loading");
  return 0;
}

template<typename T> int MeshDataImpl<T>::save(string name)
{
  if (name.find_last_of('.') == string::npos)
    errMsg("file '" + name + "' does not have an extension");
  string ext = name.substr(name.find_last_of('.'));
  if (ext == ".uni")
    return writeMdataUni<T>(name, this);
  else if (ext == ".raw")  // raw = uni for now
    return writeMdataUni<T>(name, this);
  else
    errMsg("mesh data '" + name + "' filetype not supported for saving");
  return 0;
}

// specializations

template<> MeshDataBase::MdataType MeshDataImpl<Real>::getType() const
{
  return MeshDataBase::TypeReal;
}
template<> MeshDataBase::MdataType MeshDataImpl<int>::getType() const
{
  return MeshDataBase::TypeInt;
}
template<> MeshDataBase::MdataType MeshDataImpl<Vec3>::getType() const
{
  return MeshDataBase::TypeVec3;
}

template<class T> struct knSetMdataConst : public KernelBase {
  knSetMdataConst(MeshDataImpl<T> &mdata, T value)
      : KernelBase(mdata.size()), mdata(mdata), value(value)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &mdata, T value) const
  {
    mdata[idx] = value;
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return mdata;
  }
  typedef MeshDataImpl<T> type0;
  inline T &getArg1()
  {
    return value;
  }
  typedef T type1;
  void runMessage()
  {
    debMsg("Executing kernel knSetMdataConst ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, mdata, value);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &mdata;
  T value;
};

template<class T, class S> struct knMdataSet : public KernelBase {
  knMdataSet(MeshDataImpl<T> &me, const MeshDataImpl<S> &other)
      : KernelBase(me.size()), me(me), other(other)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &me, const MeshDataImpl<S> &other) const
  {
    me[idx] += other[idx];
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline const MeshDataImpl<S> &getArg1()
  {
    return other;
  }
  typedef MeshDataImpl<S> type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataSet ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, other);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  const MeshDataImpl<S> &other;
};
template<class T, class S> struct knMdataAdd : public KernelBase {
  knMdataAdd(MeshDataImpl<T> &me, const MeshDataImpl<S> &other)
      : KernelBase(me.size()), me(me), other(other)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &me, const MeshDataImpl<S> &other) const
  {
    me[idx] += other[idx];
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline const MeshDataImpl<S> &getArg1()
  {
    return other;
  }
  typedef MeshDataImpl<S> type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataAdd ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, other);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  const MeshDataImpl<S> &other;
};
template<class T, class S> struct knMdataSub : public KernelBase {
  knMdataSub(MeshDataImpl<T> &me, const MeshDataImpl<S> &other)
      : KernelBase(me.size()), me(me), other(other)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &me, const MeshDataImpl<S> &other) const
  {
    me[idx] -= other[idx];
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline const MeshDataImpl<S> &getArg1()
  {
    return other;
  }
  typedef MeshDataImpl<S> type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataSub ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, other);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  const MeshDataImpl<S> &other;
};
template<class T, class S> struct knMdataMult : public KernelBase {
  knMdataMult(MeshDataImpl<T> &me, const MeshDataImpl<S> &other)
      : KernelBase(me.size()), me(me), other(other)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &me, const MeshDataImpl<S> &other) const
  {
    me[idx] *= other[idx];
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline const MeshDataImpl<S> &getArg1()
  {
    return other;
  }
  typedef MeshDataImpl<S> type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataMult ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, other);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  const MeshDataImpl<S> &other;
};
template<class T, class S> struct knMdataDiv : public KernelBase {
  knMdataDiv(MeshDataImpl<T> &me, const MeshDataImpl<S> &other)
      : KernelBase(me.size()), me(me), other(other)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &me, const MeshDataImpl<S> &other) const
  {
    me[idx] /= other[idx];
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline const MeshDataImpl<S> &getArg1()
  {
    return other;
  }
  typedef MeshDataImpl<S> type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataDiv ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, other);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  const MeshDataImpl<S> &other;
};

template<class T, class S> struct knMdataSetScalar : public KernelBase {
  knMdataSetScalar(MeshDataImpl<T> &me, const S &other)
      : KernelBase(me.size()), me(me), other(other)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &me, const S &other) const
  {
    me[idx] = other;
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline const S &getArg1()
  {
    return other;
  }
  typedef S type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataSetScalar ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, other);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  const S &other;
};
template<class T, class S> struct knMdataAddScalar : public KernelBase {
  knMdataAddScalar(MeshDataImpl<T> &me, const S &other)
      : KernelBase(me.size()), me(me), other(other)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &me, const S &other) const
  {
    me[idx] += other;
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline const S &getArg1()
  {
    return other;
  }
  typedef S type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataAddScalar ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, other);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  const S &other;
};
template<class T, class S> struct knMdataMultScalar : public KernelBase {
  knMdataMultScalar(MeshDataImpl<T> &me, const S &other)
      : KernelBase(me.size()), me(me), other(other)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &me, const S &other) const
  {
    me[idx] *= other;
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline const S &getArg1()
  {
    return other;
  }
  typedef S type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataMultScalar ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, other);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  const S &other;
};
template<class T, class S> struct knMdataScaledAdd : public KernelBase {
  knMdataScaledAdd(MeshDataImpl<T> &me, const MeshDataImpl<T> &other, const S &factor)
      : KernelBase(me.size()), me(me), other(other), factor(factor)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx,
                 MeshDataImpl<T> &me,
                 const MeshDataImpl<T> &other,
                 const S &factor) const
  {
    me[idx] += factor * other[idx];
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline const MeshDataImpl<T> &getArg1()
  {
    return other;
  }
  typedef MeshDataImpl<T> type1;
  inline const S &getArg2()
  {
    return factor;
  }
  typedef S type2;
  void runMessage()
  {
    debMsg("Executing kernel knMdataScaledAdd ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, other, factor);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  const MeshDataImpl<T> &other;
  const S &factor;
};

template<class T> struct knMdataSafeDiv : public KernelBase {
  knMdataSafeDiv(MeshDataImpl<T> &me, const MeshDataImpl<T> &other)
      : KernelBase(me.size()), me(me), other(other)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &me, const MeshDataImpl<T> &other) const
  {
    me[idx] = safeDivide(me[idx], other[idx]);
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline const MeshDataImpl<T> &getArg1()
  {
    return other;
  }
  typedef MeshDataImpl<T> type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataSafeDiv ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, other);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  const MeshDataImpl<T> &other;
};
template<class T> struct knMdataSetConst : public KernelBase {
  knMdataSetConst(MeshDataImpl<T> &mdata, T value)
      : KernelBase(mdata.size()), mdata(mdata), value(value)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &mdata, T value) const
  {
    mdata[idx] = value;
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return mdata;
  }
  typedef MeshDataImpl<T> type0;
  inline T &getArg1()
  {
    return value;
  }
  typedef T type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataSetConst ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, mdata, value);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &mdata;
  T value;
};

template<class T> struct knMdataClamp : public KernelBase {
  knMdataClamp(MeshDataImpl<T> &me, T min, T max)
      : KernelBase(me.size()), me(me), min(min), max(max)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &me, T min, T max) const
  {
    me[idx] = clamp(me[idx], min, max);
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline T &getArg1()
  {
    return min;
  }
  typedef T type1;
  inline T &getArg2()
  {
    return max;
  }
  typedef T type2;
  void runMessage()
  {
    debMsg("Executing kernel knMdataClamp ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, min, max);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  T min;
  T max;
};
template<class T> struct knMdataClampMin : public KernelBase {
  knMdataClampMin(MeshDataImpl<T> &me, const T vmin) : KernelBase(me.size()), me(me), vmin(vmin)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &me, const T vmin) const
  {
    me[idx] = std::max(vmin, me[idx]);
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline const T &getArg1()
  {
    return vmin;
  }
  typedef T type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataClampMin ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, vmin);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  const T vmin;
};
template<class T> struct knMdataClampMax : public KernelBase {
  knMdataClampMax(MeshDataImpl<T> &me, const T vmax) : KernelBase(me.size()), me(me), vmax(vmax)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<T> &me, const T vmax) const
  {
    me[idx] = std::min(vmax, me[idx]);
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline const T &getArg1()
  {
    return vmax;
  }
  typedef T type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataClampMax ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, vmax);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  const T vmax;
};
struct knMdataClampMinVec3 : public KernelBase {
  knMdataClampMinVec3(MeshDataImpl<Vec3> &me, const Real vmin)
      : KernelBase(me.size()), me(me), vmin(vmin)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<Vec3> &me, const Real vmin) const
  {
    me[idx].x = std::max(vmin, me[idx].x);
    me[idx].y = std::max(vmin, me[idx].y);
    me[idx].z = std::max(vmin, me[idx].z);
  }
  inline MeshDataImpl<Vec3> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<Vec3> type0;
  inline const Real &getArg1()
  {
    return vmin;
  }
  typedef Real type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataClampMinVec3 ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, vmin);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<Vec3> &me;
  const Real vmin;
};
struct knMdataClampMaxVec3 : public KernelBase {
  knMdataClampMaxVec3(MeshDataImpl<Vec3> &me, const Real vmax)
      : KernelBase(me.size()), me(me), vmax(vmax)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, MeshDataImpl<Vec3> &me, const Real vmax) const
  {
    me[idx].x = std::min(vmax, me[idx].x);
    me[idx].y = std::min(vmax, me[idx].y);
    me[idx].z = std::min(vmax, me[idx].z);
  }
  inline MeshDataImpl<Vec3> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<Vec3> type0;
  inline const Real &getArg1()
  {
    return vmax;
  }
  typedef Real type1;
  void runMessage()
  {
    debMsg("Executing kernel knMdataClampMaxVec3 ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, vmax);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<Vec3> &me;
  const Real vmax;
};

// python operators

template<typename T> MeshDataImpl<T> &MeshDataImpl<T>::copyFrom(const MeshDataImpl<T> &a)
{
  assertMsg(a.mData.size() == mData.size(),
            "different mdata size " << a.mData.size() << " vs " << this->mData.size());
  memcpy(&mData[0], &a.mData[0], sizeof(T) * mData.size());
  return *this;
}

template<typename T> void MeshDataImpl<T>::setConst(T s)
{
  knMdataSetScalar<T, T> op(*this, s);
}

template<typename T> void MeshDataImpl<T>::setConstRange(T s, const int begin, const int end)
{
  for (int i = begin; i < end; ++i)
    (*this)[i] = s;
}

// special set by flag
template<class T, class S> struct knMdataSetScalarIntFlag : public KernelBase {
  knMdataSetScalarIntFlag(MeshDataImpl<T> &me,
                          const S &other,
                          const MeshDataImpl<int> &t,
                          const int itype)
      : KernelBase(me.size()), me(me), other(other), t(t), itype(itype)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx,
                 MeshDataImpl<T> &me,
                 const S &other,
                 const MeshDataImpl<int> &t,
                 const int itype) const
  {
    if (t[idx] & itype)
      me[idx] = other;
  }
  inline MeshDataImpl<T> &getArg0()
  {
    return me;
  }
  typedef MeshDataImpl<T> type0;
  inline const S &getArg1()
  {
    return other;
  }
  typedef S type1;
  inline const MeshDataImpl<int> &getArg2()
  {
    return t;
  }
  typedef MeshDataImpl<int> type2;
  inline const int &getArg3()
  {
    return itype;
  }
  typedef int type3;
  void runMessage()
  {
    debMsg("Executing kernel knMdataSetScalarIntFlag ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, me, other, t, itype);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  MeshDataImpl<T> &me;
  const S &other;
  const MeshDataImpl<int> &t;
  const int itype;
};
template<typename T>
void MeshDataImpl<T>::setConstIntFlag(T s, const MeshDataImpl<int> &t, const int itype)
{
  knMdataSetScalarIntFlag<T, T> op(*this, s, t, itype);
}

template<typename T> void MeshDataImpl<T>::add(const MeshDataImpl<T> &a)
{
  knMdataAdd<T, T> op(*this, a);
}
template<typename T> void MeshDataImpl<T>::sub(const MeshDataImpl<T> &a)
{
  knMdataSub<T, T> op(*this, a);
}

template<typename T> void MeshDataImpl<T>::addConst(T s)
{
  knMdataAddScalar<T, T> op(*this, s);
}

template<typename T> void MeshDataImpl<T>::addScaled(const MeshDataImpl<T> &a, const T &factor)
{
  knMdataScaledAdd<T, T> op(*this, a, factor);
}

template<typename T> void MeshDataImpl<T>::mult(const MeshDataImpl<T> &a)
{
  knMdataMult<T, T> op(*this, a);
}

template<typename T> void MeshDataImpl<T>::safeDiv(const MeshDataImpl<T> &a)
{
  knMdataSafeDiv<T> op(*this, a);
}

template<typename T> void MeshDataImpl<T>::multConst(T s)
{
  knMdataMultScalar<T, T> op(*this, s);
}

template<typename T> void MeshDataImpl<T>::clamp(Real vmin, Real vmax)
{
  knMdataClamp<T> op(*this, vmin, vmax);
}

template<typename T> void MeshDataImpl<T>::clampMin(Real vmin)
{
  knMdataClampMin<T> op(*this, vmin);
}
template<typename T> void MeshDataImpl<T>::clampMax(Real vmax)
{
  knMdataClampMax<T> op(*this, vmax);
}

template<> void MeshDataImpl<Vec3>::clampMin(Real vmin)
{
  knMdataClampMinVec3 op(*this, vmin);
}
template<> void MeshDataImpl<Vec3>::clampMax(Real vmax)
{
  knMdataClampMaxVec3 op(*this, vmax);
}

template<typename T> struct KnPtsSum : public KernelBase {
  KnPtsSum(const MeshDataImpl<T> &val, const MeshDataImpl<int> *t, const int itype)
      : KernelBase(val.size()), val(val), t(t), itype(itype), result(T(0.))
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx,
                 const MeshDataImpl<T> &val,
                 const MeshDataImpl<int> *t,
                 const int itype,
                 T &result)
  {
    if (t && !((*t)[idx] & itype))
      return;
    result += val[idx];
  }
  inline operator T()
  {
    return result;
  }
  inline T &getRet()
  {
    return result;
  }
  inline const MeshDataImpl<T> &getArg0()
  {
    return val;
  }
  typedef MeshDataImpl<T> type0;
  inline const MeshDataImpl<int> *getArg1()
  {
    return t;
  }
  typedef MeshDataImpl<int> type1;
  inline const int &getArg2()
  {
    return itype;
  }
  typedef int type2;
  void runMessage()
  {
    debMsg("Executing kernel KnPtsSum ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r)
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, val, t, itype, result);
  }
  void run()
  {
    tbb::parallel_reduce(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  KnPtsSum(KnPtsSum &o, tbb::split)
      : KernelBase(o), val(o.val), t(o.t), itype(o.itype), result(T(0.))
  {
  }
  void join(const KnPtsSum &o)
  {
    result += o.result;
  }
  const MeshDataImpl<T> &val;
  const MeshDataImpl<int> *t;
  const int itype;
  T result;
};
template<typename T> struct KnPtsSumSquare : public KernelBase {
  KnPtsSumSquare(const MeshDataImpl<T> &val) : KernelBase(val.size()), val(val), result(0.)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, const MeshDataImpl<T> &val, Real &result)
  {
    result += normSquare(val[idx]);
  }
  inline operator Real()
  {
    return result;
  }
  inline Real &getRet()
  {
    return result;
  }
  inline const MeshDataImpl<T> &getArg0()
  {
    return val;
  }
  typedef MeshDataImpl<T> type0;
  void runMessage()
  {
    debMsg("Executing kernel KnPtsSumSquare ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r)
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, val, result);
  }
  void run()
  {
    tbb::parallel_reduce(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  KnPtsSumSquare(KnPtsSumSquare &o, tbb::split) : KernelBase(o), val(o.val), result(0.)
  {
  }
  void join(const KnPtsSumSquare &o)
  {
    result += o.result;
  }
  const MeshDataImpl<T> &val;
  Real result;
};
template<typename T> struct KnPtsSumMagnitude : public KernelBase {
  KnPtsSumMagnitude(const MeshDataImpl<T> &val) : KernelBase(val.size()), val(val), result(0.)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, const MeshDataImpl<T> &val, Real &result)
  {
    result += norm(val[idx]);
  }
  inline operator Real()
  {
    return result;
  }
  inline Real &getRet()
  {
    return result;
  }
  inline const MeshDataImpl<T> &getArg0()
  {
    return val;
  }
  typedef MeshDataImpl<T> type0;
  void runMessage()
  {
    debMsg("Executing kernel KnPtsSumMagnitude ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r)
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, val, result);
  }
  void run()
  {
    tbb::parallel_reduce(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  KnPtsSumMagnitude(KnPtsSumMagnitude &o, tbb::split) : KernelBase(o), val(o.val), result(0.)
  {
  }
  void join(const KnPtsSumMagnitude &o)
  {
    result += o.result;
  }
  const MeshDataImpl<T> &val;
  Real result;
};

template<typename T> T MeshDataImpl<T>::sum(const MeshDataImpl<int> *t, const int itype) const
{
  return KnPtsSum<T>(*this, t, itype);
}
template<typename T> Real MeshDataImpl<T>::sumSquare() const
{
  return KnPtsSumSquare<T>(*this);
}
template<typename T> Real MeshDataImpl<T>::sumMagnitude() const
{
  return KnPtsSumMagnitude<T>(*this);
}

template<typename T>

struct CompMdata_Min : public KernelBase {
  CompMdata_Min(const MeshDataImpl<T> &val)
      : KernelBase(val.size()), val(val), minVal(std::numeric_limits<Real>::max())
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, const MeshDataImpl<T> &val, Real &minVal)
  {
    if (val[idx] < minVal)
      minVal = val[idx];
  }
  inline operator Real()
  {
    return minVal;
  }
  inline Real &getRet()
  {
    return minVal;
  }
  inline const MeshDataImpl<T> &getArg0()
  {
    return val;
  }
  typedef MeshDataImpl<T> type0;
  void runMessage()
  {
    debMsg("Executing kernel CompMdata_Min ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r)
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, val, minVal);
  }
  void run()
  {
    tbb::parallel_reduce(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  CompMdata_Min(CompMdata_Min &o, tbb::split)
      : KernelBase(o), val(o.val), minVal(std::numeric_limits<Real>::max())
  {
  }
  void join(const CompMdata_Min &o)
  {
    minVal = min(minVal, o.minVal);
  }
  const MeshDataImpl<T> &val;
  Real minVal;
};

template<typename T>

struct CompMdata_Max : public KernelBase {
  CompMdata_Max(const MeshDataImpl<T> &val)
      : KernelBase(val.size()), val(val), maxVal(-std::numeric_limits<Real>::max())
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, const MeshDataImpl<T> &val, Real &maxVal)
  {
    if (val[idx] > maxVal)
      maxVal = val[idx];
  }
  inline operator Real()
  {
    return maxVal;
  }
  inline Real &getRet()
  {
    return maxVal;
  }
  inline const MeshDataImpl<T> &getArg0()
  {
    return val;
  }
  typedef MeshDataImpl<T> type0;
  void runMessage()
  {
    debMsg("Executing kernel CompMdata_Max ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r)
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, val, maxVal);
  }
  void run()
  {
    tbb::parallel_reduce(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  CompMdata_Max(CompMdata_Max &o, tbb::split)
      : KernelBase(o), val(o.val), maxVal(-std::numeric_limits<Real>::max())
  {
  }
  void join(const CompMdata_Max &o)
  {
    maxVal = max(maxVal, o.maxVal);
  }
  const MeshDataImpl<T> &val;
  Real maxVal;
};

template<typename T> Real MeshDataImpl<T>::getMin()
{
  return CompMdata_Min<T>(*this);
}

template<typename T> Real MeshDataImpl<T>::getMaxAbs()
{
  Real amin = CompMdata_Min<T>(*this);
  Real amax = CompMdata_Max<T>(*this);
  return max(fabs(amin), fabs(amax));
}

template<typename T> Real MeshDataImpl<T>::getMax()
{
  return CompMdata_Max<T>(*this);
}

template<typename T>
void MeshDataImpl<T>::printMdata(IndexInt start, IndexInt stop, bool printIndex)
{
  std::ostringstream sstr;
  IndexInt s = (start > 0 ? start : 0);
  IndexInt e = (stop > 0 ? stop : (IndexInt)mData.size());
  s = Manta::clamp(s, (IndexInt)0, (IndexInt)mData.size());
  e = Manta::clamp(e, (IndexInt)0, (IndexInt)mData.size());

  for (IndexInt i = s; i < e; ++i) {
    if (printIndex)
      sstr << i << ": ";
    sstr << mData[i] << " "
         << "\n";
  }
  debMsg(sstr.str(), 1);
}
template<class T> std::string MeshDataImpl<T>::getDataPointer()
{
  std::ostringstream out;
  out << &mData;
  return out.str();
}

// specials for vec3
// work on length values, ie, always positive (in contrast to scalar versions above)

struct CompMdata_MinVec3 : public KernelBase {
  CompMdata_MinVec3(const MeshDataImpl<Vec3> &val)
      : KernelBase(val.size()), val(val), minVal(-std::numeric_limits<Real>::max())
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, const MeshDataImpl<Vec3> &val, Real &minVal)
  {
    const Real s = normSquare(val[idx]);
    if (s < minVal)
      minVal = s;
  }
  inline operator Real()
  {
    return minVal;
  }
  inline Real &getRet()
  {
    return minVal;
  }
  inline const MeshDataImpl<Vec3> &getArg0()
  {
    return val;
  }
  typedef MeshDataImpl<Vec3> type0;
  void runMessage()
  {
    debMsg("Executing kernel CompMdata_MinVec3 ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r)
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, val, minVal);
  }
  void run()
  {
    tbb::parallel_reduce(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  CompMdata_MinVec3(CompMdata_MinVec3 &o, tbb::split)
      : KernelBase(o), val(o.val), minVal(-std::numeric_limits<Real>::max())
  {
  }
  void join(const CompMdata_MinVec3 &o)
  {
    minVal = min(minVal, o.minVal);
  }
  const MeshDataImpl<Vec3> &val;
  Real minVal;
};

struct CompMdata_MaxVec3 : public KernelBase {
  CompMdata_MaxVec3(const MeshDataImpl<Vec3> &val)
      : KernelBase(val.size()), val(val), maxVal(-std::numeric_limits<Real>::min())
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx, const MeshDataImpl<Vec3> &val, Real &maxVal)
  {
    const Real s = normSquare(val[idx]);
    if (s > maxVal)
      maxVal = s;
  }
  inline operator Real()
  {
    return maxVal;
  }
  inline Real &getRet()
  {
    return maxVal;
  }
  inline const MeshDataImpl<Vec3> &getArg0()
  {
    return val;
  }
  typedef MeshDataImpl<Vec3> type0;
  void runMessage()
  {
    debMsg("Executing kernel CompMdata_MaxVec3 ", 3);
    debMsg("Kernel range"
               << " size " << size << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r)
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, val, maxVal);
  }
  void run()
  {
    tbb::parallel_reduce(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  CompMdata_MaxVec3(CompMdata_MaxVec3 &o, tbb::split)
      : KernelBase(o), val(o.val), maxVal(-std::numeric_limits<Real>::min())
  {
  }
  void join(const CompMdata_MaxVec3 &o)
  {
    maxVal = max(maxVal, o.maxVal);
  }
  const MeshDataImpl<Vec3> &val;
  Real maxVal;
};

template<> Real MeshDataImpl<Vec3>::getMin()
{
  return sqrt(CompMdata_MinVec3(*this));
}

template<> Real MeshDataImpl<Vec3>::getMaxAbs()
{
  return sqrt(CompMdata_MaxVec3(*this));  // no minimum necessary here
}

template<> Real MeshDataImpl<Vec3>::getMax()
{
  return sqrt(CompMdata_MaxVec3(*this));
}

// explicit instantiation
template class MeshDataImpl<int>;
template class MeshDataImpl<Real>;
template class MeshDataImpl<Vec3>;

}  // namespace Manta
