

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

/******************************************************************************
 *
 * MantaFlow fluid solver framework
 * Copyright 2011-2015 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
 *
 * Plugins for advection
 *
 ******************************************************************************/

#include "vectorbase.h"
#include "grid.h"
#include "kernel.h"
#include <limits>

using namespace std;

namespace Manta {

//! Semi-Lagrange interpolation kernel

template<class T> struct SemiLagrange : public KernelBase {
  SemiLagrange(const FlagGrid &flags,
               const MACGrid &vel,
               Grid<T> &dst,
               const Grid<T> &src,
               Real dt,
               bool isLevelset,
               int orderSpace,
               int orderTrace)
      : KernelBase(&flags, 1),
        flags(flags),
        vel(vel),
        dst(dst),
        src(src),
        dt(dt),
        isLevelset(isLevelset),
        orderSpace(orderSpace),
        orderTrace(orderTrace)
  {
    runMessage();
    run();
  }
  inline void op(int i,
                 int j,
                 int k,
                 const FlagGrid &flags,
                 const MACGrid &vel,
                 Grid<T> &dst,
                 const Grid<T> &src,
                 Real dt,
                 bool isLevelset,
                 int orderSpace,
                 int orderTrace) const
  {
    if (orderTrace == 1) {
      // traceback position
      Vec3 pos = Vec3(i + 0.5f, j + 0.5f, k + 0.5f) - vel.getCentered(i, j, k) * dt;
      dst(i, j, k) = src.getInterpolatedHi(pos, orderSpace);
    }
    else if (orderTrace == 2) {
      // backtracing using explicit midpoint
      Vec3 p0 = Vec3(i + 0.5f, j + 0.5f, k + 0.5f);
      Vec3 p1 = p0 - vel.getCentered(i, j, k) * dt * 0.5;
      Vec3 p2 = p0 - vel.getInterpolated(p1) * dt;
      dst(i, j, k) = src.getInterpolatedHi(p2, orderSpace);
    }
    else {
      assertMsg(false, "Unknown backtracing order " << orderTrace);
    }
  }
  inline const FlagGrid &getArg0()
  {
    return flags;
  }
  typedef FlagGrid type0;
  inline const MACGrid &getArg1()
  {
    return vel;
  }
  typedef MACGrid type1;
  inline Grid<T> &getArg2()
  {
    return dst;
  }
  typedef Grid<T> type2;
  inline const Grid<T> &getArg3()
  {
    return src;
  }
  typedef Grid<T> type3;
  inline Real &getArg4()
  {
    return dt;
  }
  typedef Real type4;
  inline bool &getArg5()
  {
    return isLevelset;
  }
  typedef bool type5;
  inline int &getArg6()
  {
    return orderSpace;
  }
  typedef int type6;
  inline int &getArg7()
  {
    return orderTrace;
  }
  typedef int type7;
  void runMessage()
  {
    debMsg("Executing kernel SemiLagrange ", 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 = 1; j < _maxY; j++)
          for (int i = 1; i < _maxX; i++)
            op(i, j, k, flags, vel, dst, src, dt, isLevelset, orderSpace, orderTrace);
    }
    else {
      const int k = 0;
      for (int j = __r.begin(); j != (int)__r.end(); j++)
        for (int i = 1; i < _maxX; i++)
          op(i, j, k, flags, vel, dst, src, dt, isLevelset, orderSpace, orderTrace);
    }
  }
  void run()
  {
    if (maxZ > 1)
      tbb::parallel_for(tbb::blocked_range<IndexInt>(minZ, maxZ), *this);
    else
      tbb::parallel_for(tbb::blocked_range<IndexInt>(1, maxY), *this);
  }
  const FlagGrid &flags;
  const MACGrid &vel;
  Grid<T> &dst;
  const Grid<T> &src;
  Real dt;
  bool isLevelset;
  int orderSpace;
  int orderTrace;
};

//! Semi-Lagrange interpolation kernel for MAC grids

