// Copyright 2009-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0

#pragma once

#define MBLUR_NUM_TEMPORAL_BINS 2
#define MBLUR_NUM_OBJECT_BINS   32

#include "../bvh/bvh.h"
#include "../common/primref_mb.h"
#include "heuristic_binning_array_aligned.h"
#include "heuristic_timesplit_array.h"

namespace embree
{
  namespace isa
  {
    template<typename T>
      struct SharedVector
      {
        __forceinline SharedVector() {}

        __forceinline SharedVector(T* ptr, size_t refCount = 1)
          : prims(ptr), refCount(refCount) {}

        __forceinline void incRef() {
          refCount++;
        }

        __forceinline void decRef()
        {
          if (--refCount == 0)
            delete prims;
        }

        T* prims;
        size_t refCount;
      };

    template<typename BuildRecord, int MAX_BRANCHING_FACTOR>
      struct LocalChildListT
      {
        typedef SharedVector<mvector<PrimRefMB>> SharedPrimRefVector;

        __forceinline LocalChildListT (const BuildRecord& record)
          : numChildren(1), numSharedPrimVecs(1)
        {
          /* the local root will be freed in the ancestor where it was created (thus refCount is 2) */
          children[0] = record;
          primvecs[0] = new (&sharedPrimVecs[0]) SharedPrimRefVector(record.prims.prims, 2);
        }

        __forceinline ~LocalChildListT()
        {
          for (size_t i = 0; i < numChildren; i++)
            primvecs[i]->decRef();
        }

        __forceinline BuildRecord& operator[] ( const size_t i ) {
          return children[i];
        }

        __forceinline size_t size() const {
          return numChildren;
        }

        __forceinline void split(ssize_t bestChild, const BuildRecord& lrecord, const BuildRecord& rrecord, std::unique_ptr<mvector<PrimRefMB>> new_vector)
        {
          SharedPrimRefVector* bsharedPrimVec = primvecs[bestChild];
          if (lrecord.prims.prims == bsharedPrimVec->prims) {
            primvecs[bestChild] = bsharedPrimVec;
            bsharedPrimVec->incRef();
          }
          else {
            primvecs[bestChild] = new (&sharedPrimVecs[numSharedPrimVecs++]) SharedPrimRefVector(lrecord.prims.prims);
          }

          if (rrecord.prims.prims == bsharedPrimVec->prims) {
            primvecs[numChildren] = bsharedPrimVec;
            bsharedPrimVec->incRef();
          }
          else {
            primvecs[numChildren] = new (&sharedPrimVecs[numSharedPrimVecs++]) SharedPrimRefVector(rrecord.prims.prims);
          }
          bsharedPrimVec->decRef();
          new_vector.release();

          children[bestChild] = lrecord;
          children[numChildren] = rrecord;
          numChildren++;
        }

      public:
        array_t<BuildRecord,MAX_BRANCHING_FACTOR> children;
        array_t<SharedPrimRefVector*,MAX_BRANCHING_FACTOR> primvecs;
        size_t numChildren;

        array_t<SharedPrimRefVector,2*MAX_BRANCHING_FACTOR> sharedPrimVecs;
        size_t numSharedPrimVecs;
      };

    template<typename Mesh>
      struct RecalculatePrimRef
      {
        Scene* scene;

        __forceinline RecalculatePrimRef (Scene* scene)
          : scene(scene) {}

        __forceinline PrimRefMB operator() (const PrimRefMB& prim, const BBox1f time_range) const
        {
          const unsigned geomID = prim.geomID();
          const unsigned primID = prim.primID();
          const Mesh* mesh = scene->get<Mesh>(geomID);
          const LBBox3fa lbounds = mesh->linearBounds(primID, time_range);
          const range<int> tbounds = mesh->timeSegmentRange(time_range);
          return PrimRefMB (lbounds, tbounds.size(), mesh->time_range, mesh->numTimeSegments(), geomID, primID);
        }

