// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
//
/// @author Nick Avramoussis
///
/// @file PointTransfer.h
///
/// @brief  Framework methods for rasterizing PointDataGrid data to Trees.
///
/// @details  Provides a generic inherited interface for deriving transfer
///   schemes that represent how point data should be rasterized. The provided
///   components together support the transfer of multiple attributes to
///   arbitrary and multiple grid types. Target grids must have the same
///   transform, but this transform can differ from the source PointDataGrid
///   (multiple instantiations of rasterize() should instead be invoked to
///   transfer to grids of different transforms). Arbitrary attributes can be
///   accessed and transfered to arbitrary trees.
///

#ifndef OPENVEB_POINTS_TRANSFER_HAS_BEEN_INCLUDED
#define OPENVEB_POINTS_TRANSFER_HAS_BEEN_INCLUDED

#include <openvdb/openvdb.h>
#include <openvdb/Types.h>
#include <openvdb/Grid.h>
#include <openvdb/math/Transform.h>
#include <openvdb/util/NullInterrupter.h>
#include <openvdb/util/Assert.h>
#include <openvdb/thread/Threading.h>

#include <type_traits>
#include <tuple>

namespace openvdb {
OPENVDB_USE_VERSION_NAMESPACE
namespace OPENVDB_VERSION_NAME {
namespace points {

/// @brief Perform potentially complex rasterization from a user defined
///  transfer scheme. See below comments for the transfer scheme API.
/// @details The method works by looping over a single Tree topology, looking
///   up point data at a position relative to that topology and passing that
///   data to a transfer scheme TransferT.
/// @note  Each thread receives a copy of the transfer scheme object.
/// @param points       the point data grid or tree to rasterize
/// @param transfer     the transfer scheme
template <typename PointDataTreeOrGridT, typename TransferT>
inline void
rasterize(const PointDataTreeOrGridT& points, TransferT& transfer);

/// @par A transfer scheme must be configured to call the provided rasterize
///   methods. See below for an example, or the various native VDB files which
///   implement schemes e.g.:
///     - PointRasterizeSDF.h
///     - PointRasterizeTrilinear.h
///     - PrincipalComponentAnalysisImpl.h
/// @code
/// struct Transfer
/// {
///     /// @return Returns the tree topology to loop over. This can be different
///     ///   from the destination tree i.e. This can act as a mask.
///     inline auto& topology();
///
///     /// @brief  The maximum lookup range of this transfer scheme in index
///     ///   space of the source points.
///     /// @details The return value represent how far away from the destination
///     ///   leaf node points should be accessed.
///     /// @param origin  The leaf origin of the topology being accessed
///     /// @param idx     The leaf index of the topology being accessed
///     inline Int32 range(const Coord& origin, size_t idx) const;
///
///     /// @brief  The initialize function, called on each leaf which has valid
///     ///   topology to write to.
///     /// @param origin  The leaf origin of the topology being accessed
///     /// @param idx     The leaf index of the topology being accessed
///     /// @param bounds  The active voxel bounds of the leaf
///     inline void initialize(const Coord& origin, size_t idx, const CoordBBox& bounds);
///
///     /// @brief  Run each time a point leaf is accessed. Typically this is
///     ///  where attribute handles can be constructed
///     /// @param leaf  The PointDataLeafNode which is being accessed.
///     /// @return  Return true to continue rasterization, false to early exit
///     ///   and skip the current leaf's contribution to the destination volume.
///     inline bool startPointLeaf(const PointDataTree::LeafNodeType& leaf);
///
///     ///////////////////////////////////////////////////////////////////////
///
///     // Transfer scheme must implement either:
///     //   - rasterizePoint(Coord, Index, CoordBBox) OR
///     //   - rasterizePoints(Coord, Index, Index, CoordBBox)
///
///     /// @brief  The point stamp function. Each point which contributes to
///     ///  the current leaf will call this function exactly once.
///     /// @param ijk  The current voxel containing the point being rasterized.
///     ///   May be outside the destination leaf node depending on the range()
///     /// @param id   The point index being rasterized
///     /// @param bounds  The active bounds of the leaf node.
///     void rasterizePoint(const Coord& ijk,
///         const Index id,
///         const CoordBBox& bounds);
///
///     /// @brief  Same as above, except this is passed a range of points
///     ///   that all belong to the same voxel
///     /// @param ijk  The current voxel containing the point being rasterized.
///     ///   May be outside the destination leaf node depending on the range()
///     /// @param start   The start point index being rasterized
///     /// @param end     The end point index being rasterized
///     /// @param bounds  The active bounds of the leaf node.
///     void rasterizePoints(const Coord& ijk,
///          const Index start,
///          const Index end,
///          const CoordBBox& bounds);
///
///     ///////////////////////////////////////////////////////////////////////
///
///     /// @brief  Run each time a point leaf is finished with.
///     /// @param leaf  The PointDataLeafNode which was being accessed.
///     /// @return  Return true to continue rasterization, false to early exit
///     ///   and stop rasterization to the destination leaf node.
///     inline bool endPointLeaf(const PointDataTree::LeafNodeType& leaf);
///
///     /// @brief  The finalization function for the given destination tree(s).
///     /// @param origin  The leaf origin of the topology being accessed
///     /// @param idx     The leaf index of the topology being accessed
///     /// @return  Return true to stop, false to recursively rasterize
///     inline bool finalize(const Coord& origin, size_t idx);
/// };
/// @endcode
///
///
/// Below is a full example using the native components.
///
/// @code
/// /// @brief Sum point distances into a target float tree
/// ///   Note: Using TransformTransfer to handle different index spaces, and
/// ///   VolumeTransfer for automatic buffer setup
/// struct MyTransfer :
///     public TransformTransfer,
///     public VolumeTransfer<FloatTree>
/// {
///     MyTransfer(FloatGrid& dest, const PointDataGrid& source)
///         : TransformTransfer(source.transform(), dest.transform())
///         , VolumeTransfer(dest.tree()) {}
///
///     MyTransfer(const MyTransfer& other)
///         : TransformTransfer(other)
///         , VolumeTransfer(other) {}
///
///     /// @brief Range in index space of the source points
///     Vec3i range(const Coord&, size_t) const { return Vec3i(1); }
///
///     /// @brief Every time we start a new point leaf, init the position array.
///     ///   Always return true as we don't skip any leaf nodes.
///     bool startPointLeaf(const PointDataTree::LeafNodeType& leaf)
///     {
///         // @note consider caching the indices to "P" and "mygroup" for faster lookups
///         mHandle = std::make_unique<AttributeHandle<Vec3f>>(leaf.constAttributeArray("P"));
///         mFilter = std::make_unique<GroupFilter>("mygroup", leaf.attributeSet());
///         return true;
///     }
///
///     /// @brief  For each point, compute its relative index space position in
///     ///   the destination tree and sum the length of its distance
///     void rasterizePoint(const Coord& ijk, const Index id, const CoordBBox& bounds)
///     {
///         // skip points not in "mygroup"
///         if (!mFilter->valid(&id)) return;
///         Vec3d P = ijk.asVec3d() + Vec3d(this->mHandle->get(id));
///         P = this->transformSourceToTarget(P); // TransformTransfer::transformSourceToTarget
///         // for each active voxel, accumulate distance
///         const auto* mask = this->mask(); // VolumeTransfer::mask
///         for (auto& coord : bounds) {
///             const Index voxel = FloatTree::LeafNodeType::coordToOffset(coord);
///             if (!mask->isOn(voxel)) continue;
///             Vec3d dist = coord.asVec3d() - P;
///             this->buffer()[voxel] += dist.length(); // VolumeTransfer::buffer
///         }
///     }
///
///     /// @brief Return true for endPointLeaf() to continue, false for finalize() so
///     ///   we don't recurse.
///     bool endPointLeaf(const PointDataTree::LeafNodeType&) { return true; }
///     bool finalize(const Coord&, size_t) { return false; }
///
/// private:
///     std::unique_ptr<AttributeHandle<Vec3f>> mHandle {nullptr};
///     std::unique_ptr<GroupFilter> mFilter {nullptr};
/// };
/// @endcode


///////////////////////////////////////////////////
///////////////////////////////////////////////////

/// @brief  The TransformTransfer module should be used if the source transform
///   of the input points and the target transforms of the destination volumes
///   differ. The default rasterizer will skip index to world (and vice versa)
///   transformations unless a transfer scheme derives from a TransformTransfer.
struct TransformTransfer
{
    TransformTransfer(const math::Transform& st,
        const math::Transform& tt)
        : mSourceTransform(st)
        , mTargetTransform(tt) {}