struct SemiLagrangeMAC : public KernelBase {
  SemiLagrangeMAC(const FlagGrid &flags,
                  const MACGrid &vel,
                  MACGrid &dst,
                  const MACGrid &src,
                  Real dt,
                  int orderSpace,
                  int orderTrace)
      : KernelBase(&flags, 1),
        flags(flags),
        vel(vel),
        dst(dst),
        src(src),
        dt(dt),
        orderSpace(orderSpace),
        orderTrace(orderTrace)
  {
    runMessage();
    run();
  }
  inline void op(int i,
                 int j,
                 int k,
                 const FlagGrid &flags,
                 const MACGrid &vel,
                 MACGrid &dst,
                 const MACGrid &src,
                 Real dt,
                 int orderSpace,
                 int orderTrace) const
  {
    if (orderTrace == 1) {
      // get currect velocity at MAC position
      // no need to shift xpos etc. as lookup field is also shifted
      Vec3 xpos = Vec3(i + 0.5f, j + 0.5f, k + 0.5f) - vel.getAtMACX(i, j, k) * dt;
      Real vx = src.getInterpolatedComponentHi<0>(xpos, orderSpace);
      Vec3 ypos = Vec3(i + 0.5f, j + 0.5f, k + 0.5f) - vel.getAtMACY(i, j, k) * dt;
      Real vy = src.getInterpolatedComponentHi<1>(ypos, orderSpace);
      Vec3 zpos = Vec3(i + 0.5f, j + 0.5f, k + 0.5f) - vel.getAtMACZ(i, j, k) * dt;
      Real vz = src.getInterpolatedComponentHi<2>(zpos, orderSpace);

      dst(i, j, k) = Vec3(vx, vy, vz);
    }
    else if (orderTrace == 2) {
      Vec3 p0 = Vec3(i + 0.5, j + 0.5, k + 0.5);
      Vec3 xp0 = Vec3(i, j + 0.5f, k + 0.5f);
      Vec3 xp1 = xp0 - src.getAtMACX(i, j, k) * dt * 0.5;
      Vec3 xp2 = p0 - src.getInterpolated(xp1) * dt;
      Real vx = src.getInterpolatedComponentHi<0>(xp2, orderSpace);
      Vec3 yp0 = Vec3(i + 0.5f, j, k + 0.5f);
      Vec3 yp1 = yp0 - src.getAtMACY(i, j, k) * dt * 0.5;
      Vec3 yp2 = p0 - src.getInterpolated(yp1) * dt;
      Real vy = src.getInterpolatedComponentHi<1>(yp2, orderSpace);
      Vec3 zp0 = Vec3(i + 0.5f, j + 0.5f, k);
      Vec3 zp1 = zp0 - src.getAtMACZ(i, j, k) * dt * 0.5;
      Vec3 zp2 = p0 - src.getInterpolated(zp1) * dt;
      Real vz = src.getInterpolatedComponentHi<2>(zp2, orderSpace);

      dst(i, j, k) = Vec3(vx, vy, vz);
    }
    else {
      assertMsg(false, "Unknown backtracing order " << orderTrace);
    }
  }
  inline const FlagGrid &getArg0()
  {
    return flags;
  }
  typedef FlagGrid type0;
  inline const MACGrid &getArg1()
  {
    return vel;
  }
  typedef MACGrid type1;
  inline MACGrid &getArg2()
  {
    return dst;
  }
  typedef MACGrid type2;
  inline const MACGrid &getArg3()
  {
    return src;
  }
  typedef MACGrid type3;
  inline Real &getArg4()
  {
    return dt;
  }
  typedef Real type4;
  inline int &getArg5()
  {
    return orderSpace;
  }
  typedef int type5;
  inline int &getArg6()
  {
    return orderTrace;
  }
  typedef int type6;
  void runMessage()
  {
    debMsg("Executing kernel SemiLagrangeMAC ", 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 = 1; j < _maxY; j++)
          for (int i = 1; i < _maxX; i++)
            op(i, j, k, flags, vel, dst, src, dt, orderSpace, orderTrace);
    }
    else {
      const int k = 0;
      for (int j = __r.begin(); j != (int)__r.end(); j++)
        for (int i = 1; i < _maxX; i++)
          op(i, j, k, flags, vel, dst, src, dt, orderSpace, orderTrace);
    }
  }
  void run()
  {
    if (maxZ > 1)
      tbb::parallel_for(tbb::blocked_range<IndexInt>(minZ, maxZ), *this);
    else
      tbb::parallel_for(tbb::blocked_range<IndexInt>(1, maxY), *this);
  }
  const FlagGrid &flags;
  const MACGrid &vel;
  MACGrid &dst;
  const MACGrid &src;
  Real dt;
  int orderSpace;
  int orderTrace;
};

//! Kernel: Correct based on forward and backward SL steps (for both centered & mac grids)

template<class T> struct MacCormackCorrect : public KernelBase {
  MacCormackCorrect(const FlagGrid &flags,
                    Grid<T> &dst,
                    const Grid<T> &old,
                    const Grid<T> &fwd,
                    const Grid<T> &bwd,
                    Real strength,
                    bool isLevelSet,
                    bool isMAC = false)
      : KernelBase(&flags, 0),
        flags(flags),
        dst(dst),
        old(old),
        fwd(fwd),
        bwd(bwd),
        strength(strength),
        isLevelSet(isLevelSet),
        isMAC(isMAC)
  {
    runMessage();
    run();
  }
  inline void op(IndexInt idx,
                 const FlagGrid &flags,
                 Grid<T> &dst,
                 const Grid<T> &old,
                 const Grid<T> &fwd,
                 const Grid<T> &bwd,
                 Real strength,
                 bool isLevelSet,
                 bool isMAC = false) const
  {
    dst[idx] = fwd[idx];

    if (flags.isFluid(idx)) {
      // only correct inside fluid region; note, strenth of correction can be modified here
      dst[idx] += strength * 0.5 * (old[idx] - bwd[idx]);
    }
  }
  inline const FlagGrid &getArg0()
  {
    return flags;
  }
  typedef FlagGrid type0;
  inline Grid<T> &getArg1()
  {
    return dst;
  }
  typedef Grid<T> type1;
  inline const Grid<T> &getArg2()
  {
    return old;
  }
  typedef Grid<T> type2;
  inline const Grid<T> &getArg3()
  {
    return fwd;
  }
  typedef Grid<T> type3;
  inline const Grid<T> &getArg4()
  {
    return bwd;
  }
  typedef Grid<T> type4;
  inline Real &getArg5()
  {
    return strength;
  }
  typedef Real type5;
  inline bool &getArg6()
  {
    return isLevelSet;
  }
  typedef bool type6;
  inline bool &getArg7()
  {
    return isMAC;
  }
  typedef bool type7;
  void runMessage()
  {
    debMsg("Executing kernel MacCormackCorrect ", 3);
    debMsg("Kernel range"
               << " x " << maxX << " y " << maxY << " z " << minZ << " - " << maxZ << " ",
           4);
  };
  void operator()(const tbb::blocked_range<IndexInt> &__r) const
  {
    for (IndexInt idx = __r.begin(); idx != (IndexInt)__r.end(); idx++)
      op(idx, flags, dst, old, fwd, bwd, strength, isLevelSet, isMAC);
  }
  void run()
  {
    tbb::parallel_for(tbb::blocked_range<IndexInt>(0, size), *this);
  }
  const FlagGrid &flags;
  Grid<T> &dst;
  const Grid<T> &old;
  const Grid<T> &fwd;
  const Grid<T> &bwd;
  Real strength;
  bool isLevelSet;
  bool isMAC;
};