        // __noinline is workaround for ICC16 bug under MacOSX
        __noinline PrimRefMB operator() (const PrimRefMB& prim, const BBox1f time_range, const LinearSpace3fa& space) const
        {
          const unsigned geomID = prim.geomID();
          const unsigned primID = prim.primID();
          const Mesh* mesh = scene->get<Mesh>(geomID);
          const LBBox3fa lbounds = mesh->linearBounds(space, primID, time_range);
          const range<int> tbounds = mesh->timeSegmentRange(time_range);
          return PrimRefMB (lbounds, tbounds.size(), mesh->time_range, mesh->numTimeSegments(), geomID, primID);
        }

        __forceinline LBBox3fa linearBounds(const PrimRefMB& prim, const BBox1f time_range) const {
          return scene->get<Mesh>(prim.geomID())->linearBounds(prim.primID(), time_range);
        }

        // __noinline is workaround for ICC16 bug under MacOSX
        __noinline LBBox3fa linearBounds(const PrimRefMB& prim, const BBox1f time_range, const LinearSpace3fa& space) const {
          return scene->get<Mesh>(prim.geomID())->linearBounds(space, prim.primID(), time_range);
        }
      };

    struct VirtualRecalculatePrimRef
    {
      Scene* scene;
      
      __forceinline VirtualRecalculatePrimRef (Scene* scene)
        : scene(scene) {}
      
      __forceinline PrimRefMB operator() (const PrimRefMB& prim, const BBox1f time_range) const
      {
        const unsigned geomID = prim.geomID();
        const unsigned primID = prim.primID();
        const Geometry* mesh = scene->get(geomID);
        const LBBox3fa lbounds = mesh->vlinearBounds(primID, time_range);
        const range<int> tbounds = mesh->timeSegmentRange(time_range);
        return PrimRefMB (lbounds, tbounds.size(), mesh->time_range, mesh->numTimeSegments(), geomID, primID);
      }
      
      __forceinline PrimRefMB operator() (const PrimRefMB& prim, const BBox1f time_range, const LinearSpace3fa& space) const
      {
        const unsigned geomID = prim.geomID();
        const unsigned primID = prim.primID();
        const Geometry* mesh = scene->get(geomID);
        const LBBox3fa lbounds = mesh->vlinearBounds(space, primID, time_range);
        const range<int> tbounds = mesh->timeSegmentRange(time_range);
        return PrimRefMB (lbounds, tbounds.size(), mesh->time_range, mesh->numTimeSegments(), geomID, primID);
      }
      
      __forceinline LBBox3fa linearBounds(const PrimRefMB& prim, const BBox1f time_range) const {
        return scene->get(prim.geomID())->vlinearBounds(prim.primID(), time_range);
      }
      
      __forceinline LBBox3fa linearBounds(const PrimRefMB& prim, const BBox1f time_range, const LinearSpace3fa& space) const {
        return scene->get(prim.geomID())->vlinearBounds(space, prim.primID(), time_range);
      }
    };

    struct BVHBuilderMSMBlur
    {
      /*! settings for msmblur builder */
      struct Settings
      {
        /*! default settings */
        Settings ()
        : branchingFactor(2), maxDepth(32), logBlockSize(0), minLeafSize(1), maxLeafSize(8),
          travCost(1.0f), intCost(1.0f), singleLeafTimeSegment(false),
          singleThreadThreshold(1024) {}


        Settings (size_t sahBlockSize, size_t minLeafSize, size_t maxLeafSize, float travCost, float intCost, size_t singleThreadThreshold)
        : branchingFactor(2), maxDepth(32), logBlockSize(bsr(sahBlockSize)), minLeafSize(minLeafSize), maxLeafSize(maxLeafSize),
          travCost(travCost), intCost(intCost), singleThreadThreshold(singleThreadThreshold)
        {
          minLeafSize = min(minLeafSize,maxLeafSize);
        }