    template <typename T>
    inline auto transformSourceToTarget(const T& value) const
    {
        const auto result = mSourceTransform.indexToWorld(value);
        return mTargetTransform.worldToIndex(result);
    }

    template <typename T>
    inline auto transformTargetToSource(const T& value) const
    {
        const auto result = mTargetTransform.indexToWorld(value);
        return mSourceTransform.worldToIndex(result);
    }

    const math::Transform& sourceTransform() const { return mSourceTransform; }
    const math::Transform& targetTransform() const { return mTargetTransform; }

private:
    const math::Transform& mSourceTransform;
    const math::Transform& mTargetTransform;
};

/// @brief  InterruptableTransfer module, when derived from allows for schemes
///    to callback into a interrupter, derived from util::NullInterrupter.
struct InterruptableTransfer
{
    InterruptableTransfer(util::NullInterrupter* const interrupt)
        : mInterrupt(interrupt) {}
    inline bool interrupted() const
    {
        if (!util::wasInterrupted(mInterrupt)) return false;
        thread::cancelGroupExecution();
        return true;
    }
private:
    util::NullInterrupter* const mInterrupt;
};

/// @brief  FilteredTransfer module, when derived from allows for schemes
///    to apply point filtering. Note that this module handles the thread safe
///    intialization and storage of the filter, but derived schemes must call
///    FilteredTransfer::filter() per point id and handle the result.
template <typename FilterT>
struct FilteredTransfer
{
    FilteredTransfer(const FilterT& filter)
        : mFilter(filter)
        , mLocalFilter(nullptr) {}
    FilteredTransfer(const FilteredTransfer& other)
        : mFilter(other.mFilter)
        , mLocalFilter(nullptr) {}