//! Kernel: Correct based on forward and backward SL steps (for both centered & mac grids)

template<class T> struct MacCormackCorrectMAC : public KernelBase {
  MacCormackCorrectMAC(const FlagGrid &flags,
                       Grid<T> &dst,
                       const Grid<T> &old,
                       const Grid<T> &fwd,
                       const Grid<T> &bwd,
                       Real strength,
                       bool isLevelSet,
                       bool isMAC = false)
      : KernelBase(&flags, 0),
        flags(flags),
        dst(dst),
        old(old),
        fwd(fwd),
        bwd(bwd),
        strength(strength),
        isLevelSet(isLevelSet),
        isMAC(isMAC)
  {
    runMessage();
    run();
  }
  inline void op(int i,
                 int j,
                 int k,
                 const FlagGrid &flags,
                 Grid<T> &dst,
                 const Grid<T> &old,
                 const Grid<T> &fwd,
                 const Grid<T> &bwd,
                 Real strength,
                 bool isLevelSet,
                 bool isMAC = false) const
  {
    bool skip[3] = {false, false, false};

    if (!flags.isFluid(i, j, k))
      skip[0] = skip[1] = skip[2] = true;
    if (isMAC) {
      if ((i > 0) && (!flags.isFluid(i - 1, j, k)))
        skip[0] = true;
      if ((j > 0) && (!flags.isFluid(i, j - 1, k)))
        skip[1] = true;
      if ((k > 0) && (!flags.isFluid(i, j, k - 1)))
        skip[2] = true;
    }

    for (int c = 0; c < 3; ++c) {
      if (skip[c]) {
        dst(i, j, k)[c] = fwd(i, j, k)[c];
      }
      else {
        // perform actual correction with given strength
        dst(i, j, k)[c] = fwd(i, j, k)[c] + strength * 0.5 * (old(i, j, k)[c] - bwd(i, j, k)[c]);
      }
    }
  }
  inline const FlagGrid &getArg0()
  {
    return flags;
  }
  typedef FlagGrid type0;
  inline Grid<T> &getArg1()
  {
    return dst;
  }
  typedef Grid<T> type1;
  inline const Grid<T> &getArg2()
  {
    return old;
  }
  typedef Grid<T> type2;
  inline const Grid<T> &getArg3()
  {
    return fwd;
  }
  typedef Grid<T> type3;
  inline const Grid<T> &getArg4()
  {
    return bwd;
  }
  typedef Grid<T> type4;
  inline Real &getArg5()
  {
    return strength;
  }
  typedef Real type5;
  inline bool &getArg6()
  {
    return isLevelSet;
  }
  typedef bool type6;
  inline bool &getArg7()
  {
    return isMAC;
  }
  typedef bool type7;
  void runMessage()
  {
    debMsg("Executing kernel MacCormackCorrectMAC ", 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, flags, dst, old, fwd, bwd, strength, isLevelSet, isMAC);
    }
    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, flags, dst, old, fwd, bwd, strength, isLevelSet, isMAC);
    }
  }
  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);
  }
  const FlagGrid &flags;
  Grid<T> &dst;
  const Grid<T> &old;
  const Grid<T> &fwd;
  const Grid<T> &bwd;
  Real strength;
  bool isLevelSet;
  bool isMAC;
};

// Helper to collect min/max in a template
template<class T> inline void getMinMax(T &minv, T &maxv, const T &val)
{
  if (val < minv)
    minv = val;
  if (val > maxv)
    maxv = val;
}
template<> inline void getMinMax<Vec3>(Vec3 &minv, Vec3 &maxv, const Vec3 &val)
{
  getMinMax(minv.x, maxv.x, val.x);
  getMinMax(minv.y, maxv.y, val.y);
  getMinMax(minv.z, maxv.z, val.z);
}

//! detect out of bounds value
template<class T> inline bool cmpMinMax(T &minv, T &maxv, const T &val)
{
  if (val < minv)
    return true;
  if (val > maxv)
    return true;
  return false;
}
template<> inline bool cmpMinMax<Vec3>(Vec3 &minv, Vec3 &maxv, const Vec3 &val)
{
  return (cmpMinMax(minv.x, maxv.x, val.x) | cmpMinMax(minv.y, maxv.y, val.y) |
          cmpMinMax(minv.z, maxv.z, val.z));
}

#define checkFlag(x, y, z) (flags((x), (y), (z)) & (FlagGrid::TypeFluid | FlagGrid::TypeEmpty))