      public:
        size_t branchingFactor;  //!< branching factor of BVH to build
        size_t maxDepth;         //!< maximum depth of BVH to build
        size_t logBlockSize;     //!< log2 of blocksize for SAH heuristic
        size_t minLeafSize;      //!< minimum size of a leaf
        size_t maxLeafSize;      //!< maximum size of a leaf
        float travCost;          //!< estimated cost of one traversal step
        float intCost;           //!< estimated cost of one primitive intersection
        bool singleLeafTimeSegment; //!< split time to single time range
        size_t singleThreadThreshold; //!< threshold when we switch to single threaded build
      };

      struct BuildRecord
      {
      public:
	__forceinline BuildRecord () {}

        __forceinline BuildRecord (size_t depth)
          : depth(depth) {}

        __forceinline BuildRecord (const SetMB& prims, size_t depth)
          : depth(depth), prims(prims) {}

        __forceinline friend bool operator< (const BuildRecord& a, const BuildRecord& b) {
          return a.prims.size() < b.prims.size();
        }

        __forceinline size_t size() const {
          return prims.size();
        }

      public:
	size_t depth;                     //!< Depth of the root of this subtree.
	SetMB prims;                      //!< The list of primitives.
      };

      struct BuildRecordSplit : public BuildRecord
      {
        __forceinline BuildRecordSplit () {}

        __forceinline BuildRecordSplit (size_t depth) 
          : BuildRecord(depth) {}

        __forceinline BuildRecordSplit (const BuildRecord& record, const BinSplit<MBLUR_NUM_OBJECT_BINS>& split)
          : BuildRecord(record), split(split) {}
        
        BinSplit<MBLUR_NUM_OBJECT_BINS> split;
      };

      template<
        typename NodeRef,
        typename RecalculatePrimRef,
        typename Allocator,
        typename CreateAllocFunc,
        typename CreateNodeFunc,
        typename SetNodeFunc,
        typename CreateLeafFunc,
        typename ProgressMonitor>

        class BuilderT
        {
          ALIGNED_CLASS_(16);
          static const size_t MAX_BRANCHING_FACTOR = 16;       //!< maximum supported BVH branching factor	  
          static const size_t MIN_LARGE_LEAF_LEVELS = 8;        //!< create balanced tree if we are that many levels before the maximum tree depth

          typedef BVHNodeRecordMB4D<NodeRef> NodeRecordMB4D;
          typedef BinSplit<MBLUR_NUM_OBJECT_BINS> Split;
          typedef mvector<PrimRefMB>* PrimRefVector;
          typedef SharedVector<mvector<PrimRefMB>> SharedPrimRefVector;
          typedef LocalChildListT<BuildRecord,MAX_BRANCHING_FACTOR> LocalChildList;
          typedef LocalChildListT<BuildRecordSplit,MAX_BRANCHING_FACTOR> LocalChildListSplit;

        public:

          BuilderT (MemoryMonitorInterface* device,
                    const RecalculatePrimRef recalculatePrimRef,
                    const CreateAllocFunc createAlloc,
                    const CreateNodeFunc createNode,
                    const SetNodeFunc setNode,
                    const CreateLeafFunc createLeaf,
                    const ProgressMonitor progressMonitor,
                    const Settings& settings)
            : cfg(settings),
            heuristicObjectSplit(),
            heuristicTemporalSplit(device, recalculatePrimRef),
            recalculatePrimRef(recalculatePrimRef), createAlloc(createAlloc), createNode(createNode), setNode(setNode), createLeaf(createLeaf),
            progressMonitor(progressMonitor)
          {
            if (cfg.branchingFactor > MAX_BRANCHING_FACTOR)
              throw_RTCError(RTC_ERROR_UNKNOWN,"bvh_builder: branching factor too large");
          }