    inline void initialize(const Coord&, const size_t, const CoordBBox&)
    {
        mLocalFilter = std::make_unique<FilterT>(mFilter);
    }

    inline bool startPointLeaf(const PointDataTree::LeafNodeType& leaf)
    {
        mLocalFilter->reset(leaf);
        return true;
    }

    inline bool filter(const Index id) const
    {
        return mLocalFilter->valid(&id);
    }

private:
    const FilterT& mFilter;
    /// @note  Not all of the filters are assignable (e.g. GroupFilter).
    ///   should really fix this and make this stack allocated.
    std::unique_ptr<FilterT> mLocalFilter;
};

/// @brief  Specialization of FilteredTransfer for NullFilters which do nothing
template <>
struct FilteredTransfer<NullFilter>
{
    FilteredTransfer(const NullFilter&) {}
    inline void initialize(const Coord&, const size_t, const CoordBBox&) {}
    inline bool startPointLeaf(const PointDataTree::LeafNodeType&) { return true; }
    inline bool filter(const Index) const { return true; }
};

/// @brief  The VolumeTransfer module provides methods to automatically setup
///   and access destination buffers for multiple target volumes of arbitrary
///   types. Deriving from a VolumeTransfer ensures that the available
///   buffers correlate to the order of the provided tree arguments.
template <typename ...TreeTypes>
struct VolumeTransfer
{
    static const size_t Size = sizeof...(TreeTypes);
    using TreeTupleT = std::tuple<TreeTypes*...>;

    template <size_t Idx> using TreeType = typename std::tuple_element<Idx, std::tuple<TreeTypes...>>::type;
    template <size_t Idx> using ValueType = typename TreeType<Idx>::ValueType;
    template <typename T> struct TypeResolver { using Type = typename T::ValueType; };
    using NodeMaskT = typename TreeType<0>::LeafNodeType::NodeMaskType;

    VolumeTransfer(TreeTypes*... trees);

    VolumeTransfer(TreeTypes&... trees)
        : VolumeTransfer(&trees...) {}

    VolumeTransfer(const VolumeTransfer& other)
        : mTreeArray(other.mTreeArray)
        , mBuffers()
        , mMasks()
    {
        mBuffers.fill(nullptr);
        mMasks.fill(nullptr);
    }

    inline TreeType<0>& topology() { return *(std::get<0>(mTreeArray)); }

    inline void initialize(const Coord& origin, const size_t, const CoordBBox&);