//! Helper function for clamping non-mac grids (those have specialized per component version below)
//  Note - 2 clamp modes, a sharper one (default, clampMode 1, also uses backward step),
//         and a softer version (clampMode 2) that is recommended in Andy's paper
template<class T>
inline T doClampComponent(const Vec3i &gridSize,
                          const FlagGrid &flags,
                          T dst,
                          const Grid<T> &orig,
                          const T fwd,
                          const Vec3 &pos,
                          const Vec3 &vel,
                          const int clampMode)
{
  T minv(std::numeric_limits<Real>::max()), maxv(-std::numeric_limits<Real>::max());
  bool haveFl = false;

  // forward (and optionally) backward
  Vec3i positions[2];
  int numPos = 1;
  positions[0] = toVec3i(pos - vel);
  if (clampMode == 1) {
    numPos = 2;
    positions[1] = toVec3i(pos + vel);
  }

  for (int l = 0; l < numPos; ++l) {
    Vec3i &currPos = positions[l];

    // clamp lookup to grid
    const int i0 = clamp(currPos.x, 0, gridSize.x - 1);  // note! gridsize already has -1 from call
    const int j0 = clamp(currPos.y, 0, gridSize.y - 1);
    const int k0 = clamp(currPos.z, 0, (orig.is3D() ? (gridSize.z - 1) : 1));
    const int i1 = i0 + 1, j1 = j0 + 1, k1 = (orig.is3D() ? (k0 + 1) : k0);

    // find min/max around source pos
    if (checkFlag(i0, j0, k0)) {
      getMinMax(minv, maxv, orig(i0, j0, k0));
      haveFl = true;
    }
    if (checkFlag(i1, j0, k0)) {
      getMinMax(minv, maxv, orig(i1, j0, k0));
      haveFl = true;
    }
    if (checkFlag(i0, j1, k0)) {
      getMinMax(minv, maxv, orig(i0, j1, k0));
      haveFl = true;
    }
    if (checkFlag(i1, j1, k0)) {
      getMinMax(minv, maxv, orig(i1, j1, k0));
      haveFl = true;
    }

    if (orig.is3D()) {
      if (checkFlag(i0, j0, k1)) {
        getMinMax(minv, maxv, orig(i0, j0, k1));
        haveFl = true;
      }
      if (checkFlag(i1, j0, k1)) {
        getMinMax(minv, maxv, orig(i1, j0, k1));
        haveFl = true;
      }
      if (checkFlag(i0, j1, k1)) {
        getMinMax(minv, maxv, orig(i0, j1, k1));
        haveFl = true;
      }
      if (checkFlag(i1, j1, k1)) {
        getMinMax(minv, maxv, orig(i1, j1, k1));
        haveFl = true;
      }
    }
  }

  if (!haveFl)
    return fwd;
  if (clampMode == 1) {
    dst = clamp(dst, minv, maxv);  // hard clamp
  }
  else {
    if (cmpMinMax(minv, maxv, dst))
      dst = fwd;  // recommended in paper, "softer"
  }
  return dst;
}

//! Helper function for clamping MAC grids, slight differences in flag checks
//  similar to scalar version, just uses single component c of vec3 values
//  for symmetry, reverts to first order near boundaries for clampMode 2
template<int c>
inline Real doClampComponentMAC(const FlagGrid &flags,
                                const Vec3i &gridSize,
                                Real dst,
                                const MACGrid &orig,
                                Real fwd,
                                const Vec3 &pos,
                                const Vec3 &vel,
                                const int clampMode)
{
  Real minv = std::numeric_limits<Real>::max(), maxv = -std::numeric_limits<Real>::max();
  // bool haveFl = false;

  // forward (and optionally) backward
  Vec3i positions[2];
  int numPos = 1;
  positions[0] = toVec3i(pos - vel);
  if (clampMode == 1) {
    numPos = 2;
    positions[1] = toVec3i(pos + vel);
  }

  Vec3i oPos = toVec3i(pos);
  Vec3i nbPos = oPos;
  nbPos[c] -= 1;
  if (clampMode == 2 &&
      (!(checkFlag(oPos.x, oPos.y, oPos.z) && checkFlag(nbPos.x, nbPos.y, nbPos.z))))
    return fwd;  // replaces haveFl check

  for (int l = 0; l < numPos; ++l) {
    Vec3i &currPos = positions[l];

    const int i0 = clamp(currPos.x, 0, gridSize.x - 1);  // note! gridsize already has -1 from call
    const int j0 = clamp(
        currPos.y, 0, gridSize.y - 1);  // but we need a clamp to -2 for the +1 offset below
    const int k0 = clamp(currPos.z, 0, (orig.is3D() ? (gridSize.z - 1) : 0));
    const int i1 = i0 + 1, j1 = j0 + 1, k1 = (orig.is3D() ? (k0 + 1) : k0);

    // find min/max around source pos
    getMinMax(minv, maxv, orig(i0, j0, k0)[c]);
    getMinMax(minv, maxv, orig(i1, j0, k0)[c]);
    getMinMax(minv, maxv, orig(i0, j1, k0)[c]);
    getMinMax(minv, maxv, orig(i1, j1, k0)[c]);

    if (orig.is3D()) {
      getMinMax(minv, maxv, orig(i0, j0, k1)[c]);
      getMinMax(minv, maxv, orig(i1, j0, k1)[c]);
      getMinMax(minv, maxv, orig(i0, j1, k1)[c]);
      getMinMax(minv, maxv, orig(i1, j1, k1)[c]);
    }
  }

  if (clampMode == 1) {
    dst = clamp(dst, minv, maxv);  // hard clamp
  }
  else {
    if (cmpMinMax(minv, maxv, dst))
      dst = fwd;  // recommended in paper, "softer"
  }
  return dst;
}

#undef checkFlag

//! Kernel: Clamp obtained value to min/max in source area, and reset values that point out of grid
//! or into boundaries
//          (note - MAC grids are handled below)