          /*! finds the best split */
          const Split find(const SetMB& set)
          {
            /* first try standard object split */
            const Split object_split = heuristicObjectSplit.find(set,cfg.logBlockSize);
            const float object_split_sah = object_split.splitSAH();

            /* test temporal splits only when object split was bad */
            const float leaf_sah = set.leafSAH(cfg.logBlockSize);
            if (object_split_sah < 0.50f*leaf_sah)
              return object_split;

            /* do temporal splits only if the time range is big enough */
            if (set.time_range.size() > 1.01f/float(set.max_num_time_segments))
            {
              const Split temporal_split = heuristicTemporalSplit.find(set,cfg.logBlockSize);
              const float temporal_split_sah = temporal_split.splitSAH();

              /* take temporal split if it improved SAH */
              if (temporal_split_sah < object_split_sah)
                return temporal_split;
            }

            return object_split;
          }

          /*! array partitioning */
          __forceinline std::unique_ptr<mvector<PrimRefMB>> split(const Split& split, const SetMB& set, SetMB& lset, SetMB& rset)
          {
            /* perform object split */
            if (likely(split.data == Split::SPLIT_OBJECT)) {
              heuristicObjectSplit.split(split,set,lset,rset);
            }
            /* perform temporal split */
            else if (likely(split.data == Split::SPLIT_TEMPORAL)) {
              return heuristicTemporalSplit.split(split,set,lset,rset);
            }
            /* perform fallback split */
            else if (unlikely(split.data == Split::SPLIT_FALLBACK)) {
              set.deterministic_order();
              splitFallback(set,lset,rset);
            }
            /* split by geometry */
            else if (unlikely(split.data == Split::SPLIT_GEOMID)) {
              set.deterministic_order();
              splitByGeometry(set,lset,rset);
            }
            else
              assert(false);

            return std::unique_ptr<mvector<PrimRefMB>>();
          }

          /*! finds the best fallback split */
          __noinline Split findFallback(const SetMB& set)
          {
            /* split if primitives are not from same geometry */
            if (!sameGeometry(set))
              return Split(0.0f,Split::SPLIT_GEOMID);
            
            /* if a leaf can only hold a single time-segment, we might have to do additional temporal splits */
            if (cfg.singleLeafTimeSegment)
            {
              /* test if one primitive has more than one time segment in time range, if so split time */
              for (size_t i=set.begin(); i<set.end(); i++)
              {
                const PrimRefMB& prim = (*set.prims)[i];
                const range<int> itime_range = prim.timeSegmentRange(set.time_range);
                const int localTimeSegments = itime_range.size();
                assert(localTimeSegments > 0);
                if (localTimeSegments > 1) {
                  const int icenter = (itime_range.begin() + itime_range.end())/2;
                  const float splitTime = prim.timeStep(icenter);
                  return Split(0.0f,(unsigned)Split::SPLIT_TEMPORAL,0,splitTime);
                }
              }
            }        

            /* otherwise return fallback split */
            return Split(0.0f,Split::SPLIT_FALLBACK);
          }

          /*! performs fallback split */
          void splitFallback(const SetMB& set, SetMB& lset, SetMB& rset)
          {
            mvector<PrimRefMB>& prims = *set.prims;

            const size_t begin = set.begin();
            const size_t end   = set.end();
            const size_t center = (begin + end + 1) / 2;

            PrimInfoMB linfo = empty;
            for (size_t i=begin; i<center; i++)
              linfo.add_primref(prims[i]);

            PrimInfoMB rinfo = empty;
            for (size_t i=center; i<end; i++)
              rinfo.add_primref(prims[i]);

            new (&lset) SetMB(linfo,set.prims,range<size_t>(begin,center),set.time_range);
            new (&rset) SetMB(rinfo,set.prims,range<size_t>(center,end  ),set.time_range);
          }

          /*! checks if all primitives are from the same geometry */
          __forceinline bool sameGeometry(const SetMB& set)
          {
            if (set.size() == 0) return true;
            mvector<PrimRefMB>& prims = *set.prims;
            const size_t begin = set.begin();
            const size_t end   = set.end();
            unsigned int firstGeomID = prims[begin].geomID();
            for (size_t i=begin+1; i<end; i++) {
              if (prims[i].geomID() != firstGeomID){
                return false;
              }
            }
            return true;
          }

