void IterativeLevelSetSolver2::extrapolate(
    const FaceCenteredGrid2& input,
    const ScalarField2& sdf,
    double maxDistance,
    FaceCenteredGrid2* output) {
    JET_THROW_INVALID_ARG_IF(!input.hasSameShape(*output));

    const Vector2D gridSpacing = input.gridSpacing();

    auto u = input.uConstAccessor();
    auto uPos = input.uPosition();
    Array2<double> sdfAtU(u.size());
    input.parallelForEachUIndex([&](size_t i, size_t j) {
        sdfAtU(i, j) = sdf.sample(uPos(i, j));
    });

    extrapolate(
        u,
        sdfAtU,
        gridSpacing,
        maxDistance,
        output->uAccessor());

    auto v = input.vConstAccessor();
    auto vPos = input.vPosition();
    Array2<double> sdfAtV(v.size());
    input.parallelForEachVIndex([&](size_t i, size_t j) {
        sdfAtV(i, j) = sdf.sample(vPos(i, j));
    });

    extrapolate(
        v,
        sdfAtV,
        gridSpacing,
        maxDistance,
        output->vAccessor());
}
void IterativeLevelSetSolver2::extrapolate(
    const CollocatedVectorGrid2& input,
    const ScalarField2& sdf,
    double maxDistance,
    CollocatedVectorGrid2* output) {
    JET_THROW_INVALID_ARG_IF(!input.hasSameShape(*output));

    Array2<double> sdfGrid(input.dataSize());
    auto pos = input.dataPosition();
    sdfGrid.parallelForEachIndex([&](size_t i, size_t j) {
        sdfGrid(i, j) = sdf.sample(pos(i, j));
    });

    const Vector2D gridSpacing = input.gridSpacing();

    Array2<double> u(input.dataSize());
    Array2<double> u0(input.dataSize());
    Array2<double> v(input.dataSize());
    Array2<double> v0(input.dataSize());

    input.parallelForEachDataPointIndex([&](size_t i, size_t j) {
        u(i, j) = input(i, j).x;
        v(i, j) = input(i, j).y;
    });

    extrapolate(
        u,
        sdfGrid.constAccessor(),
        gridSpacing,
        maxDistance,
        u0);

    extrapolate(
        v,
        sdfGrid.constAccessor(),
        gridSpacing,
        maxDistance,
        v0);

    output->parallelForEachDataPointIndex([&](size_t i, size_t j) {
        (*output)(i, j).x = u(i, j);
        (*output)(i, j).y = v(i, j);
    });
}
void IterativeLevelSetSolver2::extrapolate(
    const ScalarGrid2& input,
    const ScalarField2& sdf,
    double maxDistance,
    ScalarGrid2* output) {
    JET_THROW_INVALID_ARG_IF(!input.hasSameShape(*output));

    Array2<double> sdfGrid(input.dataSize());
    auto pos = input.dataPosition();
    sdfGrid.parallelForEachIndex([&](size_t i, size_t j) {
        sdfGrid(i, j) = sdf.sample(pos(i, j));
    });

    extrapolate(
        input.constDataAccessor(),
        sdfGrid.constAccessor(),
        input.gridSpacing(),
        maxDistance,
        output->dataAccessor());
}
void GridFractionalSinglePhasePressureSolver2::buildWeights(
    const FaceCenteredGrid2& input, const ScalarField2& boundarySdf,
    const VectorField2& boundaryVelocity, const ScalarField2& fluidSdf) {
    auto size = input.resolution();

    // Build levels
    size_t maxLevels = 1;
    if (_mgSystemSolver != nullptr) {
        maxLevels = _mgSystemSolver->params().maxNumberOfLevels;
    }
    FdmMgUtils2::resizeArrayWithFinest(size, maxLevels, &_fluidSdf);
    _uWeights.resize(_fluidSdf.size());
    _vWeights.resize(_fluidSdf.size());
    for (size_t l = 0; l < _fluidSdf.size(); ++l) {
        _uWeights[l].resize(_fluidSdf[l].size() + Size2(1, 0));
        _vWeights[l].resize(_fluidSdf[l].size() + Size2(0, 1));
    }

    // Build top-level grids
    auto cellPos = input.cellCenterPosition();
    auto uPos = input.uPosition();
    auto vPos = input.vPosition();
    _boundaryVel = boundaryVelocity.sampler();
    Vector2D h = input.gridSpacing();

    _fluidSdf[0].parallelForEachIndex([&](size_t i, size_t j) {
        _fluidSdf[0](i, j) = static_cast<float>(fluidSdf.sample(cellPos(i, j)));
    });

    _uWeights[0].parallelForEachIndex([&](size_t i, size_t j) {
        Vector2D pt = uPos(i, j);
        double phi0 = boundarySdf.sample(pt - Vector2D(0.5 * h.x, 0.0));
        double phi1 = boundarySdf.sample(pt + Vector2D(0.5 * h.x, 0.0));
        double frac = fractionInsideSdf(phi0, phi1);
        double weight = clamp(1.0 - frac, 0.0, 1.0);

        // Clamp non-zero weight to kMinWeight. Having nearly-zero element
        // in the matrix can be an issue.
        if (weight < kMinWeight && weight > 0.0) {
            weight = kMinWeight;
        }

        _uWeights[0](i, j) = static_cast<float>(weight);
    });

    _vWeights[0].parallelForEachIndex([&](size_t i, size_t j) {
        Vector2D pt = vPos(i, j);
        double phi0 = boundarySdf.sample(pt - Vector2D(0.0, 0.5 * h.y));
        double phi1 = boundarySdf.sample(pt + Vector2D(0.0, 0.5 * h.y));
        double frac = fractionInsideSdf(phi0, phi1);
        double weight = clamp(1.0 - frac, 0.0, 1.0);

        // Clamp non-zero weight to kMinWeight. Having nearly-zero element
        // in the matrix can be an issue.
        if (weight < kMinWeight && weight > 0.0) {
            weight = kMinWeight;
        }

        _vWeights[0](i, j) = static_cast<float>(weight);
    });

    // Build sub-levels
    for (size_t l = 1; l < _fluidSdf.size(); ++l) {
        const auto& finerFluidSdf = _fluidSdf[l - 1];
        auto& coarserFluidSdf = _fluidSdf[l];
        const auto& finerUWeight = _uWeights[l - 1];
        auto& coarserUWeight = _uWeights[l];
        const auto& finerVWeight = _vWeights[l - 1];
        auto& coarserVWeight = _vWeights[l];

        // Fluid SDF
        restrict(finerFluidSdf, &coarserFluidSdf);
        restrict(finerUWeight, &coarserUWeight);
        restrict(finerVWeight, &coarserVWeight);
    }
}