template<class T> struct MacCormackClamp : public KernelBase {
  MacCormackClamp(const FlagGrid &flags,
                  const MACGrid &vel,
                  Grid<T> &dst,
                  const Grid<T> &orig,
                  const Grid<T> &fwd,
                  Real dt,
                  const int clampMode)
      : KernelBase(&flags, 1),
        flags(flags),
        vel(vel),
        dst(dst),
        orig(orig),
        fwd(fwd),
        dt(dt),
        clampMode(clampMode)
  {
    runMessage();
    run();
  }
  inline void op(int i,
                 int j,
                 int k,
                 const FlagGrid &flags,
                 const MACGrid &vel,
                 Grid<T> &dst,
                 const Grid<T> &orig,
                 const Grid<T> &fwd,
                 Real dt,
                 const int clampMode) const
  {
    T dval = dst(i, j, k);
    Vec3i gridUpper = flags.getSize() - 1;

    dval = doClampComponent<T>(gridUpper,
                               flags,
                               dval,
                               orig,
                               fwd(i, j, k),
                               Vec3(i, j, k),
                               vel.getCentered(i, j, k) * dt,
                               clampMode);

    if (1 && clampMode == 1) {
      // lookup forward/backward , round to closest NB
      Vec3i posFwd = toVec3i(Vec3(i, j, k) + Vec3(0.5, 0.5, 0.5) - vel.getCentered(i, j, k) * dt);
      Vec3i posBwd = toVec3i(Vec3(i, j, k) + Vec3(0.5, 0.5, 0.5) + vel.getCentered(i, j, k) * dt);

      // test if lookups point out of grid or into obstacle (note doClampComponent already checks
      // sides, below is needed for valid flags access)
      if (posFwd.x < 0 || posFwd.y < 0 || posFwd.z < 0 || posBwd.x < 0 || posBwd.y < 0 ||
          posBwd.z < 0 || posFwd.x > gridUpper.x || posFwd.y > gridUpper.y ||
          ((posFwd.z > gridUpper.z) && flags.is3D()) || posBwd.x > gridUpper.x ||
          posBwd.y > gridUpper.y || ((posBwd.z > gridUpper.z) && flags.is3D()) ||
          flags.isObstacle(posFwd) || flags.isObstacle(posBwd)) {
        dval = fwd(i, j, k);
      }
    }
    // clampMode 2 handles flags in doClampComponent call

    dst(i, j, k) = dval;
  }
  inline const FlagGrid &getArg0()
  {
    return flags;
  }
  typedef FlagGrid type0;
  inline const MACGrid &getArg1()
  {
    return vel;
  }
  typedef MACGrid type1;
  inline Grid<T> &getArg2()
  {
    return dst;
  }
  typedef Grid<T> type2;
  inline const Grid<T> &getArg3()
  {
    return orig;
  }
  typedef Grid<T> type3;
  inline const Grid<T> &getArg4()
  {
    return fwd;
  }
  typedef Grid<T> type4;
  inline Real &getArg5()
  {
    return dt;
  }
  typedef Real type5;
  inline const int &getArg6()
  {
    return clampMode;
  }
  typedef int type6;
  void runMessage()
  {
    debMsg("Executing kernel MacCormackClamp ", 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 = 1; j < _maxY; j++)
          for (int i = 1; i < _maxX; i++)
            op(i, j, k, flags, vel, dst, orig, fwd, dt, clampMode);
    }
    else {
      const int k = 0;
      for (int j = __r.begin(); j != (int)__r.end(); j++)
        for (int i = 1; i < _maxX; i++)
          op(i, j, k, flags, vel, dst, orig, fwd, dt, clampMode);
    }
  }
  void run()
  {
    if (maxZ > 1)
      tbb::parallel_for(tbb::blocked_range<IndexInt>(minZ, maxZ), *this);
    else
      tbb::parallel_for(tbb::blocked_range<IndexInt>(1, maxY), *this);
  }
  const FlagGrid &flags;
  const MACGrid &vel;
  Grid<T> &dst;
  const Grid<T> &orig;
  const Grid<T> &fwd;
  Real dt;
  const int clampMode;
};

//! Kernel: same as MacCormackClamp above, but specialized version for MAC grids

struct MacCormackClampMAC : public KernelBase {
  MacCormackClampMAC(const FlagGrid &flags,
                     const MACGrid &vel,
                     MACGrid &dst,
                     const MACGrid &orig,
                     const MACGrid &fwd,
                     Real dt,
                     const int clampMode)
      : KernelBase(&flags, 1),
        flags(flags),
        vel(vel),
        dst(dst),
        orig(orig),
        fwd(fwd),
        dt(dt),
        clampMode(clampMode)
  {
    runMessage();
    run();
  }
  inline void op(int i,
                 int j,
                 int k,
                 const FlagGrid &flags,
                 const MACGrid &vel,
                 MACGrid &dst,
                 const MACGrid &orig,
                 const MACGrid &fwd,
                 Real dt,
                 const int clampMode) const
  {
    Vec3 pos(i, j, k);
    Vec3 dval = dst(i, j, k);
    Vec3 dfwd = fwd(i, j, k);
    Vec3i gridUpper = flags.getSize() - 1;

    dval.x = doClampComponentMAC<0>(
        flags, gridUpper, dval.x, orig, dfwd.x, pos, vel.getAtMACX(i, j, k) * dt, clampMode);
    dval.y = doClampComponentMAC<1>(
        flags, gridUpper, dval.y, orig, dfwd.y, pos, vel.getAtMACY(i, j, k) * dt, clampMode);
    if (flags.is3D())
      dval.z = doClampComponentMAC<2>(
          flags, gridUpper, dval.z, orig, dfwd.z, pos, vel.getAtMACZ(i, j, k) * dt, clampMode);

    // note - the MAC version currently does not check whether source points were inside an
    // obstacle! (unlike centered version) this would need to be done for each face separately to
    // stay symmetric...

    dst(i, j, k) = dval;
  }
  inline const FlagGrid &getArg0()
  {
    return flags;
  }
  typedef FlagGrid type0;
  inline const MACGrid &getArg1()
  {
    return vel;
  }
  typedef MACGrid type1;
  inline MACGrid &getArg2()
  {
    return dst;
  }
  typedef MACGrid type2;
  inline const MACGrid &getArg3()
  {
    return orig;
  }
  typedef MACGrid type3;
  inline const MACGrid &getArg4()
  {
    return fwd;
  }
  typedef MACGrid type4;
  inline Real &getArg5()
  {
    return dt;
  }
  typedef Real type5;
  inline const int &getArg6()
  {
    return clampMode;
  }
  typedef int type6;
  void runMessage()
  {
    debMsg("Executing kernel MacCormackClampMAC ", 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 = 1; j < _maxY; j++)
          for (int i = 1; i < _maxX; i++)
            op(i, j, k, flags, vel, dst, orig, fwd, dt, clampMode);
    }
    else {
      const int k = 0;
      for (int j = __r.begin(); j != (int)__r.end(); j++)
        for (int i = 1; i < _maxX; i++)
          op(i, j, k, flags, vel, dst, orig, fwd, dt, clampMode);
    }
  }
  void run()
  {
    if (maxZ > 1)
      tbb::parallel_for(tbb::blocked_range<IndexInt>(minZ, maxZ), *this);
    else
      tbb::parallel_for(tbb::blocked_range<IndexInt>(1, maxY), *this);
  }
  const FlagGrid &flags;
  const MACGrid &vel;
  MACGrid &dst;
  const MACGrid &orig;
  const MACGrid &fwd;
  Real dt;
  const int clampMode;
};