          /* split by geometry ID */
          void splitByGeometry(const SetMB& set, SetMB& lset, SetMB& rset)
          {
            assert(set.size() > 1);

            mvector<PrimRefMB>& prims = *set.prims;
            const size_t begin = set.begin();
            const size_t end   = set.end();
            
            PrimInfoMB left(empty);
            PrimInfoMB right(empty);
            unsigned int geomID = prims[begin].geomID();
            size_t center = serial_partitioning(prims.data(),begin,end,left,right,
                                                [&] ( const PrimRefMB& prim ) { return prim.geomID() == geomID; },
                                                [ ] ( PrimInfoMB& dst, const PrimRefMB& prim ) { dst.add_primref(prim); });
            
            new (&lset) SetMB(left, set.prims,range<size_t>(begin,center),set.time_range);
            new (&rset) SetMB(right,set.prims,range<size_t>(center,end  ),set.time_range);
          }

          const NodeRecordMB4D createLargeLeaf(const BuildRecord& in, Allocator alloc)
          {
            /* this should never occur but is a fatal error */
            if (in.depth > cfg.maxDepth)
              throw_RTCError(RTC_ERROR_UNKNOWN,"depth limit reached");

            /* replace already found split by fallback split */
            const BuildRecordSplit current(BuildRecord(in.prims,in.depth),findFallback(in.prims));

            /* special case when directly creating leaf without any splits that could shrink time_range */
            bool force_split = false;
            if (current.depth == 1 && current.size() > 0)
            {
              BBox1f c = empty;
              BBox1f p = current.prims.time_range;
              for (size_t i=current.prims.begin(); i<current.prims.end(); i++) {
                mvector<PrimRefMB>& prims = *current.prims.prims;
                c.extend(prims[i].time_range);
              }
              
              force_split = c.lower > p.lower || c.upper < p.upper;
            }
	    
            /* create leaf for few primitives */
            if (current.size() <= cfg.maxLeafSize && current.split.data < Split::SPLIT_ENFORCE && !force_split)
              return createLeaf(current,alloc);
	  
            /* fill all children by always splitting the largest one */
            bool hasTimeSplits = false;
            NodeRecordMB4D values[MAX_BRANCHING_FACTOR];
            LocalChildListSplit children(current);

            do {
              /* find best child with largest bounding box area */
              size_t bestChild = -1;
              size_t bestSize = 0;
              for (size_t i=0; i<children.size(); i++)
              {
                /* ignore leaves as they cannot get split */
                if (children[i].size() <= cfg.maxLeafSize && children[i].split.data < Split::SPLIT_ENFORCE && !force_split)
                  continue;

                force_split = false;
                
                /* remember child with largest size */
                if (children[i].size() > bestSize) {
                  bestSize = children[i].size();
                  bestChild = i;
                }
              }
              if (bestChild == -1) break;

              /* perform best found split */
              BuildRecordSplit& brecord = children[bestChild];
              BuildRecordSplit lrecord(current.depth+1);
              BuildRecordSplit rrecord(current.depth+1);
              std::unique_ptr<mvector<PrimRefMB>> new_vector = split(brecord.split,brecord.prims,lrecord.prims,rrecord.prims);
              hasTimeSplits |= new_vector != nullptr;

              /* find new splits */
              lrecord.split = findFallback(lrecord.prims);
              rrecord.split = findFallback(rrecord.prims);
              children.split(bestChild,lrecord,rrecord,std::move(new_vector));

            } while (children.size() < cfg.branchingFactor);

            /* detect time_ranges that have shrunken */
            for (size_t i=0; i<children.size(); i++) {
              const BBox1f c = children[i].prims.time_range;
              const BBox1f p = in.prims.time_range;
              hasTimeSplits |= c.lower > p.lower || c.upper < p.upper;
            }

            /* create node */
            auto node = createNode(children.children.data(),children.numChildren,alloc,hasTimeSplits);

            /* recurse into each child and perform reduction */
            LBBox3fa gbounds = empty;
            for (size_t i=0; i<children.size(); i++) {
              values[i] = createLargeLeaf(children[i],alloc);
              gbounds.extend(values[i].lbounds);
            }

            setNode(current,children.children.data(),node,values,children.numChildren);

            /* calculate geometry bounds of this node */
            if (hasTimeSplits)
              return NodeRecordMB4D(node,current.prims.linearBounds(recalculatePrimRef),current.prims.time_range);
            else
              return NodeRecordMB4D(node,gbounds,current.prims.time_range);
          }