    template <size_t Idx>
    inline ValueType<Idx>* buffer()
    {
        return static_cast<ValueType<Idx>*>(mBuffers[Idx]);
    }

    template <size_t Idx>
    inline const ValueType<Idx>* buffer() const
    {
        return static_cast<ValueType<Idx>*>(mBuffers[Idx]);
    }

    template <size_t Idx>
    inline NodeMaskT* mask() { return mMasks[Idx]; }
    inline NodeMaskT* mask(const size_t idx) { return mMasks[idx]; }

    template <size_t Idx>
    inline const NodeMaskT* mask() const { return mMasks[Idx]; }
    inline const NodeMaskT* mask(const size_t idx) const { return mMasks[idx]; }

    template <typename FunctorT>
    inline void foreach(const FunctorT& functor);

private:
    const TreeTupleT mTreeArray;
    std::array<void*, Size> mBuffers;
    std::array<NodeMaskT*, Size> mMasks;
};

/// @brief  VolumeTransfer specialization for a single target volume
/// @todo this specialization should avoid the probe
template <typename TreeT>
struct VolumeTransfer<TreeT>
{
    using TreeType = TreeT;
    using ValueType = typename TreeType::ValueType;
    using NodeMaskT = typename TreeType::LeafNodeType::NodeMaskType;

    static_assert(std::is_base_of<TreeBase, TreeType>::value,
        "One or more template arguments to VolumeTransfer "
        "are not a valid openvdb::Tree type.");

    VolumeTransfer(TreeType* tree)
        : mTree(tree)
        , mBuffer(nullptr)
        , mMask(nullptr) {
        OPENVDB_ASSERT(tree);
    }

    VolumeTransfer(TreeType& tree)
        : VolumeTransfer(&tree) {}

    VolumeTransfer(const VolumeTransfer& other)
        : mTree(other.mTree)
        , mBuffer(nullptr)
        , mMask(nullptr) {}

    inline TreeType& topology() { return *mTree; }

    inline void initialize(const Coord& origin, const size_t, const CoordBBox&)
    {
        OPENVDB_ASSERT(mTree);
        if (auto leaf = mTree->probeLeaf(origin)) {
            mBuffer = leaf->buffer().data();
            mMask = &(leaf->getValueMask());
        }
        else {
            mBuffer = nullptr;
            mMask = nullptr;
        }
    }

    inline ValueType* buffer() { return mBuffer; }
    inline const ValueType* buffer() const { return mBuffer; }
    inline NodeMaskT* mask() { return mMask; }
    inline const NodeMaskT* mask() const { return mMask; }