//! template function for performing SL advection
//! (Note boundary width only needed for specialization for MAC grids below)
template<class GridType>
void fnAdvectSemiLagrange(FluidSolver *parent,
                          const FlagGrid &flags,
                          const MACGrid &vel,
                          GridType &orig,
                          int order,
                          Real strength,
                          int orderSpace,
                          int clampMode,
                          int orderTrace)
{
  typedef typename GridType::BASETYPE T;

  Real dt = parent->getDt();
  bool levelset = orig.getType() & GridBase::TypeLevelset;

  // forward step
  GridType fwd(parent);
  SemiLagrange<T>(flags, vel, fwd, orig, dt, levelset, orderSpace, orderTrace);

  if (order == 1) {
    orig.swap(fwd);
  }
  else if (order == 2) {  // MacCormack
    GridType bwd(parent);
    GridType newGrid(parent);

    // bwd <- backwards step
    SemiLagrange<T>(flags, vel, bwd, fwd, -dt, levelset, orderSpace, orderTrace);

    // newGrid <- compute correction
    MacCormackCorrect<T>(flags, newGrid, orig, fwd, bwd, strength, levelset);

    // clamp values
    MacCormackClamp<T>(flags, vel, newGrid, orig, fwd, dt, clampMode);

    orig.swap(newGrid);
  }
}

// outflow functions

//! calculate local propagation velocity for cell (i,j,k)
Vec3 getBulkVel(const FlagGrid &flags, const MACGrid &vel, int i, int j, int k)
{
  Vec3 avg = Vec3(0.);
  int count = 0;
  int size = 1;  // stencil size
  int nmax = (flags.is3D() ? size : 0);
  // average the neighboring fluid / outflow cell's velocity
  for (int n = -nmax; n <= nmax; n++) {
    for (int m = -size; m <= size; m++) {
      for (int l = -size; l <= size; l++) {
        if (flags.isInBounds(Vec3i(i + l, j + m, k + n)) &&
            (flags.isFluid(i + l, j + m, k + n) || flags.isOutflow(i + l, j + m, k + n))) {
          avg += vel(i + l, j + m, k + n);
          count++;
        }
      }
    }
  }
  return count > 0 ? avg / count : avg;
}

//! extrapolate normal velocity components into outflow cell
struct extrapolateVelConvectiveBC : public KernelBase {
  extrapolateVelConvectiveBC(const FlagGrid &flags,
                             const MACGrid &vel,
                             MACGrid &velDst,
                             const MACGrid &velPrev,
                             Real timeStep)
      : KernelBase(&flags, 0),
        flags(flags),
        vel(vel),
        velDst(velDst),
        velPrev(velPrev),
        timeStep(timeStep)
  {
    runMessage();
    run();
  }
  inline void op(int i,
                 int j,
                 int k,
                 const FlagGrid &flags,
                 const MACGrid &vel,
                 MACGrid &velDst,
                 const MACGrid &velPrev,
                 Real timeStep) const
  {
    if (flags.isOutflow(i, j, k)) {
      Vec3 bulkVel = getBulkVel(flags, vel, i, j, k);
      int dim = flags.is3D() ? 3 : 2;
      const Vec3i cur = Vec3i(i, j, k);
      Vec3i low, up, flLow, flUp;
      int cnt = 0;
      // iterate over each velocity component x, y, z
      for (int c = 0; c < dim; c++) {
        low = up = flLow = flUp = cur;
        Real factor = timeStep *
                      max((Real)1.0, bulkVel[c]);  // prevent the extrapolated velocity from
                                                   // exploding when bulk velocity below 1
        low[c] = flLow[c] = cur[c] - 1;
        up[c] = flUp[c] = cur[c] + 1;
        // iterate over bWidth to allow for extrapolation into more distant outflow cells;
        // hard-coded extrapolation distance of two cells
        for (int d = 0; d < 2; d++) {
          bool extrapolateFromLower = flags.isInBounds(flLow) && flags.isFluid(flLow);
          bool extrapolateFromUpper = flags.isInBounds(flUp) && flags.isFluid(flUp);
          if (extrapolateFromLower || extrapolateFromUpper) {
            if (extrapolateFromLower) {
              velDst(i, j, k) += ((vel(i, j, k) - velPrev(i, j, k)) / factor) + vel(low);
              cnt++;
            }
            if (extrapolateFromUpper) {
              // check for cells equally far away from two fluid cells -> average value between
              // both sides
              velDst(i, j, k) += ((vel(i, j, k) - velPrev(i, j, k)) / factor) + vel(up);
              cnt++;
            }
            break;
          }
          flLow[c]--;
          flUp[c]++;
        }
      }
      if (cnt > 0)
        velDst(i, j, k) /= cnt;
    }
  }
  inline const FlagGrid &getArg0()
  {
    return flags;
  }
  typedef FlagGrid type0;
  inline const MACGrid &getArg1()
  {
    return vel;
  }
  typedef MACGrid type1;
  inline MACGrid &getArg2()
  {
    return velDst;
  }
  typedef MACGrid type2;
  inline const MACGrid &getArg3()
  {
    return velPrev;
  }
  typedef MACGrid type3;
  inline Real &getArg4()
  {
    return timeStep;
  }
  typedef Real type4;
  void runMessage()
  {
    debMsg("Executing kernel extrapolateVelConvectiveBC ", 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, flags, vel, velDst, velPrev, timeStep);
    }
    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, flags, vel, velDst, velPrev, timeStep);
    }
  }
  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);
  }
  const FlagGrid &flags;
  const MACGrid &vel;
  MACGrid &velDst;
  const MACGrid &velPrev;
  Real timeStep;
};