          const NodeRecordMB4D recurse(const BuildRecord& current, Allocator alloc, bool toplevel)
          {
            /* get thread local allocator */
            if (!alloc)
              alloc = createAlloc();

            /* call memory monitor function to signal progress */
            if (toplevel && current.size() <= cfg.singleThreadThreshold)
              progressMonitor(current.size());

            /*! find best split */
            const Split csplit = find(current.prims);

            /*! compute leaf and split cost */
            const float leafSAH  = cfg.intCost*current.prims.leafSAH(cfg.logBlockSize);
            const float splitSAH = cfg.travCost*current.prims.halfArea()+cfg.intCost*csplit.splitSAH();
            assert((current.size() == 0) || ((leafSAH >= 0) && (splitSAH >= 0)));

            /*! create a leaf node when threshold reached or SAH tells us to stop */
            if (current.size() <= cfg.minLeafSize || current.depth+MIN_LARGE_LEAF_LEVELS >= cfg.maxDepth || (current.size() <= cfg.maxLeafSize && leafSAH <= splitSAH)) {
              current.prims.deterministic_order();
              return createLargeLeaf(current,alloc);
            }

            /*! perform initial split */
            SetMB lprims,rprims;
            std::unique_ptr<mvector<PrimRefMB>> new_vector = split(csplit,current.prims,lprims,rprims);
            bool hasTimeSplits = new_vector != nullptr;
            NodeRecordMB4D values[MAX_BRANCHING_FACTOR];
            LocalChildList children(current);
            {
              BuildRecord lrecord(lprims,current.depth+1);
              BuildRecord rrecord(rprims,current.depth+1);
              children.split(0,lrecord,rrecord,std::move(new_vector));
            }

            /*! split until node is full or SAH tells us to stop */
            while (children.size() < cfg.branchingFactor) 
            {
              /*! find best child to split */
              float bestArea = neg_inf;
              ssize_t bestChild = -1;
              for (size_t i=0; i<children.size(); i++)
              {
                if (children[i].size() <= cfg.minLeafSize) continue;
                if (expectedApproxHalfArea(children[i].prims.geomBounds) > bestArea) {
                  bestChild = i; bestArea = expectedApproxHalfArea(children[i].prims.geomBounds);
                }
              }
              if (bestChild == -1) break;

              /* perform split */
              BuildRecord& brecord = children[bestChild];
              BuildRecord lrecord(current.depth+1);
              BuildRecord rrecord(current.depth+1);
              Split csplit = find(brecord.prims);
              std::unique_ptr<mvector<PrimRefMB>> new_vector = split(csplit,brecord.prims,lrecord.prims,rrecord.prims);
              hasTimeSplits |= new_vector != nullptr;
              children.split(bestChild,lrecord,rrecord,std::move(new_vector));
            }

            /* detect time_ranges that have shrunken */
            for (size_t i=0; i<children.size(); i++) {
              const BBox1f c = children[i].prims.time_range;
              const BBox1f p = current.prims.time_range;
              hasTimeSplits |= c.lower > p.lower || c.upper < p.upper;
            }

            /* sort buildrecords for simpler shadow ray traversal */
            //std::sort(&children[0],&children[children.size()],std::greater<BuildRecord>()); // FIXME: reduces traversal performance of bvh8.triangle4 (need to verified) !!

            /*! create an inner node */
            auto node = createNode(children.children.data(), children.numChildren, alloc, hasTimeSplits);
            LBBox3fa gbounds = empty;

            /* spawn tasks */
            if (unlikely(current.size() > cfg.singleThreadThreshold))
            {
              /*! parallel_for is faster than spawing sub-tasks */
              parallel_for(size_t(0), children.size(), [&] (const range<size_t>& r) {
                  for (size_t i=r.begin(); i<r.end(); i++) {
                    values[i] = recurse(children[i],nullptr,true);
                    _mm_mfence(); // to allow non-temporal stores during build
                  }
                });

              /*! merge bounding boxes */
              for (size_t i=0; i<children.size(); i++)
                gbounds.extend(values[i].lbounds);
            }
            /* recurse into each child */
            else
            {
              //for (size_t i=0; i<children.size(); i++)
              for (ssize_t i=children.size()-1; i>=0; i--) {
                values[i] = recurse(children[i],alloc,false);
                gbounds.extend(values[i].lbounds);
              }
            }

            setNode(current,children.children.data(),node,values,children.numChildren);

            /* calculate geometry bounds of this node */
            if (unlikely(hasTimeSplits))
              return NodeRecordMB4D(node,current.prims.linearBounds(recalculatePrimRef),current.prims.time_range);
            else
              return NodeRecordMB4D(node,gbounds,current.prims.time_range);
          }

