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