//! copy extrapolated velocity components
struct copyChangedVels : public KernelBase {
  copyChangedVels(const FlagGrid &flags, const MACGrid &velDst, MACGrid &vel)
      : KernelBase(&flags, 0), flags(flags), velDst(velDst), vel(vel)
  {
    runMessage();
    run();
  }
  inline void op(
      int i, int j, int k, const FlagGrid &flags, const MACGrid &velDst, MACGrid &vel) const
  {
    if (flags.isOutflow(i, j, k))
      vel(i, j, k) = velDst(i, j, k);
  }
  inline const FlagGrid &getArg0()
  {
    return flags;
  }
  typedef FlagGrid type0;
  inline const MACGrid &getArg1()
  {
    return velDst;
  }
  typedef MACGrid type1;
  inline MACGrid &getArg2()
  {
    return vel;
  }
  typedef MACGrid type2;
  void runMessage()
  {
    debMsg("Executing kernel copyChangedVels ", 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, flags, velDst, vel);
    }
    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, flags, velDst, vel);
    }
  }
  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);
  }
  const FlagGrid &flags;
  const MACGrid &velDst;
  MACGrid &vel;
};

//! extrapolate normal velocity components into open boundary cells (marked as outflow cells)
void applyOutflowBC(const FlagGrid &flags, MACGrid &vel, const MACGrid &velPrev, double timeStep)
{
  MACGrid velDst(vel.getParent());  // do not overwrite vel while it is read
  extrapolateVelConvectiveBC(flags, vel, velDst, velPrev, max(1.0, timeStep * 4));
  copyChangedVels(flags, velDst, vel);
}

// advection helpers

