1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 
4 /*
5  * Copyright (c) Side Effects Software Inc.
6  *
7  * Produced by:
8  *      Side Effects Software Inc
9  *      477 Richmond Street West
10  *      Toronto, Ontario
11  *      Canada   M5V 3E7
12  *      416-504-9876
13  *
14  * NAME:        GU_PrimVDB.C ( GU Library, C++)
15  *
16  * COMMENTS:    Definitions for utility functions of vdb.
17  */
18 
19 #include <UT/UT_Version.h>
20 
21 #if defined(SESI_OPENVDB) || defined(SESI_OPENVDB_PRIM)
22 
23 #include "GU_PrimVDB.h"
24 
25 #include "GT_GEOPrimCollectVDB.h"
26 #include <GU/GU_ConvertParms.h>
27 #include <GU/GU_PrimPoly.h>
28 #include <GU/GU_PrimPolySoup.h>
29 #include <GU/GU_PrimVolume.h>
30 #include <GU/GU_RayIntersect.h>
31 
32 #include <GEO/GEO_AttributeHandleList.h>
33 #include <GEO/GEO_Closure.h>
34 #include <GEO/GEO_WorkVertexBuffer.h>
35 
36 #include <GA/GA_AIFTuple.h>
37 #include <GA/GA_AttributeFilter.h>
38 #include <GA/GA_ElementWrangler.h>
39 #include <GA/GA_Handle.h>
40 #include <GA/GA_PageHandle.h>
41 #include <GA/GA_PageIterator.h>
42 #include <GA/GA_SplittableRange.h>
43 
44 #include <UT/UT_Debug.h>
45 #include <UT/UT_Interrupt.h>
46 #include <UT/UT_Lock.h>
47 #include <UT/UT_MemoryCounter.h>
48 #include <UT/UT_ParallelUtil.h>
49 #include <UT/UT_UniquePtr.h>
50 
51 #include <UT/UT_Singleton.h>
52 
53 #include <UT/UT_StopWatch.h>
54 
55 #include <SYS/SYS_Inline.h>
56 #include <SYS/SYS_Types.h>
57 #include <SYS/SYS_TypeTraits.h>
58 
59 #include <openvdb/tools/VolumeToMesh.h>
60 
61 #include <hboost/function.hpp>
62 
63 #include <openvdb/tools/SignedFloodFill.h>
64 
65 #include <algorithm>
66 #include <vector>
67 
68 #include <stddef.h>
69 
70 
71 #define TIMING_DEF \
72     UT_StopWatch timer; \
73     if (verbose) timer.start();
74 #define TIMING_LOG(msg) \
75     if (verbose) { \
76         printf(msg ": %f ms\n", 1000*timer.stop()); \
77         fflush(stdout); \
78         timer.start(); \
79     }
80 
81 
82 GA_PrimitiveDefinition *GU_PrimVDB::theDefinition = 0;
83 
84 GU_PrimVDB*
build(GU_Detail * gdp,bool append_points)85 GU_PrimVDB::build(GU_Detail *gdp, bool append_points)
86 {
87 #ifndef SESI_OPENVDB
88     // This is only necessary as a stop gap measure until we have the
89     // registration code split out properly.
90     if (!GU_PrimVDB::theDefinition)
91         GU_PrimVDB::registerMyself(&GUgetFactory());
92 
93     GU_PrimVDB* primvdb = (GU_PrimVDB *)gdp->appendPrimitive(GU_PrimVDB::theTypeId());
94 
95 #else
96 
97     GU_PrimVDB* primvdb = UTverify_cast<GU_PrimVDB *>(gdp->appendPrimitive(GEO_PRIMVDB));
98     primvdb->assignVertex(gdp->appendVertex(), true);
99 
100 #endif
101 
102     if (append_points) {
103         GEO_Primitive *prim = primvdb;
104         const GA_Size npts = primvdb->getVertexCount();
105         GA_Offset startptoff = gdp->appendPointBlock(npts);
106         for (GA_Size i = 0; i < npts; i++) {
107             prim->setPointOffset(i, startptoff+i);
108         }
109     }
110     return primvdb;
111 }
112 
113 GU_PrimVDB*
buildFromGridAdapter(GU_Detail & gdp,void * gridPtr,const GEO_PrimVDB * src,const char * name)114 GU_PrimVDB::buildFromGridAdapter(GU_Detail& gdp, void* gridPtr,
115     const GEO_PrimVDB* src, const char* name)
116 {
117     // gridPtr is assumed to point to an openvdb::vX_Y_Z::GridBase::Ptr, for
118     // some version X.Y.Z of OpenVDB that may be newer than the one with which
119     // libHoudiniGEO.so was built.  This is safe provided that GridBase and
120     // its member objects are ABI-compatible between the two OpenVDB versions.
121     openvdb::GridBase::Ptr grid =
122         *static_cast<openvdb::GridBase::Ptr*>(gridPtr);
123     if (!grid)
124         return nullptr;
125 
126     GU_PrimVDB* vdb = GU_PrimVDB::build(&gdp);
127     if (vdb != nullptr) {
128         if (src != nullptr) {
129             // Copy the source primitive's attributes to this primitive,
130             // then transfer those attributes to this grid's metadata.
131             vdb->copyAttributesAndGroups(*src, /*copyGroups=*/true);
132             GU_PrimVDB::createMetadataFromGridAttrs(*grid, *vdb, gdp);
133 
134             // Copy the source's visualization options.
135             GEO_VolumeOptions visopt = src->getVisOptions();
136             vdb->setVisualization(visopt.myMode, visopt.myIso, visopt.myDensity,
137                                   visopt.myLod);
138         }
139 
140         // Ensure that certain metadata exists (grid name, grid class, etc.).
141         if (name != nullptr) grid->setName(name);
142         grid->removeMeta("value_type");
143         grid->insertMeta("value_type", openvdb::StringMetadata(grid->valueType()));
144         // For each of the following, force any existing metadata's value to be
145         // one of the supported values. Note the careful 3 statement sequences
146         // so that it works with type mismatches.
147         openvdb::GridClass grid_class = grid->getGridClass();
148         grid->removeMeta(openvdb::GridBase::META_GRID_CLASS);
149         grid->setGridClass(grid_class);
150         openvdb::VecType vec_type = grid->getVectorType();
151         grid->removeMeta(openvdb::GridBase::META_VECTOR_TYPE);
152         grid->setVectorType(vec_type);
153         bool is_in_world_space = grid->isInWorldSpace();
154         grid->removeMeta(openvdb::GridBase::META_IS_LOCAL_SPACE);
155         grid->setIsInWorldSpace(is_in_world_space);
156         bool save_as_half = grid->saveFloatAsHalf();
157         grid->removeMeta(openvdb::GridBase::META_SAVE_HALF_FLOAT);
158         grid->setSaveFloatAsHalf(save_as_half);
159 
160         // Transfer the grid's metadata to primitive attributes.
161         GU_PrimVDB::createGridAttrsFromMetadata(*vdb, *grid, gdp);
162 
163         vdb->setGrid(*grid);
164 
165         // If we had no source, have to set options to reasonable
166         // defaults.
167         if (src == nullptr)
168         {
169             if (grid->getGridClass() == openvdb::GRID_LEVEL_SET)
170             {
171                 vdb->setVisualization(GEO_VOLUMEVIS_ISO,
172                                       vdb->getVisIso(), vdb->getVisDensity(),
173                                       vdb->getVisLod());
174             }
175             else
176             {
177                 vdb->setVisualization(GEO_VOLUMEVIS_SMOKE,
178                                       vdb->getVisIso(), vdb->getVisDensity(),
179                                       vdb->getVisLod());
180             }
181         }
182     }
183     return vdb;
184 }
185 
186 int64
getMemoryUsage() const187 GU_PrimVDB::getMemoryUsage() const
188 {
189     int64 mem = sizeof(*this);
190     mem += GEO_PrimVDB::getBaseMemoryUsage();
191     return mem;
192 }
193 
194 void
countMemory(UT_MemoryCounter & counter) const195 GU_PrimVDB::countMemory(UT_MemoryCounter &counter) const
196 {
197     counter.countUnshared(sizeof(*this));
198     GEO_PrimVDB::countBaseMemory(counter);
199 }
200 
201 namespace // anonymous
202 {
203 
204 class gu_VolumeMax
205 {
206 public:
gu_VolumeMax(const UT_VoxelArrayReadHandleF & vox,UT_AutoInterrupt & progress)207     gu_VolumeMax(
208             const UT_VoxelArrayReadHandleF &vox,
209             UT_AutoInterrupt &progress)
210         : myVox(vox)
211         , myProgress(progress)
212         , myMax(std::numeric_limits<float>::min())
213     {
214     }
gu_VolumeMax(const gu_VolumeMax & other,UT_Split)215     gu_VolumeMax(const gu_VolumeMax &other, UT_Split)
216         : myVox(other.myVox)
217         , myProgress(other.myProgress)
218         // NOTE: other.myMax could be half written-to while this
219         //       constructor is being called, so don't use its
220         //       value here.  Initialize myMax as in the main
221         //       constructor.
222         , myMax(std::numeric_limits<float>::min())
223     {
224     }
225 
operator ()(const UT_BlockedRange<int> & range)226     void operator()(const UT_BlockedRange<int> &range)
227     {
228         uint8 bcnt = 0;
229 
230         for (int i = range.begin(); i != range.end(); ++i) {
231             float   min_value;
232             float   max_value;
233 
234             myVox->getLinearTile(i)->findMinMax(min_value, max_value);
235             myMax = SYSmax(myMax, max_value);
236 
237             if (!bcnt++ && myProgress.wasInterrupted())
238                 break;
239         }
240     }
241 
join(const gu_VolumeMax & other)242     void join(const gu_VolumeMax &other)
243     {
244         myMax = std::max(myMax, other.myMax);
245     }
246 
findMax()247     float findMax()
248     {
249         UTparallelReduce(UT_BlockedRange<int>(0, myVox->numTiles()), *this);
250         return myMax;
251     }
252 
253 private:
254     const UT_VoxelArrayReadHandleF &    myVox;
255     UT_AutoInterrupt &                  myProgress;
256     float                               myMax;
257 };
258 
259 class gu_ConvertToVDB
260 {
261 public:
gu_ConvertToVDB(const UT_VoxelArrayReadHandleF & vox,float background,UT_AutoInterrupt & progress,bool activateInsideSDF)262     gu_ConvertToVDB(
263             const UT_VoxelArrayReadHandleF &vox,
264             float background,
265             UT_AutoInterrupt &progress,
266             bool activateInsideSDF
267             )
268         : myVox(vox)
269         , myGrid(openvdb::FloatGrid::create(background))
270         , myProgress(progress)
271         , myActivateInsideSDF(activateInsideSDF)
272     {
273     }
gu_ConvertToVDB(const gu_ConvertToVDB & other,UT_Split)274     gu_ConvertToVDB(const gu_ConvertToVDB &other, UT_Split)
275         : myVox(other.myVox)
276         , myGrid(openvdb::FloatGrid::create(other.myGrid->background()))
277         , myProgress(other.myProgress)
278         , myActivateInsideSDF(other.myActivateInsideSDF)
279     {
280     }
281 
run()282     openvdb::FloatGrid::Ptr run()
283     {
284         using namespace openvdb;
285 
286         UTparallelReduce(UT_BlockedRange<int>(0, myVox->numTiles()), *this);
287 
288         // Check if the VDB grid can be made empty
289         openvdb::Coord dim = myGrid->evalActiveVoxelDim();
290         if (dim[0] == 1 && dim[1] == 1 && dim[2] == 1) {
291             openvdb::Coord ijk = myGrid->evalActiveVoxelBoundingBox().min();
292             float value = myGrid->tree().getValue(ijk);
293             if (openvdb::math::isApproxEqual<float>(value, myGrid->background())) {
294                     myGrid->clear();
295             }
296         }
297 
298         return myGrid;
299     }
300 
operator ()(const UT_BlockedRange<int> & range)301     void operator()(const UT_BlockedRange<int> &range)
302     {
303         using namespace openvdb;
304 
305         FloatGrid &             grid = *myGrid.get();
306         const float             background = grid.background();
307         const UT_VoxelArrayF &  vox = *myVox;
308         uint8                   bcnt = 0;
309 
310         FloatGrid::Accessor acc = grid.getAccessor();
311 
312         for (int i = range.begin(); i != range.end(); ++i) {
313 
314             const UT_VoxelTile<float> & tile = *vox.getLinearTile(i);
315             Coord                       org;
316             Coord                       dim;
317 
318             vox.linearTileToXYZ(i, org[0], org[1], org[2]);
319             org[0] *= TILESIZE; org[1] *= TILESIZE; org[2] *= TILESIZE;
320             dim[0] = tile.xres(); dim[1] = tile.yres(); dim[2] = tile.zres();
321 
322             if (tile.isConstant()) {
323                 CoordBBox   bbox(org, org + dim.offsetBy(-1));
324                 float       value = tile(0, 0, 0);
325 
326                 if (!SYSisEqual(value, background) &&
327                     (myActivateInsideSDF || !SYSisEqual(value, -background))) {
328                     grid.fill(bbox, value);
329                 }
330             } else {
331                 openvdb::Coord ijk;
332                 for (ijk[2] = 0; ijk[2] < dim[2]; ++ijk[2]) {
333                     for (ijk[1] = 0; ijk[1] < dim[1]; ++ijk[1]) {
334                         for (ijk[0] = 0; ijk[0] < dim[0]; ++ijk[0]) {
335                             float value = tile(ijk[0], ijk[1], ijk[2]);
336                             if (!SYSisEqual(value, background) &&
337                                 (myActivateInsideSDF || !SYSisEqual(value, -background))) {
338                                 Coord pos = ijk.offsetBy(org[0], org[1], org[2]);
339                                 acc.setValue(pos, value);
340                             }
341                         }
342                     }
343                 }
344             }
345 
346             if (!bcnt++ && myProgress.wasInterrupted())
347                 break;
348         }
349     }
350 
join(const gu_ConvertToVDB & other)351     void join(const gu_ConvertToVDB &other)
352     {
353         if (myProgress.wasInterrupted())
354             return;
355         UT_IF_ASSERT(int old_count = myGrid->activeVoxelCount();)
356         UT_IF_ASSERT(int other_count = other.myGrid->activeVoxelCount();)
357         myGrid->merge(*other.myGrid);
358         UT_ASSERT(myGrid->activeVoxelCount() == old_count + other_count);
359     }
360 
361 private:
362     const UT_VoxelArrayReadHandleF &    myVox;
363     openvdb::FloatGrid::Ptr             myGrid;
364     UT_AutoInterrupt &                  myProgress;
365     bool                                myActivateInsideSDF;
366 
367 }; // class gu_ConvertToVDB
368 
369 } // namespace anonymous
370 
371 GU_PrimVDB *
buildFromPrimVolume(GU_Detail & geo,const GEO_PrimVolume & vol,const char * name,const bool flood_sdf,const bool prune,const float tolerance,const bool activate_inside_sdf)372 GU_PrimVDB::buildFromPrimVolume(
373         GU_Detail &geo,
374         const GEO_PrimVolume &vol,
375         const char *name,
376         const bool flood_sdf,
377         const bool prune,
378         const float tolerance,
379         const bool activate_inside_sdf)
380 {
381     using namespace openvdb;
382 
383     UT_AutoInterrupt            progress("Converting to VDB");
384     UT_VoxelArrayReadHandleF    vox = vol.getVoxelHandle();
385 
386     float background;
387     if (vol.isSDF())
388     {
389         gu_VolumeMax max_op(vox, progress);
390         background = max_op.findMax();
391         if (progress.wasInterrupted())
392             return nullptr;
393     }
394     else
395     {
396         if (vol.getBorder() == GEO_VOLUMEBORDER_CONSTANT)
397             background = vol.getBorderValue();
398         else
399             background = 0.0;
400     }
401 
402     // When flood-filling SDFs, the inactive interior voxels will be set to
403     // -background.  In that case we can avoid activating all inside voxels
404     // that already have that value, maintaining the narrow band (if any) of the
405     // original native volume.  For non-SDF we always activate interior voxels.
406     gu_ConvertToVDB converter(vox, background, progress,
407                               activate_inside_sdf || !flood_sdf || !vol.isSDF());
408     FloatGrid::Ptr grid = converter.run();
409     if (progress.wasInterrupted())
410         return nullptr;
411 
412     if (vol.isSDF())
413         grid->setGridClass(GridClass(GRID_LEVEL_SET));
414     else
415         grid->setGridClass(GridClass(GRID_FOG_VOLUME));
416 
417     if (prune) {
418         grid->pruneGrid(tolerance);
419     }
420 
421     if (flood_sdf && vol.isSDF()) {
422         // only call signed flood fill on SDFs
423         openvdb::tools::signedFloodFill(grid->tree());
424     }
425 
426     GU_PrimVDB *prim_vdb = buildFromGrid(geo, grid, nullptr, name);
427     if (!prim_vdb)
428         return nullptr;
429     int rx, ry, rz;
430     vol.getRes(rx, ry, rz);
431     prim_vdb->setSpaceTransform(vol.getSpaceTransform(), UT_Vector3R(rx,ry,rz));
432     prim_vdb->setVisualization(
433                 vol.getVisualization(), vol.getVisIso(), vol.getVisDensity(),
434                 GEO_VOLUMEVISLOD_FULL);
435     return prim_vdb;
436 }
437 
438 // Copy the exclusive bbox [start,end) from myVox into acc
439 static void
guCopyVoxelBBox(const UT_VoxelArrayReadHandleF & vox,openvdb::FloatGrid::Accessor & acc,openvdb::Coord start,openvdb::Coord end)440 guCopyVoxelBBox(
441         const UT_VoxelArrayReadHandleF &vox,
442         openvdb::FloatGrid::Accessor &acc,
443         openvdb::Coord start, openvdb::Coord end)
444 {
445     openvdb::Coord c;
446     for (c[0] = start[0] ; c[0] < end[0]; c[0]++) {
447         for (c[1] = start[1] ; c[1] < end[1]; c[1]++) {
448             for (c[2] = start[2] ; c[2] < end[2]; c[2]++) {
449                 float value = vox->getValue(c[0], c[1], c[2]);
450                 acc.setValueOnly(c, value);
451             }
452         }
453     }
454 }
455 
456 void
expandBorderFromPrimVolume(const GEO_PrimVolume & vol,int pad)457 GU_PrimVDB::expandBorderFromPrimVolume(const GEO_PrimVolume &vol, int pad)
458 {
459     using namespace openvdb;
460 
461     UT_AutoInterrupt                progress("Add inactive VDB border");
462     const UT_VoxelArrayReadHandleF  vox(vol.getVoxelHandle());
463     const Coord                     res(vox->getXRes(),
464                                         vox->getYRes(),
465                                         vox->getZRes());
466     GridBase &                      base = getGrid();
467     FloatGrid &                     grid = UTvdbGridCast<FloatGrid>(base);
468     FloatGrid::Accessor             acc = grid.getAccessor();
469 
470     // For simplicity, we overdraw the edges and corners
471     for (int axis = 0; axis < 3; axis++) {
472 
473         if (progress.wasInterrupted())
474             return;
475 
476         openvdb::Coord beg(-pad, -pad, -pad);
477         openvdb::Coord end = res.offsetBy(+pad);
478 
479         beg[axis] = -pad;
480         end[axis] = 0;
481         guCopyVoxelBBox(vox, acc, beg, end);
482 
483         beg[axis] = res[axis];
484         end[axis] = res[axis] + pad;
485         guCopyVoxelBBox(vox, acc, beg, end);
486     }
487 }
488 
489 // The following code is for HDK only
490 #ifndef SESI_OPENVDB
491 // Static callback for our factory.
492 static GA_Primitive*
gu_newPrimVDB(GA_Detail & detail,GA_Offset offset,const GA_PrimitiveDefinition &)493 gu_newPrimVDB(GA_Detail &detail, GA_Offset offset,
494         const GA_PrimitiveDefinition &)
495 {
496     return new GU_PrimVDB(static_cast<GU_Detail *>(&detail), offset);
497 }
498 
499 static GA_Primitive*
gaPrimitiveMergeConstructor(const GA_MergeMap & map,GA_Detail & dest_detail,GA_Offset dest_offset,const GA_Primitive & src_prim)500 gaPrimitiveMergeConstructor(const GA_MergeMap &map,
501                             GA_Detail &dest_detail,
502                             GA_Offset dest_offset,
503                             const GA_Primitive &src_prim)
504 {
505     return new GU_PrimVDB(map, dest_detail, dest_offset, static_cast<const GU_PrimVDB &>(src_prim));
506 }
507 
508 static UT_Lock theInitPrimDefLock;
509 
510 void
registerMyself(GA_PrimitiveFactory * factory)511 GU_PrimVDB::registerMyself(GA_PrimitiveFactory *factory)
512 {
513     // Ignore double registration
514     if (theDefinition) return;
515 
516     UT_Lock::Scope lock(theInitPrimDefLock);
517 
518     if (theDefinition) return;
519 
520 #if defined(__ICC)
521     // Disable ICC "assignment to static variable" warning,
522     // since the assignment to theDefinition is mutex-protected.
523     __pragma(warning(disable:1711));
524 #endif
525 
526     theDefinition = factory->registerDefinition("VDB",
527         gu_newPrimVDB, GA_FAMILY_NONE);
528 
529 #if defined(__ICC)
530     __pragma(warning(default:1711));
531 #endif
532 
533     if (!theDefinition) {
534         std::cerr << "WARNING: Unable to register custom GU_PrimVDB\n";
535         if (!factory->lookupDefinition("VDB")) {
536             std::cerr << "WARNING: failed to register GU_PrimVDB\n";
537         }
538         return;
539     }
540 
541     theDefinition->setLabel("Sparse Volumes (VDBs)");
542     theDefinition->setHasLocalTransform(true);
543     theDefinition->setMergeConstructor(&gaPrimitiveMergeConstructor);
544     registerIntrinsics(*theDefinition);
545 
546     // Register the GT tesselation too (now we know what type id we have)
547     openvdb_houdini::GT_GEOPrimCollectVDB::registerPrimitive(theDefinition->getId());
548 }
549 #endif
550 
551 GEO_Primitive *
convertToNewPrim(GEO_Detail & dst_geo,GU_ConvertParms & parms,fpreal adaptivity,bool split_disjoint_volumes,bool & success) const552 GU_PrimVDB::convertToNewPrim(
553         GEO_Detail &dst_geo,
554         GU_ConvertParms &parms,
555         fpreal adaptivity,
556         bool split_disjoint_volumes,
557         bool &success) const
558 {
559     GEO_Primitive *     prim = nullptr;
560 
561     const GA_PrimCompat::TypeMask parmType = parms.toType();
562 
563     success = false;
564     if (parmType == GEO_PrimTypeCompat::GEOPRIMPOLY) {
565         prim = convertToPoly(dst_geo, parms, adaptivity,
566                              /*polysoup*/false, success);
567     } else if (parmType == GEO_PrimTypeCompat::GEOPRIMPOLYSOUP) {
568         prim = convertToPoly(dst_geo, parms, adaptivity,
569                              /*polysoup*/true, success);
570     } else if (parmType == GEO_PrimTypeCompat::GEOPRIMVOLUME) {
571         prim = convertToPrimVolume(dst_geo, parms, split_disjoint_volumes);
572         if (prim)
573             success = true;
574     }
575 
576     return prim;
577 }
578 
579 GEO_Primitive *
convertNew(GU_ConvertParms & parms)580 GU_PrimVDB::convertNew(GU_ConvertParms &parms)
581 {
582     bool success = false;
583     return convertToNewPrim(*getParent(), parms,
584                 /*adaptivity*/0, /*sparse*/false, success);
585 }
586 
587 static void
guCopyMesh(GEO_Detail & detail,openvdb::tools::VolumeToMesh & mesher,bool buildpolysoup,bool verbose)588 guCopyMesh(
589         GEO_Detail& detail,
590         openvdb::tools::VolumeToMesh& mesher,
591         bool buildpolysoup,
592         bool verbose)
593 {
594     TIMING_DEF;
595 
596     const openvdb::tools::PointList& points = mesher.pointList();
597     openvdb::tools::PolygonPoolList& polygonPoolList = mesher.polygonPoolList();
598 
599     // NOTE: Adaptive meshes consist of tringles and quads.
600 
601     // Construct the points
602     GA_Size npoints = mesher.pointListSize();
603     GA_Offset startpt = detail.appendPointBlock(npoints);
604     SYS_STATIC_ASSERT(sizeof(openvdb::tools::PointList::element_type) == sizeof(UT_Vector3));
605     GA_RWHandleV3 pthandle(detail.getP());
606     pthandle.setBlock(startpt, npoints, (UT_Vector3 *)points.get());
607 
608     TIMING_LOG("Copy Points");
609 
610     // Construct the array of polygon point numbers
611     // NOTE: For quad meshes, the number of quads is about the number of points,
612     //       so the number of vertices is about 4*npoints
613 
614     GA_Size nquads = 0, ntris = 0;
615     for (size_t n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) {
616         const openvdb::tools::PolygonPool& polygons = polygonPoolList[n];
617         nquads += polygons.numQuads();
618         ntris += polygons.numTriangles();
619     }
620 
621     TIMING_LOG("Count Quads and Tris");
622 
623     // Don't create anything if nothing to create
624     if (!ntris && !nquads)
625         return;
626 
627     GA_Size nverts = nquads*4 + ntris*3;
628     UT_IntArray verts(nverts, nverts);
629     GA_Size iquad = 0;
630     GA_Size itri = nquads*4;
631     for (size_t n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) {
632         const openvdb::tools::PolygonPool& polygons = polygonPoolList[n];
633 
634         // Copy quads
635         for (size_t i = 0, I = polygons.numQuads(); i < I; ++i) {
636             const openvdb::Vec4I& quad = polygons.quad(i);
637             verts(iquad++) = quad[0];
638             verts(iquad++) = quad[1];
639             verts(iquad++) = quad[2];
640             verts(iquad++) = quad[3];
641         }
642 
643         // Copy triangles (adaptive mesh)
644         for (size_t i = 0, I = polygons.numTriangles(); i < I; ++i) {
645             const openvdb::Vec3I& triangle = polygons.triangle(i);
646             verts(itri++) = triangle[0];
647             verts(itri++) = triangle[1];
648             verts(itri++) = triangle[2];
649         }
650     }
651 
652     TIMING_LOG("Get Quad and Tri Verts");
653 
654     GEO_PolyCounts sizelist;
655     if (nquads)
656         sizelist.append(4, nquads);
657     if (ntris)
658         sizelist.append(3, ntris);
659     if (buildpolysoup)
660         GU_PrimPolySoup::build(&detail, startpt, npoints, sizelist, verts.array());
661     else
662         GU_PrimPoly::buildBlock(&detail, startpt, npoints, sizelist, verts.array());
663 
664     TIMING_LOG("Build Polys");
665 }
666 
667 namespace {
668 class gu_VDBNormalsParallel
669 {
670 public:
gu_VDBNormalsParallel(GA_Attribute * p,GA_Attribute * n,const GU_PrimVDB & vdb)671     gu_VDBNormalsParallel(GA_Attribute *p, GA_Attribute *n, const GU_PrimVDB &vdb)
672         : myP(p)
673         , myN(n)
674         , myVDB(vdb)
675     {}
676 
operator ()(const GA_SplittableRange & r) const677     void operator()(const GA_SplittableRange &r) const
678     {
679         UT_Interrupt *boss = UTgetInterrupt();
680         GA_ROPageHandleV3 positions(myP);
681         GA_RWPageHandleV3 normals(myN);
682 
683         for (GA_PageIterator pit = r.beginPages(); !pit.atEnd(); ++pit)
684         {
685             if (boss->opInterrupt())
686                 break;
687 
688             const GA_Offset pagefirstoff = pit.getFirstOffsetInPage();
689             positions.setPage(pagefirstoff);
690             normals.setPage(pagefirstoff);
691             GA_Offset start;
692             GA_Offset end;
693             for (GA_Iterator it = pit.begin(); it.blockAdvance(start, end); )
694             {
695                 myVDB.evalGradients(&normals.value(start), 1,
696                                     &positions.value(start), end - start,
697                                     /*normalize*/true);
698             }
699         }
700     }
701 private:
702     GA_Attribute *const myP;
703     GA_Attribute *const myN;
704     const GU_PrimVDB &myVDB;
705 };
706 }
707 
708 GEO_Primitive *
convertToPoly(GEO_Detail & dst_geo,GU_ConvertParms & parms,fpreal adaptivity,bool polysoup,bool & success) const709 GU_PrimVDB::convertToPoly(
710         GEO_Detail &dst_geo,
711         GU_ConvertParms &parms,
712         fpreal adaptivity,
713         bool polysoup,
714         bool &success) const
715 {
716     using namespace openvdb;
717 
718     UT_AutoInterrupt    progress("Convert VDB to Polygons");
719     GA_Detail::OffsetMarker marker(dst_geo);
720     bool                verbose = false;
721 
722     success = false;
723 
724     try
725     {
726         tools::VolumeToMesh mesher(parms.myOffset, adaptivity);
727         UTvdbProcessTypedGridScalar(getStorageType(), getGrid(), mesher);
728         if (progress.wasInterrupted())
729             return nullptr;
730         guCopyMesh(dst_geo, mesher, polysoup, verbose);
731         if (progress.wasInterrupted())
732             return nullptr;
733     }
734     catch (std::exception& /*e*/)
735     {
736         return nullptr;
737     }
738     GA_Range pointrange(marker.pointRange());
739     GA_Range primitiverange(marker.primitiveRange());
740     GUconvertCopySingleVertexPrimAttribsAndGroups(
741             parms, *getParent(), getMapOffset(), dst_geo,
742             primitiverange, pointrange);
743     if (progress.wasInterrupted())
744         return nullptr;
745 
746     // If there was already a point normal attribute, we should compute normals
747     // to avoid getting zero default values for the new polygons.
748     GA_RWAttributeRef normal_ref = dst_geo.findNormalAttribute(GA_ATTRIB_POINT);
749     if (normal_ref.isValid() && !pointrange.isEmpty())
750     {
751         UTparallelFor(GA_SplittableRange(pointrange),
752                       gu_VDBNormalsParallel(dst_geo.getP(), normal_ref.getAttribute(), *this));
753         if (progress.wasInterrupted())
754             return nullptr;
755     }
756 
757     // At this point, we have succeeded, marker.numPrimitives() might be 0 if
758     // we had an empty VDB.
759     success = true;
760     if (primitiverange.isEmpty())
761         return nullptr;
762 
763     return dst_geo.getGEOPrimitive(marker.primitiveBegin());
764 }
765 
766 namespace {
767 class gu_DestroyVDBPrimGuard
768 {
769 public:
gu_DestroyVDBPrimGuard(GU_PrimVDB & vdb)770     gu_DestroyVDBPrimGuard(GU_PrimVDB &vdb)
771         : myVDB(vdb)
772     {
773     }
~gu_DestroyVDBPrimGuard()774     ~gu_DestroyVDBPrimGuard()
775     {
776         myVDB.getDetail().destroyPrimitive(myVDB, /*and_points*/true);
777     }
778 private:
779     GU_PrimVDB &myVDB;
780 };
781 } // anonymous namespace
782 
783 /*static*/ void
convertPrimVolumeToPolySoup(GU_Detail & dst_geo,const GEO_PrimVolume & src_vol)784 GU_PrimVDB::convertPrimVolumeToPolySoup(
785         GU_Detail &dst_geo,
786         const GEO_PrimVolume &src_vol)
787 {
788     using namespace openvdb;
789     UT_AutoInterrupt progress("Convert to Polygons");
790 
791     GU_PrimVDB &vdb = *buildFromPrimVolume(
792                             dst_geo, src_vol, nullptr,
793                             /*flood*/false, /*prune*/true, /*tol*/0,
794                             /*activate_inside*/true);
795     gu_DestroyVDBPrimGuard destroy_guard(vdb);
796 
797     if (progress.wasInterrupted())
798         return;
799 
800     try
801     {
802         BoolGrid::Ptr mask;
803         if (src_vol.getBorder() != GEO_VOLUMEBORDER_CONSTANT)
804         {
805             Coord res;
806             src_vol.getRes(res[0], res[1], res[2]);
807             CoordBBox bbox(Coord(0, 0, 0), res.offsetBy(-1)); // inclusive
808             if (bbox.hasVolume())
809             {
810                 vdb.expandBorderFromPrimVolume(src_vol, 4);
811                 if (progress.wasInterrupted())
812                     return;
813                 mask = BoolGrid::create(/*background*/false);
814                 mask->setTransform(vdb.getGrid().transform().copy());
815                 mask->fill(bbox, /*foreground*/true);
816             }
817         }
818 
819         tools::VolumeToMesh mesher(src_vol.getVisIso());
820         mesher.setSurfaceMask(mask);
821         GEOvdbProcessTypedGridScalar(vdb, mesher);
822         if (progress.wasInterrupted())
823             return;
824         guCopyMesh(dst_geo, mesher, /*polysoup*/true, /*verbose*/false);
825         if (progress.wasInterrupted())
826             return;
827     }
828     catch (std::exception& /*e*/)
829     {
830     }
831 }
832 
833 namespace // anonymous
834 {
835 
836 #define SCALAR_RET(T) \
837         typename SYS_EnableIf< SYS_IsArithmetic<T>::value, T >::type
838 
839 #define NON_SCALAR_RET(T) \
840         typename SYS_DisableIf< SYS_IsArithmetic<T>::value, T >::type
841 
842 /// Houdini Volume wrapper to abstract multiple volumes with a consistent API.
843 template <int TUPLE_SIZE>
844 class VoxelArrayVolume
845 {
846 public:
847     static const int TupleSize = TUPLE_SIZE;
848 
VoxelArrayVolume(GU_Detail & geo)849     VoxelArrayVolume(GU_Detail& geo): mGeo(geo)
850     {
851         for (int i = 0; i < TUPLE_SIZE; i++) {
852             mVol[i] = (GU_PrimVolume *)GU_PrimVolume::build(&mGeo);
853             mHandle[i] = mVol[i]->getVoxelWriteHandle();
854         }
855     }
856 
857     void
setSize(const openvdb::Coord & dim)858     setSize(const openvdb::Coord &dim)
859     {
860         for (int i = 0; i < TUPLE_SIZE; i++) {
861             mHandle[i]->size(dim[0], dim[1], dim[2]);
862         }
863     }
864 
865     template <class ValueT>
866     void
setVolumeOptions(bool is_sdf,const ValueT & background,GEO_VolumeVis vismode,fpreal iso,fpreal density,SCALAR_RET (ValueT)* =0)867     setVolumeOptions(
868             bool is_sdf, const ValueT& background,
869             GEO_VolumeVis vismode, fpreal iso, fpreal density,
870             SCALAR_RET(ValueT)* /*dummy*/ = 0)
871     {
872         if (is_sdf) {
873             mVol[0]->setBorder(GEO_VOLUMEBORDER_SDF, background);
874             mVol[0]->setVisualization(vismode, iso, density);
875         } else {
876             mVol[0]->setBorder(GEO_VOLUMEBORDER_CONSTANT, background);
877             mVol[0]->setVisualization(vismode, iso, density);
878         }
879     }
880     template <class ValueT>
881     void
setVolumeOptions(bool is_sdf,const ValueT & background,GEO_VolumeVis vismode,fpreal iso,fpreal density,NON_SCALAR_RET (ValueT)* =0)882     setVolumeOptions(
883             bool is_sdf, const ValueT& background,
884             GEO_VolumeVis vismode, fpreal iso, fpreal density,
885             NON_SCALAR_RET(ValueT)* /*dummy*/ = 0)
886     {
887         if (is_sdf) {
888             for (int i = 0; i < TUPLE_SIZE; i++) {
889                 mVol[i]->setBorder(GEO_VOLUMEBORDER_SDF, background[i]);
890                 mVol[i]->setVisualization(vismode, iso, density);
891             }
892         } else {
893             for (int i = 0; i < TUPLE_SIZE; i++) {
894                 mVol[i]->setBorder(GEO_VOLUMEBORDER_CONSTANT, background[i]);
895                 mVol[i]->setVisualization(vismode, iso, density);
896             }
897         }
898     }
899 
setSpaceTransform(const GEO_PrimVolumeXform & s)900     void setSpaceTransform(const GEO_PrimVolumeXform& s)
901     {
902         for (int i = 0; i < TUPLE_SIZE; i++)
903             mVol[i]->setSpaceTransform(s);
904     }
905 
906     int
numTiles() const907     numTiles() const
908     {
909         // Since we create all volumes the same size, we can simply use the
910         // first one.
911         return mHandle[0]->numTiles();
912     }
913 
914     template<typename ConstAccessorT>
915     void copyToAlignedTile(
916             int tile_index,
917             ConstAccessorT& src,
918             const openvdb::Coord& src_origin);
919 
920     template<typename ConstAccessorT>
921     void copyToTile(
922             int tile_index,
923             ConstAccessorT& src,
924             const openvdb::Coord& src_origin);
925 
926 private: // methods
927 
928     using VoxelTileF = UT_VoxelTile<fpreal32>;
929 
930     template <class ValueT>
931     static void
makeConstant_(VoxelTileF * tiles[TUPLE_SIZE],const ValueT & v,SCALAR_RET (ValueT)* =0)932     makeConstant_(VoxelTileF* tiles[TUPLE_SIZE], const ValueT& v,
933                  SCALAR_RET(ValueT)* /*dummy*/ = 0)
934     {
935         tiles[0]->makeConstant(fpreal32(v));
936     }
937     template <class ValueT>
938     static void
makeConstant_(VoxelTileF * tiles[TUPLE_SIZE],const ValueT & v,NON_SCALAR_RET (ValueT)* =0)939     makeConstant_(VoxelTileF* tiles[TUPLE_SIZE], const ValueT& v,
940                  NON_SCALAR_RET(ValueT)* /*dummy*/ = 0)
941     {
942         for (int i = 0; i < TUPLE_SIZE; i++)
943             tiles[i]->makeConstant(fpreal32(v[i]));
944     }
945 
946     // Convert a local tile coordinate to a linear offset. This is used instead
947     // of UT_VoxelTile::operator()() since we always decompress the tile first.
948     SYS_FORCE_INLINE static int
tileCoordToOffset(const VoxelTileF * tile,const openvdb::Coord & xyz)949     tileCoordToOffset(const VoxelTileF* tile, const openvdb::Coord& xyz)
950     {
951         UT_ASSERT_P(xyz[0] >= 0 && xyz[0] < tile->xres());
952         UT_ASSERT_P(xyz[1] >= 0 && xyz[1] < tile->yres());
953         UT_ASSERT_P(xyz[2] >= 0 && xyz[2] < tile->zres());
954         return ((xyz[2] * tile->yres()) + xyz[1]) * tile->xres() + xyz[0];
955     }
956 
957     // Set the value into tile coord xyz
958     template <class ValueT>
959     static void
setTileVoxel(const openvdb::Coord & xyz,VoxelTileF * tile,fpreal32 * rawData,const ValueT & v,int,SCALAR_RET (ValueT)* =0)960     setTileVoxel(
961             const openvdb::Coord& xyz,
962             VoxelTileF* tile,
963             fpreal32* rawData,
964             const ValueT& v,
965             int /*i*/,
966             SCALAR_RET(ValueT)* /*dummy*/ = 0)
967     {
968         rawData[tileCoordToOffset(tile, xyz)] = v;
969     }
970     template <class ValueT>
971     static void
setTileVoxel(const openvdb::Coord & xyz,VoxelTileF * tile,fpreal32 * rawData,const ValueT & v,int i,NON_SCALAR_RET (ValueT)* =0)972     setTileVoxel(
973             const openvdb::Coord& xyz,
974             VoxelTileF* tile,
975             fpreal32* rawData,
976             const ValueT& v,
977             int i,
978             NON_SCALAR_RET(ValueT)* /*dummy*/ = 0)
979     {
980         rawData[tileCoordToOffset(tile, xyz)] = v[i];
981     }
982 
983     template <class ValueT>
984     static bool
compareVoxel(const openvdb::Coord & xyz,VoxelTileF * tile,fpreal32 * rawData,const ValueT & v,int i,SCALAR_RET (ValueT)* dummy=0)985     compareVoxel(
986             const openvdb::Coord& xyz,
987             VoxelTileF* tile,
988             fpreal32* rawData,
989             const ValueT& v,
990             int i,
991             SCALAR_RET(ValueT) *dummy = 0)
992     {
993         UT_ASSERT_P(xyz[0] >= 0 && xyz[0] < tile->xres());
994         UT_ASSERT_P(xyz[1] >= 0 && xyz[1] < tile->yres());
995         UT_ASSERT_P(xyz[2] >= 0 && xyz[2] < tile->zres());
996         float vox = (*tile)(xyz[0], xyz[1], xyz[2]);
997         return openvdb::math::isApproxEqual<float>(vox, v);
998     }
999     template <class ValueT>
1000     static bool
compareVoxel(const openvdb::Coord & xyz,VoxelTileF * tile,fpreal32 * rawData,const ValueT & v,int i,NON_SCALAR_RET (ValueT)* dummy=0)1001     compareVoxel(
1002             const openvdb::Coord& xyz,
1003             VoxelTileF* tile,
1004             fpreal32* rawData,
1005             const ValueT& v,
1006             int i,
1007             NON_SCALAR_RET(ValueT) *dummy = 0)
1008     {
1009         UT_ASSERT_P(xyz[0] >= 0 && xyz[0] < tile->xres());
1010         UT_ASSERT_P(xyz[1] >= 0 && xyz[1] < tile->yres());
1011         UT_ASSERT_P(xyz[2] >= 0 && xyz[2] < tile->zres());
1012         float vox = (*tile)(xyz[0], xyz[1], xyz[2]);
1013         return openvdb::math::isApproxEqual<float>(vox, v[i]);
1014     }
1015 
1016     // Check if aligned VDB bbox region is constant
1017     template<typename ConstAccessorT, typename ValueType>
1018     static bool
isAlignedConstantRegion_(ConstAccessorT & acc,const openvdb::Coord & beg,const openvdb::Coord & end,const ValueType & const_value)1019     isAlignedConstantRegion_(
1020             ConstAccessorT& acc,
1021             const openvdb::Coord& beg,
1022             const openvdb::Coord& end,
1023             const ValueType& const_value)
1024     {
1025         using openvdb::math::isApproxEqual;
1026 
1027         using LeafNodeType = typename ConstAccessorT::LeafNodeT;
1028         const openvdb::Index DIM = LeafNodeType::DIM;
1029 
1030         // The smallest constant tile size in vdb is DIM and the
1031         // vdb-leaf/hdk-tile coords are aligned.
1032         openvdb::Coord ijk;
1033         for (ijk[0] = beg[0]; ijk[0] < end[0]; ijk[0] += DIM) {
1034             for (ijk[1] = beg[1]; ijk[1] < end[1]; ijk[1] += DIM) {
1035                 for (ijk[2] = beg[2]; ijk[2] < end[2]; ijk[2] += DIM) {
1036 
1037                     if (acc.probeConstLeaf(ijk) != nullptr)
1038                         return false;
1039 
1040                     ValueType sampleValue = acc.getValue(ijk);
1041                     if (!isApproxEqual(const_value, sampleValue))
1042                         return false;
1043                 }
1044             }
1045         }
1046         return true;
1047     }
1048 
1049     // Copy all data of the aligned leaf node to the tile at origin
1050     template<typename LeafType>
1051     static void
copyAlignedLeafNode_(VoxelTileF * tile,int tuple_i,const openvdb::Coord & origin,const LeafType & leaf)1052     copyAlignedLeafNode_(
1053             VoxelTileF* tile,
1054             int tuple_i,
1055             const openvdb::Coord& origin,
1056             const LeafType& leaf)
1057     {
1058         fpreal32* data = tile->rawData();
1059         for (openvdb::Index i = 0; i < LeafType::NUM_VALUES; ++i) {
1060             openvdb::Coord xyz = origin + LeafType::offsetToLocalCoord(i);
1061             setTileVoxel(xyz, tile, data, leaf.getValue(i), tuple_i);
1062         }
1063     }
1064 
1065     // Check if unaligned VDB bbox region is constant.
1066     // beg_a is beg rounded down to the nearest leaf origin.
1067     template<typename ConstAccessorT, typename ValueType>
1068     static bool
isConstantRegion_(ConstAccessorT & acc,const openvdb::Coord & beg,const openvdb::Coord & end,const openvdb::Coord & beg_a,const ValueType & const_value)1069     isConstantRegion_(
1070             ConstAccessorT& acc,
1071             const openvdb::Coord& beg,
1072             const openvdb::Coord& end,
1073             const openvdb::Coord& beg_a,
1074             const ValueType& const_value)
1075     {
1076         using openvdb::math::isApproxEqual;
1077 
1078         using LeafNodeType = typename ConstAccessorT::LeafNodeT;
1079         const openvdb::Index DIM = LeafNodeType::DIM;
1080         const openvdb::Index LOG2DIM = LeafNodeType::LOG2DIM;
1081 
1082         UT_ASSERT(beg_a[0] % DIM == 0);
1083         UT_ASSERT(beg_a[1] % DIM == 0);
1084         UT_ASSERT(beg_a[2] % DIM == 0);
1085 
1086         openvdb::Coord ijk;
1087         for (ijk[0] = beg_a[0]; ijk[0] < end[0]; ijk[0] += DIM) {
1088             for (ijk[1] = beg_a[1]; ijk[1] < end[1]; ijk[1] += DIM) {
1089                 for (ijk[2] = beg_a[2]; ijk[2] < end[2]; ijk[2] += DIM) {
1090 
1091                     const LeafNodeType* leaf = acc.probeConstLeaf(ijk);
1092                     if (!leaf)
1093                     {
1094                         ValueType sampleValue = acc.getValue(ijk);
1095                         if (!isApproxEqual(const_value, sampleValue))
1096                             return false;
1097                         continue;
1098                     }
1099 
1100                     // Else, we're a leaf node, determine if region is constant
1101                     openvdb::Coord leaf_beg = ijk;
1102                     openvdb::Coord leaf_end = ijk + openvdb::Coord(DIM, DIM, DIM);
1103 
1104                     // Clamp the leaf region to the tile bbox
1105                     leaf_beg.maxComponent(beg);
1106                     leaf_end.minComponent(end);
1107 
1108                     // Offset into local leaf coordinates
1109                     leaf_beg -= leaf->origin();
1110                     leaf_end -= leaf->origin();
1111 
1112                     const ValueType* s0 = &leaf->getValue(leaf_beg[2]);
1113                     for (openvdb::Int32 x = leaf_beg[0]; x < leaf_end[0]; ++x) {
1114                         const ValueType* s1 = s0 + (x<<2*LOG2DIM);
1115                         for (openvdb::Int32 y = leaf_beg[1]; y < leaf_end[1]; ++y) {
1116                             const ValueType* s2 = s1 + (y<<LOG2DIM);
1117                             for (openvdb::Int32 z = leaf_beg[2]; z < leaf_end[2]; ++z) {
1118                                 if (!isApproxEqual(const_value, *s2))
1119                                     return false;
1120                                 s2++;
1121                             }
1122                         }
1123                     }
1124                 }
1125             }
1126         }
1127         return true;
1128     }
1129 
1130     // Copy the leaf node data at the global coord leaf_origin to the local
1131     // tile region [beg,end)
1132     template<typename LeafType>
1133     static void
copyLeafNode_(VoxelTileF * tile,int tuple_i,const openvdb::Coord & beg,const openvdb::Coord & end,const openvdb::Coord & leaf_origin,const LeafType & leaf)1134     copyLeafNode_(
1135             VoxelTileF* tile,
1136             int tuple_i,
1137             const openvdb::Coord& beg,
1138             const openvdb::Coord& end,
1139             const openvdb::Coord& leaf_origin,
1140             const LeafType& leaf)
1141     {
1142         using openvdb::Coord;
1143 
1144         fpreal32* data = tile->rawData();
1145 
1146         Coord xyz;
1147         Coord ijk = leaf_origin;
1148         for (xyz[2] = beg[2]; xyz[2] < end[2]; ++xyz[2], ++ijk[2]) {
1149             ijk[1] = leaf_origin[1];
1150             for (xyz[1] = beg[1]; xyz[1] < end[1]; ++xyz[1], ++ijk[1]) {
1151                 ijk[0] = leaf_origin[0];
1152                 for (xyz[0] = beg[0]; xyz[0] < end[0]; ++xyz[0], ++ijk[0]) {
1153                     setTileVoxel(xyz, tile, data, leaf.getValue(ijk), tuple_i);
1154                 }
1155             }
1156         }
1157     }
1158 
1159     // Set the local tile region [beg,end) to the same value.
1160     template <class ValueT>
1161     static void
setConstantRegion_(VoxelTileF * tile,int tuple_i,const openvdb::Coord & beg,const openvdb::Coord & end,const ValueT & value)1162     setConstantRegion_(
1163             VoxelTileF* tile,
1164             int tuple_i,
1165             const openvdb::Coord& beg,
1166             const openvdb::Coord& end,
1167             const ValueT& value)
1168     {
1169         fpreal32* data = tile->rawData();
1170         openvdb::Coord xyz;
1171         for (xyz[2] = beg[2]; xyz[2] < end[2]; ++xyz[2]) {
1172             for (xyz[1] = beg[1]; xyz[1] < end[1]; ++xyz[1]) {
1173                 for (xyz[0] = beg[0]; xyz[0] < end[0]; ++xyz[0]) {
1174                     setTileVoxel(xyz, tile, data, value, tuple_i);
1175                 }
1176             }
1177         }
1178     }
1179 
1180     void
getTileCopyData_(int tile_index,const openvdb::Coord & src_origin,VoxelTileF * tiles[TUPLE_SIZE],openvdb::Coord & res,openvdb::Coord & src_bbox_beg,openvdb::Coord & src_bbox_end)1181     getTileCopyData_(
1182             int tile_index,
1183             const openvdb::Coord& src_origin,
1184             VoxelTileF* tiles[TUPLE_SIZE],
1185             openvdb::Coord& res,
1186             openvdb::Coord& src_bbox_beg,
1187             openvdb::Coord& src_bbox_end)
1188     {
1189         for (int i = 0; i < TUPLE_SIZE; i++) {
1190             tiles[i] = mHandle[i]->getLinearTile(tile_index);
1191         }
1192 
1193         // Since all tiles are the same size, so just use the first one.
1194         res[0] = tiles[0]->xres();
1195         res[1] = tiles[0]->yres();
1196         res[2] = tiles[0]->zres();
1197 
1198         // Define the inclusive coordinate range, in vdb index space.
1199         // ie. The source bounding box that we will copy from.
1200         // NOTE: All tiles are the same size, so just use the first handle.
1201         openvdb::Coord dst;
1202         mHandle[0]->linearTileToXYZ(tile_index, dst.x(), dst.y(), dst.z());
1203         dst.x() *= TILESIZE;
1204         dst.y() *= TILESIZE;
1205         dst.z() *= TILESIZE;
1206         src_bbox_beg = src_origin + dst;
1207         src_bbox_end = src_bbox_beg + res;
1208     }
1209 
1210 private: // data
1211 
1212     GU_Detail& mGeo;
1213     GU_PrimVolume *mVol[TUPLE_SIZE];
1214     UT_VoxelArrayWriteHandleF mHandle[TUPLE_SIZE];
1215 };
1216 
1217 // Copy the vdb data to the current tile. Assumes full tiles.
1218 template<int TUPLE_SIZE>
1219 template<typename ConstAccessorT>
1220 inline void
copyToAlignedTile(int tile_index,ConstAccessorT & acc,const openvdb::Coord & src_origin)1221 VoxelArrayVolume<TUPLE_SIZE>::copyToAlignedTile(
1222         int tile_index, ConstAccessorT &acc, const openvdb::Coord& src_origin)
1223 {
1224     using openvdb::Coord;
1225     using openvdb::CoordBBox;
1226 
1227     using ValueType = typename ConstAccessorT::ValueType;
1228     using LeafNodeType = typename ConstAccessorT::LeafNodeT;
1229     const openvdb::Index LEAF_DIM = LeafNodeType::DIM;
1230 
1231     VoxelTileF* tiles[TUPLE_SIZE];
1232     Coord tile_res;
1233     Coord beg;
1234     Coord end;
1235 
1236     getTileCopyData_(tile_index, src_origin, tiles, tile_res, beg, end);
1237 
1238     ValueType const_value = acc.getValue(beg);
1239     if (isAlignedConstantRegion_(acc, beg, end, const_value)) {
1240 
1241         makeConstant_(tiles, const_value);
1242 
1243     } else { // populate dense tile
1244 
1245         for (int tuple_i = 0; tuple_i < TUPLE_SIZE; tuple_i++) {
1246 
1247             VoxelTileF* tile = tiles[tuple_i];
1248             tile->makeRawUninitialized();
1249 
1250             Coord ijk;
1251             for (ijk[0] = beg[0]; ijk[0] < end[0]; ijk[0] += LEAF_DIM) {
1252                 for (ijk[1] = beg[1]; ijk[1] < end[1]; ijk[1] += LEAF_DIM) {
1253                     for (ijk[2] = beg[2]; ijk[2] < end[2]; ijk[2] += LEAF_DIM) {
1254 
1255                         Coord tile_beg = ijk - beg; // local tile coord
1256                         Coord tile_end = tile_beg.offsetBy(LEAF_DIM);
1257 
1258                         const LeafNodeType* leaf = acc.probeConstLeaf(ijk);
1259                         if (leaf != nullptr) {
1260                             copyAlignedLeafNode_(
1261                                     tile, tuple_i, tile_beg, *leaf);
1262                         } else {
1263                             setConstantRegion_(
1264                                     tile, tuple_i, tile_beg, tile_end,
1265                                     acc.getValue(ijk));
1266                         }
1267                     }
1268                 }
1269             }
1270         }
1271 
1272     } // end populate dense tile
1273 }
1274 
1275 template<int TUPLE_SIZE>
1276 template<typename ConstAccessorT>
1277 inline void
copyToTile(int tile_index,ConstAccessorT & acc,const openvdb::Coord & src_origin)1278 VoxelArrayVolume<TUPLE_SIZE>::copyToTile(
1279         int tile_index, ConstAccessorT &acc, const openvdb::Coord& src_origin)
1280 {
1281     using openvdb::Coord;
1282     using openvdb::CoordBBox;
1283 
1284     using ValueType = typename ConstAccessorT::ValueType;
1285     using LeafNodeType = typename ConstAccessorT::LeafNodeT;
1286     const openvdb::Index DIM = LeafNodeType::DIM;
1287 
1288     VoxelTileF* tiles[TUPLE_SIZE];
1289     Coord tile_res;
1290     Coord beg;
1291     Coord end;
1292 
1293     getTileCopyData_(tile_index, src_origin, tiles, tile_res, beg, end);
1294 
1295     // a_beg is beg rounded down to the nearest leaf origin
1296     Coord a_beg(beg[0]&~(DIM-1), beg[1]&~(DIM-1), beg[2]&~(DIM-1));
1297 
1298     ValueType const_value = acc.getValue(a_beg);
1299     if (isConstantRegion_(acc, beg, end, a_beg, const_value)) {
1300 
1301         makeConstant_(tiles, const_value);
1302 
1303     } else {
1304 
1305         for (int tuple_i = 0; tuple_i < TUPLE_SIZE; tuple_i++) {
1306 
1307             VoxelTileF* tile = tiles[tuple_i];
1308             tile->makeRawUninitialized();
1309 
1310             Coord ijk;
1311             for (ijk[0] = a_beg[0]; ijk[0] < end[0]; ijk[0] += DIM) {
1312                 for (ijk[1] = a_beg[1]; ijk[1] < end[1]; ijk[1] += DIM) {
1313                     for (ijk[2] = a_beg[2]; ijk[2] < end[2]; ijk[2] += DIM) {
1314 
1315                         // Compute clamped local tile coord bbox
1316                         Coord leaf_beg = ijk;
1317                         Coord tile_beg = ijk - beg;
1318                         Coord tile_end = tile_beg.offsetBy(DIM);
1319                         for (int axis = 0; axis < 3; ++axis) {
1320                             if (tile_beg[axis] < 0) {
1321                                 tile_beg[axis] = 0;
1322                                 leaf_beg[axis] = beg[axis];
1323                             }
1324                             if (tile_end[axis] > tile_res[axis])
1325                                 tile_end[axis] = tile_res[axis];
1326                         }
1327 
1328                         // Copy the region
1329                         const LeafNodeType* leaf = acc.probeConstLeaf(leaf_beg);
1330                         if (leaf != nullptr) {
1331                             copyLeafNode_(
1332                                     tile, tuple_i, tile_beg, tile_end,
1333                                     leaf_beg, *leaf);
1334                         } else {
1335                             setConstantRegion_(
1336                                     tile, tuple_i, tile_beg, tile_end,
1337                                     acc.getValue(ijk));
1338                         }
1339                     }
1340                 }
1341             }
1342         }
1343     }
1344 
1345     // Enable this to do slow code path verification
1346 #if 0
1347     for (int tuple_i = 0; tuple_i < TUPLE_SIZE; ++tuple_i) {
1348         VoxelTileF* tile = tiles[tuple_i];
1349         fpreal32* data = tile->rawData();
1350         Coord xyz;
1351         for (xyz[2] = 0; xyz[2] < tile_res[2]; ++xyz[2]) {
1352             for (xyz[1] = 0; xyz[1] < tile_res[1]; ++xyz[1]) {
1353                 for (xyz[0] = 0; xyz[0] < tile_res[0]; ++xyz[0]) {
1354                     Coord ijk = beg + xyz;
1355                     if (!compareVoxel(xyz, tile, data,
1356                                       acc.getValue(ijk), tuple_i)) {
1357                         UT_ASSERT(!"Voxels are different");
1358                         compareVoxel(xyz, tile, data,
1359                                      acc.getValue(ijk), tuple_i);
1360                     }
1361                 }
1362             }
1363         }
1364     }
1365 #endif
1366 }
1367 
1368 template<typename TreeType, typename VolumeT, bool aligned>
1369 class gu_SparseTreeCopy;
1370 
1371 template<typename TreeType, typename VolumeT>
1372 class gu_SparseTreeCopy<TreeType, VolumeT, /*aligned=*/true>
1373 {
1374 public:
gu_SparseTreeCopy(const TreeType & tree,VolumeT & volume,const openvdb::Coord & src_origin,UT_AutoInterrupt & progress)1375     gu_SparseTreeCopy(
1376             const TreeType& tree,
1377             VolumeT& volume,
1378             const openvdb::Coord& src_origin,
1379             UT_AutoInterrupt& progress
1380         )
1381         : mVdbAcc(tree)
1382         , mVolume(volume)
1383         , mSrcOrigin(src_origin)
1384         , mProgress(progress)
1385     {
1386     }
1387 
run(bool threaded=true)1388     void run(bool threaded = true)
1389     {
1390         tbb::blocked_range<int> range(0, mVolume.numTiles());
1391         if (threaded) tbb::parallel_for(range, *this);
1392         else (*this)(range);
1393     }
1394 
operator ()(const tbb::blocked_range<int> & range) const1395     void operator()(const tbb::blocked_range<int>& range) const
1396     {
1397         uint8 bcnt = 0;
1398         for (int i = range.begin(); i != range.end(); ++i) {
1399             mVolume.copyToAlignedTile(i, mVdbAcc, mSrcOrigin);
1400             if (!bcnt++ && mProgress.wasInterrupted())
1401                 return;
1402         }
1403     }
1404 
1405 private:
1406     openvdb::tree::ValueAccessor<const TreeType> mVdbAcc;
1407     VolumeT& mVolume;
1408     const openvdb::Coord mSrcOrigin;
1409     UT_AutoInterrupt& mProgress;
1410 };
1411 
1412 template<typename TreeType, typename VolumeT>
1413 class gu_SparseTreeCopy<TreeType, VolumeT, /*aligned=*/false>
1414 {
1415 public:
gu_SparseTreeCopy(const TreeType & tree,VolumeT & volume,const openvdb::Coord & src_origin,UT_AutoInterrupt & progress)1416     gu_SparseTreeCopy(
1417             const TreeType& tree,
1418             VolumeT& volume,
1419             const openvdb::Coord& src_origin,
1420             UT_AutoInterrupt& progress
1421         )
1422         : mVdbAcc(tree)
1423         , mVolume(volume)
1424         , mSrcOrigin(src_origin)
1425         , mProgress(progress)
1426     {
1427     }
1428 
run(bool threaded=true)1429     void run(bool threaded = true)
1430     {
1431         tbb::blocked_range<int> range(0, mVolume.numTiles());
1432         if (threaded) tbb::parallel_for(range, *this);
1433         else (*this)(range);
1434     }
1435 
operator ()(const tbb::blocked_range<int> & range) const1436     void operator()(const tbb::blocked_range<int>& range) const
1437     {
1438         uint8 bcnt = 0;
1439         for (int i = range.begin(); i != range.end(); ++i) {
1440             mVolume.copyToTile(i, mVdbAcc, mSrcOrigin);
1441             if (!bcnt++ && mProgress.wasInterrupted())
1442                 return;
1443         }
1444     }
1445 
1446 private:
1447     openvdb::tree::ValueAccessor<const TreeType> mVdbAcc;
1448     VolumeT& mVolume;
1449     const openvdb::Coord mSrcOrigin;
1450     UT_AutoInterrupt& mProgress;
1451 };
1452 
1453 /// @brief Converts an OpenVDB grid into one/three Houdini Volume.
1454 /// @note Vector grids are converted into three Houdini Volumes.
1455 template <class VolumeT>
1456 class gu_ConvertFromVDB
1457 {
1458 public:
1459 
gu_ConvertFromVDB(GEO_Detail & dst_geo,const GU_PrimVDB & src_vdb,bool split_disjoint_volumes,UT_AutoInterrupt & progress)1460     gu_ConvertFromVDB(
1461             GEO_Detail& dst_geo,
1462             const GU_PrimVDB& src_vdb,
1463             bool split_disjoint_volumes,
1464             UT_AutoInterrupt& progress)
1465         : mDstGeo(static_cast<GU_Detail&>(dst_geo))
1466         , mSrcVDB(src_vdb)
1467         , mSplitDisjoint(split_disjoint_volumes)
1468         , mProgress(progress)
1469     {
1470     }
1471 
1472     template<typename GridT>
operator ()(const GridT & grid)1473     void operator()(const GridT &grid)
1474     {
1475         if (mSplitDisjoint) {
1476             vdbToDisjointVolumes(grid);
1477         } else {
1478             using LeafNodeType = typename GridT::TreeType::LeafNodeType;
1479             const openvdb::Index LEAF_DIM = LeafNodeType::DIM;
1480 
1481             VolumeT volume(mDstGeo);
1482             openvdb::CoordBBox bbox(grid.evalActiveVoxelBoundingBox());
1483             bool aligned = (   (bbox.min()[0] % LEAF_DIM) == 0
1484                             && (bbox.min()[1] % LEAF_DIM) == 0
1485                             && (bbox.min()[2] % LEAF_DIM) == 0
1486                             && ((bbox.max()[0]+1) % LEAF_DIM) == 0
1487                             && ((bbox.max()[1]+1) % LEAF_DIM) == 0
1488                             && ((bbox.max()[2]+1) % LEAF_DIM) == 0);
1489             vdbToVolume(grid, bbox, volume, aligned);
1490         }
1491     }
1492 
components() const1493     const UT_IntArray& components() const
1494     {
1495         return mDstComponents;
1496     }
1497 
1498 private:
1499 
1500     template<typename GridType>
1501     void vdbToVolume(const GridType& grid, const openvdb::CoordBBox& bbox,
1502                      VolumeT& volume, bool aligned);
1503 
1504     template<typename GridType>
1505     void vdbToDisjointVolumes(const GridType& grid);
1506 
1507 private:
1508     GU_Detail&          mDstGeo;
1509     UT_IntArray         mDstComponents;
1510     const GU_PrimVDB&   mSrcVDB;
1511     bool                mSplitDisjoint;
1512     UT_AutoInterrupt&   mProgress;
1513 };
1514 
1515 // Copy the grid's bbox into volume at (0,0,0)
1516 template<typename VolumeT>
1517 template<typename GridType>
1518 void
vdbToVolume(const GridType & grid,const openvdb::CoordBBox & bbox,VolumeT & vol,bool aligned)1519 gu_ConvertFromVDB<VolumeT>::vdbToVolume(
1520         const GridType& grid,
1521         const openvdb::CoordBBox& bbox,
1522         VolumeT& vol,
1523         bool aligned)
1524 {
1525     using LeafNodeType = typename GridType::TreeType::LeafNodeType;
1526 
1527     // Creating a Houdini volume with a zero bbox seems to break the transform.
1528     // (probably related to the bbox derived 'local space')
1529     openvdb::CoordBBox space_bbox = bbox;
1530     if (space_bbox.empty())
1531         space_bbox.resetToCube(openvdb::Coord(0, 0, 0), 1);
1532     vol.setSize(space_bbox.dim());
1533 
1534     vol.setVolumeOptions(mSrcVDB.isSDF(), grid.background(),
1535                          mSrcVDB.getVisualization(), mSrcVDB.getVisIso(),
1536                          mSrcVDB.getVisDensity());
1537     vol.setSpaceTransform(mSrcVDB.getSpaceTransform(UTvdbConvert(space_bbox)));
1538 
1539     for (int i = 0; i < VolumeT::TupleSize; i++)
1540         mDstComponents.append(i);
1541 
1542     // Copy the VDB bbox data to voxel array coord (0,0,0).
1543     SYS_STATIC_ASSERT(LeafNodeType::DIM <= TILESIZE);
1544     SYS_STATIC_ASSERT((TILESIZE % LeafNodeType::DIM) == 0);
1545     if (aligned) {
1546         gu_SparseTreeCopy<typename GridType::TreeType, VolumeT, true>
1547             copy(grid.tree(), vol, space_bbox.min(), mProgress);
1548         copy.run();
1549     } else {
1550         gu_SparseTreeCopy<typename GridType::TreeType, VolumeT, false>
1551             copy(grid.tree(), vol, space_bbox.min(), mProgress);
1552         copy.run();
1553     }
1554 }
1555 
1556 template<typename VolumeT>
1557 template<typename GridType>
1558 void
vdbToDisjointVolumes(const GridType & grid)1559 gu_ConvertFromVDB<VolumeT>::vdbToDisjointVolumes(const GridType& grid)
1560 {
1561     using TreeType = typename GridType::TreeType;
1562     using NodeType = typename TreeType::RootNodeType::ChildNodeType;
1563 
1564     std::vector<const NodeType*> nodes;
1565 
1566     typename TreeType::NodeCIter iter = grid.tree().cbeginNode();
1567     iter.setMaxDepth(1);
1568     iter.setMinDepth(1);
1569     for (; iter; ++iter) {
1570         const NodeType* node = nullptr;
1571         iter.template getNode<const NodeType>(node);
1572         if (node) nodes.push_back(node);
1573     }
1574 
1575     std::vector<openvdb::CoordBBox> nodeBBox(nodes.size());
1576     for (size_t n = 0, N = nodes.size(); n < N; ++n) {
1577         nodes[n]->evalActiveBoundingBox(nodeBBox[n], false);
1578     }
1579 
1580     openvdb::CoordBBox regionA, regionB;
1581 
1582     const int searchDist = int(GridType::TreeType::LeafNodeType::DIM) << 1;
1583 
1584     for (size_t n = 0, N = nodes.size(); n < N; ++n) {
1585         if (!nodes[n]) continue;
1586 
1587         openvdb::CoordBBox& bbox = nodeBBox[n];
1588 
1589         regionA = bbox;
1590         regionA.max().offset(searchDist);
1591 
1592         bool expanded = true;
1593 
1594         while (expanded) {
1595 
1596             expanded = false;
1597 
1598             for (size_t i = (n + 1); i < N; ++i) {
1599 
1600                 if (!nodes[i]) continue;
1601 
1602                 regionB = nodeBBox[i];
1603                 regionB.max().offset(searchDist);
1604 
1605                 if (regionA.hasOverlap(regionB)) {
1606 
1607                     nodes[i] = nullptr;
1608                     expanded = true;
1609 
1610                     bbox.expand(nodeBBox[i]);
1611 
1612                     regionA = bbox;
1613                     regionA.max().offset(searchDist);
1614                 }
1615             }
1616         }
1617 
1618         VolumeT volume(mDstGeo);
1619         vdbToVolume(grid, bbox, volume, /*aligned*/true);
1620     }
1621 }
1622 
1623 } // namespace anonymous
1624 
1625 GEO_Primitive *
convertToPrimVolume(GEO_Detail & dst_geo,GU_ConvertParms & parms,bool split_disjoint_volumes) const1626 GU_PrimVDB::convertToPrimVolume(
1627         GEO_Detail &dst_geo,
1628         GU_ConvertParms &parms,
1629         bool split_disjoint_volumes) const
1630 {
1631     using namespace openvdb;
1632 
1633     UT_AutoInterrupt    progress("Convert VDB to Volume");
1634     GA_Detail::OffsetMarker marker(dst_geo);
1635     UT_IntArray         dst_components;
1636 
1637     bool processed = false;
1638     { // Try to convert scalar grid
1639         gu_ConvertFromVDB< VoxelArrayVolume<1> >
1640             converter(dst_geo, *this, split_disjoint_volumes, progress);
1641         processed = GEOvdbProcessTypedGridScalar(*this, converter);
1642     }
1643     if (!processed) {  // Try to convert vector grid
1644         gu_ConvertFromVDB< VoxelArrayVolume<3> >
1645             converter(dst_geo, *this, split_disjoint_volumes, progress);
1646         processed = GEOvdbProcessTypedGridVec3(*this, converter);
1647         dst_components = converter.components();
1648     }
1649 
1650     // Copy attributes from source to dest primitives
1651     GA_Range pointrange(marker.pointRange());
1652     GA_Range primitiverange(marker.primitiveRange());
1653     if (!processed || primitiverange.isEmpty() || progress.wasInterrupted())
1654         return nullptr;
1655 
1656     GUconvertCopySingleVertexPrimAttribsAndGroups(
1657             parms, *getParent(), getMapOffset(),
1658             dst_geo, primitiverange, pointrange);
1659 
1660     // Handle the name attribute if needed
1661     if (dst_components.entries() > 0)
1662     {
1663         GA_ROHandleS src_name(getParent(), GA_ATTRIB_PRIMITIVE, "name");
1664         GA_RWHandleS dst_name(&dst_geo, GA_ATTRIB_PRIMITIVE, "name");
1665 
1666         if (src_name.isValid() && dst_name.isValid())
1667         {
1668             const UT_String name(src_name.get(getMapOffset()));
1669             if (name.isstring())
1670             {
1671                 UT_String   full_name(name);
1672                 int         last = name.length() + 1;
1673                 const char  component[] = { 'x', 'y', 'z', 'w' };
1674 
1675                 GA_Size nprimitives = primitiverange.getEntries();
1676                 UT_ASSERT(dst_components.entries() == nprimitives);
1677                 full_name += ".x";
1678                 for (int j = 0; j < nprimitives; j++)
1679                 {
1680                     int i = dst_components(j);
1681                     if (i < 4)
1682                         full_name(last) = component[i];
1683                     else
1684                         full_name.sprintf("%s%d", (const char *)name, i);
1685                     // NOTE: This assumes that the offsets are contiguous,
1686                     //       which is only valid if the converter didn't
1687                     //       delete anything.
1688                     dst_name.set(marker.primitiveBegin() + GA_Offset(i),
1689                                  full_name);
1690                 }
1691             }
1692         }
1693     }
1694 
1695     return dst_geo.getGEOPrimitive(marker.primitiveBegin());
1696 }
1697 
1698 GEO_Primitive *
convert(GU_ConvertParms & parms,GA_PointGroup * usedpts)1699 GU_PrimVDB::convert(GU_ConvertParms &parms, GA_PointGroup *usedpts)
1700 {
1701     bool            success = false;
1702     GEO_Primitive * prim;
1703 
1704     prim = convertToNewPrim(*getParent(), parms,
1705                 /*adaptivity*/0, /*sparse*/false, success);
1706     if (success)
1707     {
1708         if (usedpts)
1709             addPointRefToGroup(*usedpts);
1710 
1711         GA_PrimitiveGroup *group = parms.getDeletePrimitives();
1712         if (group)
1713             group->add(this);
1714         else
1715             getParent()->deletePrimitive(*this, !usedpts);
1716     }
1717     return prim;
1718 }
1719 
1720 /*static*/ void
convertVolumesToVDBs(GU_Detail & dst_geo,const GU_Detail & src_geo,GU_ConvertParms & parms,bool flood_sdf,bool prune,fpreal tolerance,bool keep_original,bool activate_inside_sdf)1721 GU_PrimVDB::convertVolumesToVDBs(
1722         GU_Detail &dst_geo,
1723         const GU_Detail &src_geo,
1724         GU_ConvertParms &parms,
1725         bool flood_sdf,
1726         bool prune,
1727         fpreal tolerance,
1728         bool keep_original,
1729         bool activate_inside_sdf)
1730 {
1731     UT_AutoInterrupt progress("Convert");
1732 
1733     const GA_ROHandleS nameHandle(&src_geo, GA_ATTRIB_PRIMITIVE, "name");
1734 
1735     GEO_Primitive *prim;
1736     GEO_Primitive *next;
1737     GA_FOR_SAFE_GROUP_PRIMITIVES(&src_geo, parms.primGroup, prim, next)
1738     {
1739         if (progress.wasInterrupted())
1740             break;
1741         if (prim->getTypeId() != GEO_PRIMVOLUME)
1742             continue;
1743 
1744         GEO_PrimVolume *vol = UTverify_cast<GEO_PrimVolume*>(prim);
1745         GA_Offset voloff = vol->getMapOffset();
1746         GA_Detail::OffsetMarker marker(dst_geo);
1747 
1748         // Get the volume's name, if it has one.
1749         char const * const volname = (nameHandle.isValid() ? nameHandle.get(voloff) : nullptr);
1750 
1751         GU_PrimVDB *new_prim;
1752         new_prim = GU_PrimVDB::buildFromPrimVolume(
1753                 dst_geo, *vol, volname, flood_sdf, prune, tolerance,
1754                 activate_inside_sdf);
1755         if (!new_prim || progress.wasInterrupted())
1756             break;
1757 
1758         GA_Range pointrange(marker.pointRange());
1759         GA_Range primitiverange(marker.primitiveRange());
1760         GUconvertCopySingleVertexPrimAttribsAndGroups(
1761                 parms, src_geo, voloff, dst_geo,
1762                 primitiverange, pointrange);
1763 
1764         if (!keep_original && (&dst_geo == &src_geo))
1765             dst_geo.deletePrimitive(*vol, /*and points*/true);
1766     }
1767 }
1768 
1769 /*static*/ void
convertVDBs(GU_Detail & dst_geo,const GU_Detail & src_geo,GU_ConvertParms & parms,fpreal adaptivity,bool keep_original,bool split_disjoint_volumes)1770 GU_PrimVDB::convertVDBs(
1771         GU_Detail &dst_geo,
1772         const GU_Detail &src_geo,
1773         GU_ConvertParms &parms,
1774         fpreal adaptivity,
1775         bool keep_original,
1776         bool split_disjoint_volumes)
1777 {
1778     UT_AutoInterrupt    progress("Convert");
1779 
1780     GEO_Primitive *prim;
1781     GEO_Primitive *next;
1782     GA_FOR_SAFE_GROUP_PRIMITIVES(&src_geo, parms.primGroup, prim, next)
1783     {
1784         if (progress.wasInterrupted())
1785             break;
1786 
1787         GU_PrimVDB *vdb = dynamic_cast<GU_PrimVDB*>(prim);
1788         if (vdb == nullptr)
1789             continue;
1790 
1791         bool success = false;
1792         (void) vdb->convertToNewPrim(dst_geo, parms, adaptivity,
1793                                      split_disjoint_volumes, success);
1794         if (success && !keep_original && (&dst_geo == &src_geo))
1795             dst_geo.deletePrimitive(*vdb, /*and points*/true);
1796     }
1797 }
1798 
1799 /*static*/ void
convertVDBs(GU_Detail & dst_geo,const GU_Detail & src_geo,GU_ConvertParms & parms,fpreal adaptivity,bool keep_original)1800 GU_PrimVDB::convertVDBs(
1801         GU_Detail &dst_geo,
1802         const GU_Detail &src_geo,
1803         GU_ConvertParms &parms,
1804         fpreal adaptivity,
1805         bool keep_original)
1806 {
1807     convertVDBs(dst_geo, src_geo, parms, adaptivity, keep_original, false);
1808 }
1809 
1810 void
normal(NormalComp &) const1811 GU_PrimVDB::normal(NormalComp& /*output*/) const
1812 {
1813     // No need here.
1814 }
1815 
1816 
1817 ////////////////////////////////////////
1818 
1819 
1820 namespace {
1821 
1822 template <typename T> struct IsScalarMeta
1823 { HBOOST_STATIC_CONSTANT(bool, value = true); };
1824 
1825 #define DECLARE_VECTOR(METADATA_T) \
1826     template <> struct IsScalarMeta<METADATA_T> \
1827     { HBOOST_STATIC_CONSTANT(bool, value = false); }; \
1828     /**/
1829 DECLARE_VECTOR(openvdb::Vec2IMetadata)
1830 DECLARE_VECTOR(openvdb::Vec2SMetadata)
1831 DECLARE_VECTOR(openvdb::Vec2DMetadata)
1832 DECLARE_VECTOR(openvdb::Vec3IMetadata)
1833 DECLARE_VECTOR(openvdb::Vec3SMetadata)
1834 DECLARE_VECTOR(openvdb::Vec3DMetadata)
1835 DECLARE_VECTOR(openvdb::Vec4IMetadata)
1836 DECLARE_VECTOR(openvdb::Vec4SMetadata)
1837 DECLARE_VECTOR(openvdb::Vec4DMetadata)
1838 #undef DECLARE_VECTOR
1839 
1840 template<typename T, typename MetadataT, int I, typename ENABLE = void>
1841 struct MetaTuple
1842 {
get__anon678bc1dc0511::MetaTuple1843     static T get(const MetadataT& meta) {
1844         return meta.value()[I];
1845     }
1846 };
1847 
1848 template<typename T, typename MetadataT, int I>
1849 struct MetaTuple<T, MetadataT, I, typename SYS_EnableIf< IsScalarMeta<MetadataT>::value >::type>
1850 {
get__anon678bc1dc0511::MetaTuple1851     static T get(const MetadataT& meta) {
1852         UT_ASSERT(I == 0);
1853         return meta.value();
1854     }
1855 };
1856 
1857 template<int I>
1858 struct MetaTuple<const char*, openvdb::StringMetadata, I>
1859 {
get__anon678bc1dc0511::MetaTuple1860     static const char* get(const openvdb::StringMetadata& meta) {
1861     UT_ASSERT(I == 0);
1862         return meta.value().c_str();
1863     }
1864 };
1865 
1866 template <typename MetadataT> struct MetaAttr;
1867 
1868 #define META_ATTR(METADATA_T, STORAGE, TUPLE_T, TUPLE_SIZE) \
1869     template <> \
1870     struct MetaAttr<METADATA_T> { \
1871         using TupleT = TUPLE_T; \
1872         using RWHandleT = GA_HandleT<TupleT>::RWType; \
1873         static const int theTupleSize = TUPLE_SIZE; \
1874         static const GA_Storage theStorage = STORAGE; \
1875     }; \
1876     /**/
1877 
1878 META_ATTR(openvdb::BoolMetadata,   GA_STORE_INT8,   int8,        1)
1879 META_ATTR(openvdb::FloatMetadata,  GA_STORE_REAL32, fpreal32,    1)
1880 META_ATTR(openvdb::DoubleMetadata, GA_STORE_REAL64, fpreal64,    1)
1881 META_ATTR(openvdb::Int32Metadata,  GA_STORE_INT32,  int32,       1)
1882 META_ATTR(openvdb::Int64Metadata,  GA_STORE_INT64,  int64,       1)
1883 //META_ATTR(openvdb::StringMetadata, GA_STORE_STRING, const char*, 1)
1884 META_ATTR(openvdb::Vec2IMetadata,  GA_STORE_INT32,  int32,       2)
1885 META_ATTR(openvdb::Vec2SMetadata,  GA_STORE_REAL32, fpreal32,    2)
1886 META_ATTR(openvdb::Vec2DMetadata,  GA_STORE_REAL64, fpreal64,    2)
1887 META_ATTR(openvdb::Vec3IMetadata,  GA_STORE_INT32,  int32,       3)
1888 META_ATTR(openvdb::Vec3SMetadata,  GA_STORE_REAL32, fpreal32,    3)
1889 META_ATTR(openvdb::Vec3DMetadata,  GA_STORE_REAL64, fpreal64,    3)
1890 META_ATTR(openvdb::Vec4IMetadata,  GA_STORE_INT32,  int32,       4)
1891 META_ATTR(openvdb::Vec4SMetadata,  GA_STORE_REAL32, fpreal32,    4)
1892 META_ATTR(openvdb::Vec4DMetadata,  GA_STORE_REAL64, fpreal64,    4)
1893 META_ATTR(openvdb::Mat4SMetadata,  GA_STORE_REAL32, fpreal32,    16)
1894 META_ATTR(openvdb::Mat4DMetadata,  GA_STORE_REAL64, fpreal64,    16)
1895 
1896 #undef META_ATTR
1897 
1898 // Functor for setAttr()
1899 typedef hboost::function<
1900     void (GEO_Detail&, GA_AttributeOwner, GA_Offset, const char*, const openvdb::Metadata&)> AttrSettor;
1901 
1902 template <typename MetadataT>
1903 static void
setAttr(GEO_Detail & geo,GA_AttributeOwner owner,GA_Offset elem,const char * name,const openvdb::Metadata & meta_base)1904 setAttr(GEO_Detail& geo, GA_AttributeOwner owner, GA_Offset elem,
1905     const char* name, const openvdb::Metadata& meta_base)
1906 {
1907     using MetaAttrT = MetaAttr<MetadataT>;
1908     using RWHandleT = typename MetaAttrT::RWHandleT;
1909     using TupleT = typename MetaAttrT::TupleT;
1910 
1911     // Try to bind the type, if fails, then create a tuple.
1912     RWHandleT handle(&geo, owner, name, MetaAttrT::theTupleSize);
1913     if (!handle.isValid())
1914     {
1915         geo.addTuple(MetaAttrT::theStorage, owner, name, MetaAttrT::theTupleSize);
1916         handle.bind(&geo, owner, name, MetaAttrT::theTupleSize);
1917         if (!handle.isValid())
1918             return;
1919     }
1920 
1921     const MetadataT& meta = static_cast<const MetadataT&>(meta_base);
1922     switch (MetaAttrT::theTupleSize) {
1923     case 4: handle.set(elem, 3, MetaTuple<TupleT,MetadataT,3>::get(meta));
1924             SYS_FALLTHROUGH;
1925     case 3: handle.set(elem, 2, MetaTuple<TupleT,MetadataT,2>::get(meta));
1926             SYS_FALLTHROUGH;
1927     case 2: handle.set(elem, 1, MetaTuple<TupleT,MetadataT,1>::get(meta));
1928             SYS_FALLTHROUGH;
1929     case 1: handle.set(elem, 0, MetaTuple<TupleT,MetadataT,0>::get(meta));
1930     }
1931     UT_ASSERT(MetaAttrT::theTupleSize >= 1 && MetaAttrT::theTupleSize <= 4);
1932 }
1933 
1934 /// for Houdini 12.1
1935 template <typename MetadataT>
1936 static void
setStrAttr(GEO_Detail & geo,GA_AttributeOwner owner,GA_Offset elem,const char * name,const openvdb::Metadata & meta_base)1937 setStrAttr(GEO_Detail& geo, GA_AttributeOwner owner, GA_Offset elem,
1938     const char* name, const openvdb::Metadata& meta_base)
1939 {
1940     GA_RWHandleS handle(&geo, owner, name);
1941     if (!handle.isValid())
1942     {
1943         geo.addStringTuple(owner, name, 1);
1944         handle.bind(&geo, owner, name);
1945         if (!handle.isValid())
1946             return;
1947     }
1948 
1949     const MetadataT& meta = static_cast<const MetadataT&>(meta_base);
1950     handle.set(elem, 0, MetaTuple<const char*, MetadataT, 0>::get(meta));
1951 }
1952 
1953 template <typename MetadataT>
1954 static void
setMatAttr(GEO_Detail & geo,GA_AttributeOwner owner,GA_Offset elem,const char * name,const openvdb::Metadata & meta_base)1955 setMatAttr(GEO_Detail& geo, GA_AttributeOwner owner, GA_Offset elem,
1956     const char* name, const openvdb::Metadata& meta_base)
1957 {
1958     using MetaAttrT = MetaAttr<MetadataT>;
1959     using RWHandleT = typename MetaAttrT::RWHandleT;
1960     using TupleT = typename MetaAttrT::TupleT;
1961 
1962     // Try to bind the type, if fails, then create a tuple.
1963     RWHandleT handle(&geo, owner, name, MetaAttrT::theTupleSize);
1964     if (!handle.isValid())
1965     {
1966         geo.addTuple(MetaAttrT::theStorage, owner, name, MetaAttrT::theTupleSize);
1967         handle.bind(&geo, owner, name, MetaAttrT::theTupleSize);
1968         if (!handle.isValid())
1969             return;
1970     }
1971 
1972     const MetadataT& meta = static_cast<const MetadataT&>(meta_base);
1973 
1974     auto && value = meta.value();
1975     for (int i = 0; i < MetaAttrT::theTupleSize; i++)
1976     {
1977         handle.set(elem, i, value.asPointer()[i]);
1978     }
1979 }
1980 
1981 class MetaToAttrMap : public std::map<std::string, AttrSettor>
1982 {
1983 public:
MetaToAttrMap()1984     MetaToAttrMap()
1985     {
1986         using namespace openvdb;
1987         // Construct a mapping from OpenVDB metadata types to functions
1988         // that create attributes of corresponding types.
1989         (*this)[BoolMetadata::staticTypeName()]   = &setAttr<BoolMetadata>;
1990         (*this)[FloatMetadata::staticTypeName()]  = &setAttr<FloatMetadata>;
1991         (*this)[DoubleMetadata::staticTypeName()] = &setAttr<DoubleMetadata>;
1992         (*this)[Int32Metadata::staticTypeName()]  = &setAttr<Int32Metadata>;
1993         (*this)[Int64Metadata::staticTypeName()]  = &setAttr<Int64Metadata>;
1994         (*this)[StringMetadata::staticTypeName()] = &setStrAttr<StringMetadata>;
1995         (*this)[Vec2IMetadata::staticTypeName()]  = &setAttr<Vec2IMetadata>;
1996         (*this)[Vec2SMetadata::staticTypeName()]  = &setAttr<Vec2SMetadata>;
1997         (*this)[Vec2DMetadata::staticTypeName()]  = &setAttr<Vec2DMetadata>;
1998         (*this)[Vec3IMetadata::staticTypeName()]  = &setAttr<Vec3IMetadata>;
1999         (*this)[Vec3SMetadata::staticTypeName()]  = &setAttr<Vec3SMetadata>;
2000         (*this)[Vec3DMetadata::staticTypeName()]  = &setAttr<Vec3DMetadata>;
2001         (*this)[Vec4IMetadata::staticTypeName()]  = &setAttr<Vec4IMetadata>;
2002         (*this)[Vec4SMetadata::staticTypeName()]  = &setAttr<Vec4SMetadata>;
2003         (*this)[Vec4DMetadata::staticTypeName()]  = &setAttr<Vec4DMetadata>;
2004 
2005         (*this)[Mat4SMetadata::staticTypeName()]  = &setMatAttr<Mat4SMetadata>;
2006         (*this)[Mat4DMetadata::staticTypeName()]  = &setMatAttr<Mat4DMetadata>;
2007     }
2008 };
2009 
2010 
2011 static UT_SingletonWithLock<MetaToAttrMap> sMetaToAttrMap;
2012 
2013 } // unnamed namespace
2014 
2015 
2016 ////////////////////////////////////////
2017 
2018 
2019 void
syncAttrsFromMetadata()2020 GU_PrimVDB::syncAttrsFromMetadata()
2021 {
2022     if (GEO_Detail* detail = this->getParent()) {
2023         createGridAttrsFromMetadata(*this, this->getConstGrid(), *detail);
2024     }
2025 }
2026 
2027 
2028 void
createGridAttrsFromMetadataAdapter(const GEO_PrimVDB & prim,const void * gridPtr,GEO_Detail & aGdp)2029 GU_PrimVDB::createGridAttrsFromMetadataAdapter(
2030         const GEO_PrimVDB& prim,
2031         const void* gridPtr,
2032         GEO_Detail& aGdp)
2033 {
2034     createAttrsFromMetadataAdapter(
2035         GA_ATTRIB_PRIMITIVE, prim.getMapOffset(), gridPtr, aGdp);
2036 }
2037 
2038 
2039 void
createAttrsFromMetadataAdapter(GA_AttributeOwner owner,GA_Offset element,const void * meta_map_ptr,GEO_Detail & geo)2040 GU_PrimVDB::createAttrsFromMetadataAdapter(
2041     GA_AttributeOwner owner,
2042     GA_Offset element,
2043     const void* meta_map_ptr,
2044     GEO_Detail& geo)
2045 {
2046     // meta_map_ptr is assumed to point to an openvdb::vX_Y_Z::MetaMap, for some
2047     // version X.Y.Z of OpenVDB that may be newer than the one with which
2048     // libHoudiniGEO.so was built.  This is safe provided that MetaMap and
2049     // its member objects are ABI-compatible between the two OpenVDB versions.
2050     const openvdb::MetaMap& meta_map = *static_cast<const openvdb::MetaMap*>(meta_map_ptr);
2051 
2052     for (openvdb::MetaMap::ConstMetaIterator metaIt = meta_map.beginMeta(),
2053             metaEnd = meta_map.endMeta(); metaIt != metaEnd; ++metaIt) {
2054 
2055         if (openvdb::Metadata::Ptr meta = metaIt->second) {
2056             std::string name = metaIt->first;
2057 
2058             UT_String str(name);
2059             str.toLower();
2060             str.forceValidVariableName();
2061             UT_String prefixed(name);
2062             prefixed.prepend("vdb_");
2063             if (isIntrinsicMetadata(prefixed))
2064                 continue;
2065 
2066             // If this grid's name is empty and a "name" attribute
2067             // doesn't already exist, don't create one.
2068             if (str == "name"
2069                 && meta->typeName() == openvdb::StringMetadata::staticTypeName()
2070                 && meta->str().empty())
2071             {
2072                 if (!geo.findAttribute(owner, name.c_str())) continue;
2073             }
2074 
2075             MetaToAttrMap::const_iterator creatorIt =
2076                 sMetaToAttrMap->find(meta->typeName());
2077             if (creatorIt != sMetaToAttrMap->end()) {
2078                 creatorIt->second(geo, owner, element, name.c_str(), *meta);
2079             } else {
2080                 /// @todo Add warning:
2081                 // std::string("discarded metadata \"") + name
2082                 //    + "\" of unsupported type " + meta->typeName()
2083             }
2084         }
2085     }
2086 }
2087 
2088 
2089 void
createMetadataFromGridAttrsAdapter(void * gridPtr,const GEO_PrimVDB & prim,const GEO_Detail & aGdp)2090 GU_PrimVDB::createMetadataFromGridAttrsAdapter(
2091         void* gridPtr,
2092         const GEO_PrimVDB& prim,
2093         const GEO_Detail& aGdp)
2094 {
2095     createMetadataFromAttrsAdapter(
2096         gridPtr, GA_ATTRIB_PRIMITIVE, prim.getMapOffset(), aGdp);
2097 }
2098 
2099 void
createMetadataFromAttrsAdapter(void * meta_map_ptr,GA_AttributeOwner owner,GA_Offset element,const GEO_Detail & geo)2100 GU_PrimVDB::createMetadataFromAttrsAdapter(
2101     void* meta_map_ptr,
2102     GA_AttributeOwner owner,
2103     GA_Offset element,
2104     const GEO_Detail& geo)
2105 {
2106     using namespace openvdb;
2107 
2108     // meta_map_ptr is assumed to point to an openvdb::vX_Y_Z::MetaMap, for some
2109     // version X.Y.Z of OpenVDB that may be newer than the one with which
2110     // libHoudiniGEO.so was built.  This is safe provided that MetaMap and
2111     // its member objects are ABI-compatible between the two OpenVDB versions.
2112     openvdb::MetaMap& meta_map = *static_cast<openvdb::MetaMap*>(meta_map_ptr);
2113 
2114     const GA_AttributeSet& attrs = geo.getAttributes();
2115     for (GA_AttributeDict::iterator it = attrs.begin(owner, GA_SCOPE_PUBLIC); !it.atEnd(); ++it) {
2116 
2117         if (!it.name()) continue;
2118 
2119         std::string name = it.name();
2120 
2121         UT_String prefixed(name);
2122         prefixed.prepend("vdb_");
2123         if (isIntrinsicMetadata(prefixed))
2124             continue;
2125 
2126         const GA_Attribute* attrib = it.attrib();
2127         const GA_AIFTuple* tuple = attrib->getAIFTuple();
2128         const int entries = attrib->getTupleSize();
2129 
2130         switch (attrib->getStorageClass()) {
2131 
2132         case GA_STORECLASS_INT:
2133             if (!tuple)
2134                 continue;
2135             switch (entries) {
2136             case 1:
2137                 meta_map.removeMeta(name);
2138                 if (name.substr(0, 3) == "is_") {
2139                     // Scalar integer attributes whose names begin with "is_"
2140                     // are mapped to boolean metadata.
2141                     if (tuple->getStorage(attrib) == GA_STORE_INT64) {
2142                         GA_ROHandleT<int64> handle(attrib);
2143                         meta_map.insertMeta(name, BoolMetadata(
2144                         handle.get(element) != 0));
2145                     } else {
2146                         GA_ROHandleT<int32> handle(attrib);
2147                         meta_map.insertMeta(name, BoolMetadata(
2148                         handle.get(element) != 0));
2149                     }
2150                 } else {
2151                     if (tuple->getStorage(attrib) == GA_STORE_INT64) {
2152                         GA_ROHandleT<int64> handle(attrib);
2153                         meta_map.insertMeta(name, Int64Metadata(
2154                         handle.get(element)));
2155                     } else {
2156                         GA_ROHandleT<int32> handle(attrib);
2157                         meta_map.insertMeta(name, Int32Metadata(
2158                         handle.get(element)));
2159                     }
2160                 }
2161                 break;
2162             case 2:
2163                 {
2164                     GA_ROHandleT<UT_Vector2i> handle(attrib);
2165                     meta_map.removeMeta(name);
2166                     meta_map.insertMeta(name, Vec2IMetadata(
2167                     UTvdbConvert(handle.get(element))));
2168                 }
2169             break;
2170             case 3:
2171                 {
2172                     GA_ROHandleT<UT_Vector3i> handle(attrib);
2173                     meta_map.removeMeta(name);
2174                     meta_map.insertMeta(name, Vec3IMetadata(
2175                     UTvdbConvert(handle.get(element))));
2176                 }
2177             break;
2178             case 4:
2179                 {
2180                     GA_ROHandleT<UT_Vector4i> handle(attrib);
2181                     meta_map.removeMeta(name);
2182                     meta_map.insertMeta(name, Vec4IMetadata(
2183                     UTvdbConvert(handle.get(element))));
2184                 }
2185             break;
2186             default:
2187                 {
2188                     /// @todo Add warning:
2189                     //std::ostringstream ostr;
2190                     //ostr << "Skipped int[" << entries << "] metadata attribute \""
2191                     //    << it.name() << "\" (int tuples of size > 3 are not supported)";
2192                 }
2193                 break;
2194             }
2195             break;
2196 
2197         case GA_STORECLASS_FLOAT:
2198             if (!tuple)
2199                 continue;
2200             switch (entries) {
2201             case 1:
2202                 meta_map.removeMeta(name);
2203                 if (tuple->getStorage(attrib) == GA_STORE_REAL64) {
2204                     GA_ROHandleT<fpreal64> handle(attrib);
2205                     meta_map.insertMeta(name, DoubleMetadata(
2206                     handle.get(element)));
2207                 } else {
2208                     GA_ROHandleT<fpreal32> handle(attrib);
2209                     meta_map.insertMeta(name, FloatMetadata(
2210                     handle.get(element)));
2211                 }
2212                 break;
2213             case 2:
2214                 meta_map.removeMeta(name);
2215                 if (tuple->getStorage(attrib) == GA_STORE_REAL64) {
2216                     GA_ROHandleT<UT_Vector2D> handle(attrib);
2217                     meta_map.insertMeta(name, Vec2DMetadata(
2218                     UTvdbConvert(handle.get(element))));
2219                 } else {
2220                     GA_ROHandleT<UT_Vector2F> handle(attrib);
2221                     meta_map.insertMeta(name, Vec2SMetadata(
2222                     UTvdbConvert(handle.get(element))));
2223                 }
2224             break;
2225             case 3:
2226                 meta_map.removeMeta(name);
2227                 if (tuple->getStorage(attrib) == GA_STORE_REAL64) {
2228                     GA_ROHandleT<UT_Vector3D> handle(attrib);
2229                     meta_map.insertMeta(name, Vec3DMetadata(
2230                     UTvdbConvert(handle.get(element))));
2231                 } else {
2232                     GA_ROHandleT<UT_Vector3F> handle(attrib);
2233                     meta_map.insertMeta(name, Vec3SMetadata(
2234                     UTvdbConvert(handle.get(element))));
2235                 }
2236                 break;
2237             case 4:
2238                 meta_map.removeMeta(name);
2239                 if (tuple->getStorage(attrib) == GA_STORE_REAL64) {
2240                     GA_ROHandleT<UT_Vector4D> handle(attrib);
2241                     meta_map.insertMeta(name, Vec4DMetadata(
2242                     UTvdbConvert(handle.get(element))));
2243                 } else {
2244                     GA_ROHandleT<UT_Vector4F> handle(attrib);
2245                     meta_map.insertMeta(name, Vec4SMetadata(
2246                     UTvdbConvert(handle.get(element))));
2247                 }
2248                 break;
2249             case 16:
2250                 meta_map.removeMeta(name);
2251                 if (tuple->getStorage(attrib) == GA_STORE_REAL64) {
2252                     GA_ROHandleT<UT_Matrix4D> handle(attrib);
2253                     meta_map.insertMeta(name, Mat4DMetadata(
2254                     UTvdbConvert(handle.get(element))));
2255                 } else {
2256                     GA_ROHandleT<UT_Matrix4F> handle(attrib);
2257                     meta_map.insertMeta(name, Mat4SMetadata(
2258                     UTvdbConvert(handle.get(element))));
2259                 }
2260                 break;
2261             default:
2262                 {
2263                     /// @todo Add warning:
2264                     //std::ostringstream ostr;
2265                     //ostr << "Skipped float[" << entries << "] metadata attribute \""
2266                     //    << it.name() << "\" (float tuples of size > 3 are not supported)";
2267                 }
2268                 break;
2269             }
2270             break;
2271 
2272         case GA_STORECLASS_STRING: {
2273             GA_ROHandleS handle(attrib);
2274             if (entries == 1 && handle.isValid()) {
2275                 meta_map.removeMeta(name);
2276                 const char* str = handle.get(element);
2277                 if (!str) str = "";
2278                 meta_map.insertMeta(name, StringMetadata(str));
2279             } else {
2280                 /// @todo Add warning:
2281                 //std::ostringstream ostr;
2282                 //ostr << "Skipped string[" << entries << "] metadata attribute \""
2283                 //    << it.name() << "\" (string tuples are not supported)";
2284             }
2285             break;
2286         }
2287 
2288         case GA_STORECLASS_INVALID: break;
2289         case GA_STORECLASS_DICT: break;
2290         case GA_STORECLASS_OTHER: break;
2291         }
2292     }
2293 }
2294 
2295 
2296 ////////////////////////////////////////
2297 
2298 // Following code is for HDK only
2299 #ifndef SESI_OPENVDB
2300 // This is the usual DSO hook.
2301 extern "C" {
2302 void
newGeometryPrim(GA_PrimitiveFactory * factory)2303 newGeometryPrim(GA_PrimitiveFactory *factory)
2304 {
2305     GU_PrimVDB::registerMyself(factory);
2306 }
2307 
2308 } // extern "C"
2309 #endif
2310 
2311 #endif // SESI_OPENVDB || SESI_OPENVDB_PRIM
2312