    // compatibility with multi tree containers
    template <size_t> inline ValueType* buffer() { return this->buffer(); }
    template <size_t> inline const ValueType* buffer() const { return this->buffer(); }
    template <size_t> inline NodeMaskT* mask() { return this->mask(); }
    template <size_t> inline const NodeMaskT* mask() const { return this->mask(); }

private:
    TreeType* const mTree;
    ValueType* mBuffer;
    NodeMaskT* mMask;
};

namespace transfer_internal
{
template<typename T, typename F, size_t... Is>
void foreach(T&& t, const F& func, std::integer_sequence<size_t, Is...>)
{
    auto init = { (func(std::get<Is>(t), Is), 0)... };
    (void)init;
}

template<typename T, typename F, size_t... Is>
void foreach(void** buffers, const F& func, std::integer_sequence<size_t, Is...>)
{
    int init[sizeof...(Is)] = {
        (func(static_cast<typename std::tuple_element<Is, T>::type*>
            (*(buffers + Is)), Is), 0)...
    };
}

template<typename T, template <typename> class R, typename F, size_t... Is>
void foreach(void** buffers, const F& func, std::integer_sequence<size_t, Is...>)
{
    int init[sizeof...(Is)] = {
        (func(static_cast<typename R<typename std::tuple_element<Is, T>::type>::Type*>
           (*(buffers + Is)), Is), 0)...
    };
}
}

template <typename ...TreeTypes>
VolumeTransfer<TreeTypes...>::VolumeTransfer(TreeTypes*... trees)
    : mTreeArray({ trees... })
    , mBuffers()
    , mMasks()
{
    transfer_internal::foreach(mTreeArray, [](auto&& tree, const size_t) {
        using TreeT = typename std::remove_pointer<typename std::decay<decltype(tree)>::type>::type;
        static_assert(std::is_base_of<TreeBase, TreeT>::value,
            "One or more template arguments to VolumeTransfer "
            "are not a valid openvdb::Tree type.");
        OPENVDB_ASSERT(tree);
    }, std::make_integer_sequence<size_t, Size>());

    mBuffers.fill(nullptr);
    mMasks.fill(nullptr);
}

template <typename ...TreeTypes>
inline void VolumeTransfer<TreeTypes...>::initialize(const Coord& origin, const size_t, const CoordBBox&)
{
    transfer_internal::foreach(mTreeArray,
        [&](auto&& tree, const size_t i) {
            OPENVDB_ASSERT(tree);
            if (auto leaf = tree->probeLeaf(origin)) {
                mBuffers[i] = static_cast<void*>(leaf->buffer().data());
                mMasks[i] = &(leaf->getValueMask());
            }
            else {
                mBuffers[i] = nullptr;
                mMasks[i] = nullptr;
            }
        }, std::make_integer_sequence<size_t, Size>());
}

template <typename ...TreeTypes>
template <typename FunctorT>
inline void VolumeTransfer<TreeTypes...>::foreach(const FunctorT& functor)
{
    transfer_internal::foreach<TreeTupleT, TypeResolver>(mBuffers.data(), functor,
        std::make_integer_sequence<size_t, Size>());
}

namespace transfer_internal
{

OPENVDB_INIT_INVOKABLE_MEMBER_FUNCTION(rasterizePoint)
OPENVDB_INIT_INVOKABLE_MEMBER_FUNCTION(rasterizePoints)

template <typename TransferT,
          typename TopologyT,
          typename PointFilterT = points::NullFilter,
          typename InterrupterT = util::NullInterrupter>
struct RasterizePoints
{
    using LeafManagerT = tree::LeafManager<TopologyT>;
    using LeafNodeT = typename LeafManagerT::LeafNodeType;

    using RasterizePointSignature  = void(const Coord&, const Index, const CoordBBox&);
    using RasterizePointsSignature = void(const Coord&, const Index, const Index, const CoordBBox&);

    static const Index DIM = TopologyT::LeafNodeType::DIM;
    static const Int32 DIM32 = static_cast<Int32>(DIM);
    static const Index LOG2DIM = TopologyT::LeafNodeType::LOG2DIM;
    static constexpr bool UseRasterizePoints =
        OPENVDB_HAS_INVOKABLE_MEMBER_FUNCTION(TransferT, rasterizePoints, Coord, Index, Index, CoordBBox);
    static constexpr bool UseRasterizePoint =
        OPENVDB_HAS_INVOKABLE_MEMBER_FUNCTION(TransferT, rasterizePoint, Coord, Index, CoordBBox);

    RasterizePoints(const points::PointDataTree& tree,
                    const TransferT& transfer,
                    const CoordBBox& pointBounds,
                    const PointFilterT& filter = PointFilterT(),
                    InterrupterT* interrupter = nullptr)
        : mPointAccessor(tree)
        , mTransfer(transfer)
        , mPointBounds(pointBounds)
        , mFilter(filter)
        , mInterrupter(interrupter) {}