//! prevent parts of the surface getting "stuck" in obstacle regions
struct knResetPhiInObs : public KernelBase {
  knResetPhiInObs(const FlagGrid &flags, Grid<Real> &sdf)
      : KernelBase(&flags, 0), flags(flags), sdf(sdf)
  {
    runMessage();
    run();
  }
  inline void op(int i, int j, int k, const FlagGrid &flags, Grid<Real> &sdf) const
  {
    if (flags.isObstacle(i, j, k) && (sdf(i, j, k) < 0.)) {
      sdf(i, j, k) = 0.1;
    }
  }
  inline const FlagGrid &getArg0()
  {
    return flags;
  }
  typedef FlagGrid type0;
  inline Grid<Real> &getArg1()
  {
    return sdf;
  }
  typedef Grid<Real> type1;
  void runMessage()
  {
    debMsg("Executing kernel knResetPhiInObs ", 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, flags, sdf);
    }
    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, flags, sdf);
    }
  }
  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);
  }
  const FlagGrid &flags;
  Grid<Real> &sdf;
};
void resetPhiInObs(const FlagGrid &flags, Grid<Real> &sdf)
{
  knResetPhiInObs(flags, sdf);
}
static PyObject *_W_0(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
{
  try {
    PbArgs _args(_linargs, _kwds);
    FluidSolver *parent = _args.obtainParent();
    bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
    pbPreparePlugin(parent, "resetPhiInObs", !noTiming);
    PyObject *_retval = 0;
    {
      ArgLocker _lock;
      const FlagGrid &flags = *_args.getPtr<FlagGrid>("flags", 0, &_lock);
      Grid<Real> &sdf = *_args.getPtr<Grid<Real>>("sdf", 1, &_lock);
      _retval = getPyNone();
      resetPhiInObs(flags, sdf);
      _args.check();
    }
    pbFinalizePlugin(parent, "resetPhiInObs", !noTiming);
    return _retval;
  }
  catch (std::exception &e) {
    pbSetError("resetPhiInObs", e.what());
    return 0;
  }
}
static const Pb::Register _RP_resetPhiInObs("", "resetPhiInObs", _W_0);
extern "C" {
void PbRegister_resetPhiInObs()
{
  KEEP_UNUSED(_RP_resetPhiInObs);
}
}

// advection main calls

//! template function for performing SL advection: specialized version for MAC grids
template<>
void fnAdvectSemiLagrange<MACGrid>(FluidSolver *parent,
                                   const FlagGrid &flags,
                                   const MACGrid &vel,
                                   MACGrid &orig,
                                   int order,
                                   Real strength,
                                   int orderSpace,
                                   int clampMode,
                                   int orderTrace)
{
  Real dt = parent->getDt();

  // forward step
  MACGrid fwd(parent);
  SemiLagrangeMAC(flags, vel, fwd, orig, dt, orderSpace, orderTrace);

  if (orderSpace != 1) {
    debMsg("Warning higher order for MAC grids not yet implemented...", 1);
  }

  if (order == 1) {
    applyOutflowBC(flags, fwd, orig, dt);
    orig.swap(fwd);
  }
  else if (order == 2) {  // MacCormack
    MACGrid bwd(parent);
    MACGrid newGrid(parent);

    // bwd <- backwards step
    SemiLagrangeMAC(flags, vel, bwd, fwd, -dt, orderSpace, orderTrace);

    // newGrid <- compute correction
    MacCormackCorrectMAC<Vec3>(flags, newGrid, orig, fwd, bwd, strength, false, true);

    // clamp values
    MacCormackClampMAC(flags, vel, newGrid, orig, fwd, dt, clampMode);

    applyOutflowBC(flags, newGrid, orig, dt);
    orig.swap(newGrid);
  }
}

//! Perform semi-lagrangian advection of target Real- or Vec3 grid
//! Open boundary handling needs information about width of border
//! Clamping modes: 1 regular clamp leading to more overshoot and sharper results, 2 revert to 1st
//! order slightly smoother less overshoot (enable when 1 gives artifacts)

void advectSemiLagrange(const FlagGrid *flags,
                        const MACGrid *vel,
                        GridBase *grid,
                        int order = 1,
                        Real strength = 1.0,
                        int orderSpace = 1,
                        bool openBounds = false,
                        int boundaryWidth = -1,
                        int clampMode = 2,
                        int orderTrace = 1)
{
  assertMsg(order == 1 || order == 2,
            "AdvectSemiLagrange: Only order 1 (regular SL) and 2 (MacCormack) supported");
  if ((boundaryWidth != -1) || (openBounds)) {
    debMsg(
        "Warning: boundaryWidth and openBounds parameters in AdvectSemiLagrange plugin are "
        "deprecated (and have no more effect), please remove.",
        0);
  }

  // determine type of grid
  if (grid->getType() & GridBase::TypeReal) {
    fnAdvectSemiLagrange<Grid<Real>>(flags->getParent(),
                                     *flags,
                                     *vel,
                                     *((Grid<Real> *)grid),
                                     order,
                                     strength,
                                     orderSpace,
                                     clampMode,
                                     orderTrace);
  }
  else if (grid->getType() & GridBase::TypeMAC) {
    fnAdvectSemiLagrange<MACGrid>(flags->getParent(),
                                  *flags,
                                  *vel,
                                  *((MACGrid *)grid),
                                  order,
                                  strength,
                                  orderSpace,
                                  clampMode,
                                  orderTrace);
  }
  else if (grid->getType() & GridBase::TypeVec3) {
    fnAdvectSemiLagrange<Grid<Vec3>>(flags->getParent(),
                                     *flags,
                                     *vel,
                                     *((Grid<Vec3> *)grid),
                                     order,
                                     strength,
                                     orderSpace,
                                     clampMode,
                                     orderTrace);
  }
  else
    errMsg("AdvectSemiLagrange: Grid Type is not supported (only Real, Vec3, MAC, Levelset)");
}
static PyObject *_W_1(PyObject *_self, PyObject *_linargs, PyObject *_kwds)
{
  try {
    PbArgs _args(_linargs, _kwds);
    FluidSolver *parent = _args.obtainParent();
    bool noTiming = _args.getOpt<bool>("notiming", -1, 0);
    pbPreparePlugin(parent, "advectSemiLagrange", !noTiming);
    PyObject *_retval = 0;
    {
      ArgLocker _lock;
      const FlagGrid *flags = _args.getPtr<FlagGrid>("flags", 0, &_lock);
      const MACGrid *vel = _args.getPtr<MACGrid>("vel", 1, &_lock);
      GridBase *grid = _args.getPtr<GridBase>("grid", 2, &_lock);
      int order = _args.getOpt<int>("order", 3, 1, &_lock);
      Real strength = _args.getOpt<Real>("strength", 4, 1.0, &_lock);
      int orderSpace = _args.getOpt<int>("orderSpace", 5, 1, &_lock);
      bool openBounds = _args.getOpt<bool>("openBounds", 6, false, &_lock);
      int boundaryWidth = _args.getOpt<int>("boundaryWidth", 7, -1, &_lock);
      int clampMode = _args.getOpt<int>("clampMode", 8, 2, &_lock);
      int orderTrace = _args.getOpt<int>("orderTrace", 9, 1, &_lock);
      _retval = getPyNone();
      advectSemiLagrange(flags,
                         vel,
                         grid,
                         order,
                         strength,
                         orderSpace,
                         openBounds,
                         boundaryWidth,
                         clampMode,
                         orderTrace);
      _args.check();
    }
    pbFinalizePlugin(parent, "advectSemiLagrange", !noTiming);
    return _retval;
  }
  catch (std::exception &e) {
    pbSetError("advectSemiLagrange", e.what());
    return 0;
  }
}
static const Pb::Register _RP_advectSemiLagrange("", "advectSemiLagrange", _W_1);
extern "C" {
void PbRegister_advectSemiLagrange()
{
  KEEP_UNUSED(_RP_advectSemiLagrange);
}
}

}  // namespace Manta