          /*! builder entry function */
          __forceinline const NodeRecordMB4D operator() (mvector<PrimRefMB>& prims, const PrimInfoMB& pinfo)
          {
            const SetMB set(pinfo,&prims);
            auto ret = recurse(BuildRecord(set,1),nullptr,true);
            _mm_mfence(); // to allow non-temporal stores during build
            return ret;
          }

        private:
          Settings cfg;
          HeuristicArrayBinningMB<PrimRefMB,MBLUR_NUM_OBJECT_BINS> heuristicObjectSplit;
          HeuristicMBlurTemporalSplit<PrimRefMB,RecalculatePrimRef,MBLUR_NUM_TEMPORAL_BINS> heuristicTemporalSplit;
          const RecalculatePrimRef recalculatePrimRef;
          const CreateAllocFunc createAlloc;
          const CreateNodeFunc createNode;
          const SetNodeFunc setNode;
          const CreateLeafFunc createLeaf;
          const ProgressMonitor progressMonitor;
        };

      template<typename NodeRef,
        typename RecalculatePrimRef,
        typename CreateAllocFunc,
        typename CreateNodeFunc,
        typename SetNodeFunc,
        typename CreateLeafFunc,
        typename ProgressMonitorFunc>

        static const BVHNodeRecordMB4D<NodeRef> build(mvector<PrimRefMB>& prims,
                                                      const PrimInfoMB& pinfo,
                                                      MemoryMonitorInterface* device,
                                                      const RecalculatePrimRef recalculatePrimRef,
                                                      const CreateAllocFunc createAlloc,
                                                      const CreateNodeFunc createNode,
                                                      const SetNodeFunc setNode,
                                                      const CreateLeafFunc createLeaf,
                                                      const ProgressMonitorFunc progressMonitor,
                                                      const Settings& settings)
      {
          typedef BuilderT<
            NodeRef,
            RecalculatePrimRef,
            decltype(createAlloc()),
            CreateAllocFunc,
            CreateNodeFunc,
            SetNodeFunc,
            CreateLeafFunc,
            ProgressMonitorFunc> Builder;

          Builder builder(device,
                          recalculatePrimRef,
                          createAlloc,
                          createNode,
                          setNode,
                          createLeaf,
                          progressMonitor,
                          settings);


          return builder(prims,pinfo);
        }
    };
  }
}