    void operator()(LeafNodeT& leaf, const size_t idx) const
    {
        if (this->interrupted()) return;

        const Coord& origin = leaf.origin();
        auto& mask = leaf.getValueMask();

        CoordBBox bounds;

        bool state;
        if (mask.isConstant(state)) {
            if (!state) return; // all inactive
            else bounds = leaf.getNodeBoundingBox();
        }
        else {
            // Use evalActiveBoundingBox over getNodeBoundingBox()
            // to get a better approximation
            leaf.evalActiveBoundingBox(bounds);
            OPENVDB_ASSERT(!bounds.empty());
        }

        mTransfer.initialize(origin, idx, bounds);

        CoordBBox search = bounds;
        const Vec3i range(mTransfer.range(origin, idx));
        search.min() -= Coord(range);
        search.max() += Coord(range);
        this->transform<>(search);
        search.intersect(mPointBounds);

        // start the iteration from a leaf origin
        const Coord min = (search.min() & ~(DIM-1));
        const Coord& max = search.max();

        /// @todo  remove this - with the introduction of rasterizePoints we no
        ///   longer accept a filter at this level (it's expected to be handled)
        ///   in the transfer scheme
        PointFilterT localFilter(mFilter);

        // loop over overlapping leaf nodes
        Coord leafOrigin;
        for (leafOrigin[0] = min[0]; leafOrigin[0] <= max[0]; leafOrigin[0]+=DIM32) {
            for (leafOrigin[1] = min[1]; leafOrigin[1] <= max[1]; leafOrigin[1]+=DIM32) {
                for (leafOrigin[2] = min[2]; leafOrigin[2] <= max[2]; leafOrigin[2]+=DIM32) {

                    // if no overlap, continue
                    CoordBBox pbox = CoordBBox::createCube(leafOrigin, DIM32);
                    pbox.intersect(search);
                    if (pbox.empty()) continue;

                    // if no points, continue
                    const auto* pointLeaf = mPointAccessor.probeConstLeaf(leafOrigin);
                    if (!pointLeaf) continue;
                    if (!mTransfer.startPointLeaf(*pointLeaf)) continue;
                    localFilter.reset(*pointLeaf);

                    if (this->interrupted()) return;

                    // It's actually faster to go through the ValueIter API than
                    // the leaf API as the value iterators cache the value buffer
                    // ptrs (the leaf buffer API has to check the ptr on every
                    // access).
                    // @todo  Once we've improved the leaf buffer impl this
                    //   should be removed
                    const auto valiter = pointLeaf->cbeginValueAll();

                    // loop over point voxels which contribute to this leaf
                    const Coord& pmin(pbox.min());
                    const Coord& pmax(pbox.max());
                    for (Coord ijk = pmin; ijk.x() <= pmax.x(); ++ijk.x()) {
                        const Index i = ((ijk.x() & (DIM-1u)) << 2*LOG2DIM); // unsigned bit shift mult
                        for (ijk.y() = pmin.y(); ijk.y() <= pmax.y(); ++ijk.y()) {
                            const Index ij = i + ((ijk.y() & (DIM-1u)) << LOG2DIM);
                            for (ijk.z() = pmin.z(); ijk.z() <= pmax.z(); ++ijk.z()) {
                                // voxel should be in this points leaf
                                OPENVDB_ASSERT((ijk & ~(DIM-1u)) == leafOrigin);
                                const Index index = ij + /*k*/(ijk.z() & (DIM-1u));
                                const Index end = valiter.getItem(index);
                                Index id = (index == 0) ? 0 : Index(valiter.getItem(index - 1));
                                if (this->interrupted()) return;

                                if constexpr (UseRasterizePoints)
                                {
                                    // No filter support here, must be on the transfer scheme
                                    mTransfer.rasterizePoints(ijk, id, end, bounds);
                                }
                                else if constexpr (UseRasterizePoint)
                                {
                                    for (; id < end; ++id) {
                                        if (!localFilter.valid(&id)) continue;
                                        mTransfer.rasterizePoint(ijk, id, bounds);
                                    } //point idx
                                }
                                else {
                                    static_assert(UseRasterizePoints || UseRasterizePoint,
                                        "Invalid transfer scheme in openvdb::tools::rasterize. Must correctly Implement rasterizePoints or rasterizePoint.");
                                }
                            }
                        }
                    } // outer point voxel

                    if (!mTransfer.endPointLeaf(*pointLeaf)) {
                        // rescurse if necessary
                        if (!mTransfer.finalize(origin, idx)) {
                            this->operator()(leaf, idx);
                        }
                        return;
                    }
                }
            }
        } // outer leaf node

        // rescurse if necessary
        if (!mTransfer.finalize(origin, idx)) {
            this->operator()(leaf, idx);
        }
    }

    void operator()(const typename LeafManagerT::LeafRange& range) const
    {
        for (auto leaf = range.begin(); leaf; ++leaf) {
            (*this)(*leaf, leaf.pos());
        }
    }

private:

    template <typename EnableT = TransferT>
    typename std::enable_if<std::is_base_of<TransformTransfer, EnableT>::value>::type
    transform(CoordBBox& bounds) const
    {
        const TransformTransfer* transform =
            static_cast<TransformTransfer*>(&mTransfer);
        const BBoxd bbox(bounds.min().asVec3d(), bounds.max().asVec3d());
        bounds = transform->sourceTransform().worldToIndexCellCentered(
            transform->targetTransform().indexToWorld(bbox));
    }

    template <typename EnableT = TransferT>
    typename std::enable_if<!std::is_base_of<TransformTransfer, EnableT>::value>::type
    transform(CoordBBox&) const {}

    template <typename EnableT = TransferT>
    typename std::enable_if<std::is_base_of<InterruptableTransfer, EnableT>::value, bool>::type
    interrupted() const
    {
        return mTransfer.interrupted();
    }

    template <typename EnableT = TransferT>
    constexpr typename std::enable_if<!std::is_base_of<InterruptableTransfer, EnableT>::value, bool>::type
    interrupted() const
    {
        // @todo  This method should just return false once the old rasterize signature is deprecated
        if constexpr (std::is_same<InterrupterT, util::NullInterrupter>::value) return false;
        else {
            if (util::wasInterrupted(mInterrupter)) {
                thread::cancelGroupExecution();
                return true;
            }
            return false;
        }
    }

private:
    const PointDataGrid::ConstAccessor mPointAccessor;
    mutable TransferT mTransfer;
    const CoordBBox& mPointBounds;
    const PointFilterT& mFilter; // @todo remove
    InterrupterT* mInterrupter; // @todo remove
};

} // namespace transfer_internal

///////////////////////////////////////////////////
///////////////////////////////////////////////////

template <typename PointDataTreeOrGridT, typename TransferT>
inline void
rasterize(const PointDataTreeOrGridT& points, TransferT& transfer)
{
    using PointTreeT = typename TreeAdapter<PointDataTreeOrGridT>::TreeType;
    static_assert(std::is_base_of<TreeBase, PointTreeT>::value,
        "Provided points to rasterize is not a derived TreeBase type.");

    const auto& tree = TreeAdapter<PointDataTreeOrGridT>::tree(points);
    auto& topology = transfer.topology();
    using TreeT = typename std::decay<decltype(topology)>::type;

    // Compute max search bounds
    CoordBBox bounds;
    tree.evalLeafBoundingBox(bounds);

    tree::LeafManager<TreeT> manager(topology);
    transfer_internal::RasterizePoints<TransferT, TreeT> raster(tree, transfer, bounds);
    manager.foreach(raster);
}

/// @brief Perform potentially complex rasterization from a user defined
///  transfer scheme.
/// @details The method works by looping over a single Tree topology, looking
///   up point data at a position relative to that topology and passing that
///   data to a transfer scheme TransferT.
/// @note  Each thread receives a copy of the transfer scheme object.
/// @param points       the point data grid to rasterize
/// @param transfer     the transfer scheme
/// @param filter       optional point filter
/// @param interrupter  optional interrupter
template <typename PointDataTreeOrGridT,
    typename TransferT,
    typename FilterT,
    typename InterrupterT = util::NullInterrupter>
OPENVDB_DEPRECATED_MESSAGE("openvdb::tools::rasterize no longer takes a filter or "
"interrupter. Implement this on your transfer scheme (see PointTransfer.h for an example).")
inline void
rasterize(const PointDataTreeOrGridT& points,
          TransferT& transfer,
          const FilterT& filter,
          InterrupterT* interrupter = nullptr)
{
    using PointTreeT = typename TreeAdapter<PointDataTreeOrGridT>::TreeType;
    static_assert(std::is_base_of<TreeBase, PointTreeT>::value,
        "Provided points to rasterize is not a derived TreeBase type.");

    const auto& tree = TreeAdapter<PointDataTreeOrGridT>::tree(points);

    auto& topology = transfer.topology();
    using TreeT = typename std::decay<decltype(topology)>::type;

    // Compute max search bounds
    CoordBBox bounds;
    tree.evalLeafBoundingBox(bounds);

    tree::LeafManager<TreeT> manager(topology);
    transfer_internal::RasterizePoints<TransferT, TreeT, FilterT, InterrupterT>
        raster(tree, transfer, bounds, filter, interrupter);
    manager.foreach(raster);
}

} // namespace points
} // namespace OPENVDB_VERSION_NAME
} // namespace openvdb

#endif //OPENVEB_POINTS_TRANSFER_HAS_BEEN_INCLUDED
