HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SOP_VolumeProject.C
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2024
3  * Side Effects Software Inc. All rights reserved.
4  *
5  * Redistribution and use of Houdini Development Kit samples in source and
6  * binary forms, with or without modification, are permitted provided that the
7  * following conditions are met:
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  * 2. The name of Side Effects Software may not be used to endorse or
11  * promote products derived from this software without specific prior
12  * written permission.
13  *
14  * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
15  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
17  * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
20  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
21  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
23  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  *----------------------------------------------------------------------------
26  */
27 
28 #include "SOP_VolumeProject.h"
29 #include "SOP_VolumeProject.proto.h"
30 
31 #include <UT/UT_VoxelArray.h>
32 #include <UT/UT_ParallelUtil.h>
33 #include <UT/UT_StopWatch.h>
34 
35 #include <GU/GU_Detail.h>
36 #include <GU/GU_PrimVolume.h>
37 
38 #include <PRM/PRM_Include.h>
40 #include <UT/UT_DSOVersion.h>
41 
42 
43 using namespace UT::Literal;
44 using namespace HDK_Sample;
45 void
47 {
48  table->addOperator(new OP_Operator(
49  "hdk_volumeproject",
50  "Volume Project",
51  SOP_VolumeProject::myConstructor,
52  SOP_VolumeProject::buildTemplates(),
53  3,
54  4));
55 }
56 
57 static const char *theDsFile = R"THEDSFILE(
58 {
59  name volumefft
60  parm {
61  name "group"
62  label "Velocity Volumes"
63  type string
64  default { "" }
65  parmtag { "script_action" "import soputils\nkwargs['geometrytype'] = (hou.geometryType.Primitives,)\nkwargs['inputindex'] = 0\nsoputils.selectGroupParm(kwargs)" }
66  parmtag { "script_action_help" "Select geometry from an available viewport.\nShift-click to turn on Select Groups." }
67  parmtag { "script_action_icon" "BUTTONS_reselect" }
68  }
69  parm {
70  name "divgroup"
71  cppname "DivGroup"
72  label "Divergence Volume"
73  type string
74  default { "" }
75  parmtag { "script_action" "import soputils\nkwargs['geometrytype'] = (hou.geometryType.Primitives,)\nkwargs['inputindex'] = 1\nsoputils.selectGroupParm(kwargs)" }
76  parmtag { "script_action_help" "Select geometry from an available viewport.\nShift-click to turn on Select Groups." }
77  parmtag { "script_action_icon" "BUTTONS_reselect" }
78  }
79  parm {
80  name "lutgroup"
81  cppname "LUTGroup"
82  label "LUT Volumes"
83  type string
84  default { "" }
85  parmtag { "script_action" "import soputils\nkwargs['geometrytype'] = (hou.geometryType.Primitives,)\nkwargs['inputindex'] = 2\nsoputils.selectGroupParm(kwargs)" }
86  parmtag { "script_action_help" "Select geometry from an available viewport.\nShift-click to turn on Select Groups." }
87  parmtag { "script_action_icon" "BUTTONS_reselect" }
88  }
89  parm {
90  name "activegroup"
91  cppname "ActiveGroup"
92  label "Active Volume"
93  type string
94  default { "" }
95  parmtag { "script_action" "import soputils\nkwargs['geometrytype'] = (hou.geometryType.Primitives,)\nkwargs['inputindex'] = 3\nsoputils.selectGroupParm(kwargs)" }
96  parmtag { "script_action_help" "Select geometry from an available viewport.\nShift-click to turn on Select Groups." }
97  parmtag { "script_action_icon" "BUTTONS_reselect" }
98  }
99  parm {
100  name "lutcenter"
101  cppname "LUTCenter"
102  label "LUT Center"
103  type integer
104  default { 10 }
105  }
106  parm {
107  name "lutrad"
108  label "LUT Rad"
109  cppname LUTRad
110  type integer
111  default { 1 }
112  }
113  parm {
114  name "lutmagic"
115  label "LUT Magic"
116  cppname LUTMagic
117  type float
118  default { 1 }
119  }
120  parm {
121  name "lutround"
122  label "LUT Round Pattern"
123  cppname LUTRound
124  type toggle
125  default { "1" }
126  }
127  parm {
128  name "domip"
129  label "Do MIP MAP"
130  cppname DoMIP
131  type toggle
132  default { "1" }
133  }
134  parm {
135  name "mipby4"
136  label "Mip MAP by 4"
137  cppname MipBy4
138  type toggle
139  default { "1" }
140  }
141  parm {
142  name "mipmagic"
143  label "MIP Magic"
144  cppname MIPMagic
145  type float
146  default { 1 }
147  }
148 
149  parm {
150  name "zeroinactive"
151  label "Zero Inactive"
152  cppname ZeroInactive
153  type toggle
154  default { 0 }
155  }
156 }
157 )THEDSFILE";
158 
159 PRM_Template *
160 SOP_VolumeProject::buildTemplates()
161 {
162  static PRM_TemplateBuilder templ("SOP_VolumeProject.C"_sh, theDsFile);
163  if (templ.justBuilt())
164  {
165  templ.setChoiceListPtr("group", &SOP_Node::primGroupMenu);
166  templ.setChoiceListPtr("divgroup", &SOP_Node::primGroupMenu);
167  }
168  return templ.templates();
169 }
170 
171 
172 OP_Node *
173 SOP_VolumeProject::myConstructor(OP_Network *dad, const char *name, OP_Operator *op)
174 {
175  return new SOP_VolumeProject(dad, name, op);
176 }
177 
178 SOP_VolumeProject::SOP_VolumeProject(OP_Network *dad, const char *name, OP_Operator *op)
179  : SOP_Node(dad, name, op)
180 {
181 }
182 
184 {
185 }
186 
187 OP_ERROR
189 {
190  return cookMyselfAsVerb(context);
191 }
192 
194 {
195 public:
197  {
198  }
200 
201  virtual SOP_NodeParms *allocParms() const { return new SOP_VolumeProjectParms(); }
202  virtual UT_StringHolder name() const { return "volumeproject"_sh; }
203 
204  virtual CookMode cookMode(const SOP_NodeParms *parms) const { return COOK_INPLACE; }
205 
206  virtual void cook(const CookParms &cookparms) const;
207 };
208 
209 
210 static SOP_NodeVerb::Register<SOP_VolumeProjectVerb> theSOPVolumeProjectVerb;
211 
212 const SOP_NodeVerb *
214 {
215  return theSOPVolumeProjectVerb.get();
216 }
217 
218 
219 static void
220 sop_applyVelLUT(const SOP_NodeVerb::CookParms &cookparms,
221  UT_VoxelArrayF *vel, const UT_VoxelArrayF *div,
222  const UT_VoxelArrayF *active,
223  const UT_VoxelArrayF *lut,
224  UT_Vector3 voxelsize)
225 {
226  auto &&sopparms = cookparms.parms<SOP_VolumeProjectParms>();
227  bool doround = sopparms.getLUTRound();
228 
229  UT_StopWatch watch;
230  watch.start();
231 
232  // Verify lut is big enough.
233  if (sopparms.getLUTCenter() - sopparms.getLUTRad() < 0)
234  {
235  cookparms.sopAddError(SOP_MESSAGE, "LUT range invalid");
236  return;
237  }
238  if (sopparms.getLUTCenter() + sopparms.getLUTRad() >= SYSmax(lut->getXRes(), lut->getYRes(), lut->getZRes()) )
239  {
240  cookparms.sopAddError(SOP_MESSAGE, "LUT range exceeds provided table");
241  return;
242  }
243  if (sopparms.getLUTCenter() - sopparms.getLUTRad() < 0)
244  {
245  cookparms.sopAddError(SOP_MESSAGE, "LUT range exceeds provided table");
246  return;
247  }
248 
249  int divxres = div->getXRes();
250  int divyres = div->getYRes();
251  int divzres = div->getZRes();
252 
253  // Assume our lookup table is built with unit-sized voxels.
254  // Then it has the update value for unit voxels, so we need
255  // to divide by our voxel size.
256  // Correspondingly, we divide by 3 for 3 dimensions we sum divergence
257  // in to build our correction matrix.
258  float lutmagic = sopparms.getLUTMagic();
259 
260  lutmagic /= voxelsize.x();
261  lutmagic /= 3;
262 
264  [&](const UT_BlockedRange<exint> &r)
265  {
266  exint curtile = r.begin();
268  vit.setLinearTile(curtile, vel);
269  for (vit.rewind(); !vit.atEnd(); vit.advance())
270  {
271  float delta = 0;
272  int rad = sopparms.getLUTRad();
273 
274  if (active)
275  {
276  if (active->getValue(vit.x(), vit.y(), vit.z()) < 0.5)
277  {
278  if (sopparms.getZeroInactive())
279  vit.setValue(0);
280  continue;
281  }
282  }
283 
284  int radzmin = -rad;
285  int radzmax = rad;
286  // Bump so we fit fully in the next level mip map.
287  if (doround)
288  {
289  if ( (vit.z() - rad) & 1 )
290  radzmin--;
291  if ( !( (vit.z() + rad) & 1 ) )
292  radzmax++;
293  }
294 
295  for (int z = radzmin; z <= radzmax; z++)
296  {
297  if (vit.z() + z < 0)
298  continue;
299  if (vit.z() + z >= divzres)
300  continue;
301 
302  int radymin = -rad;
303  int radymax = rad;
304  // Bump so we fit fully in the next level mip map.
305  if (doround)
306  {
307  if ( (vit.y() - rad) & 1 )
308  radymin--;
309  if ( !( (vit.y() + rad) & 1 ) )
310  radymax++;
311  }
312 
313  for (int y = radymin; y <= radymax; y++)
314  {
315  if (vit.y() + y < 0)
316  continue;
317  if (vit.y() + y >= divyres)
318  continue;
319 
320  int radxmin = -rad;
321  int radxmax = rad;
322  // Bump so we fit fully in the next level mip map.
323  if (doround)
324  {
325  if ( (vit.x() - rad) & 1 )
326  radxmin--;
327  if ( !( (vit.x() + rad) & 1 ) )
328  radxmax++;
329  }
330 
331  for (int x = radxmin; x <= radxmax; x++)
332  {
333  if (vit.x() + x < 0)
334  continue;
335  if (vit.x() + x >= divxres)
336  continue;
337 
338  // Compute the divergence relative index.
339  float val = (*div)(vit.x()+x, vit.y()+y, vit.z()+z);
340 
341  val *= lutmagic;
342  val *= (*lut)(sopparms.getLUTCenter() - x,
343  sopparms.getLUTCenter() - y,
344  sopparms.getLUTCenter() - z);
345 
346  delta += val;
347  }
348  }
349  }
350 
351  // If we ended up with a delta, update our current voxel.
352  if (delta)
353  {
354  vit.setValue(vit.getValue() - delta);
355  }
356  }
357  });
358 
359  UTdebugPrintCd(none,"Apply LUT Time:", watch.stop());
360 }
361 
362 #define MIN_DIV 1e-8
363 
364 static void
365 sop_buildMipMap(UT_Array<UT_VoxelArrayV4 *> &pos_mip,
367  const GEO_PrimVolume *div)
368 {
369  UT_StopWatch watch;
370  watch.start();
371 
373 
374  const UT_VoxelArrayF *div_vox = &*divh;
375  GEO_PrimVolumeXform divxform = div->getIndexSpaceTransform(*div_vox);
376  UT_ASSERT(pos_mip.size() == 0);
377  UT_ASSERT(neg_mip.size() == 0);
378 
379  // Base level we have to initialize specially as we must
380  // compute actual coordinates...
381  pos_mip.append(new UT_VoxelArrayV4());
382  neg_mip.append(new UT_VoxelArrayV4());
383 
384  UT_VoxelArrayV4 *pos, *neg;
385 
386  pos = pos_mip(0);
387  neg = neg_mip(0);
388 
389  pos->size((div_vox->getXRes()+1) / 2,
390  (div_vox->getYRes()+1) / 2,
391  (div_vox->getZRes()+1) / 2);
392  neg->size((div_vox->getXRes()+1) / 2,
393  (div_vox->getYRes()+1) / 2,
394  (div_vox->getZRes()+1) / 2);
395 
397  [&](const UT_BlockedRange<exint> &r)
398  {
399  exint curtile = r.begin();
401  vit.setLinearTile(curtile, pos);
402  for (vit.rewind(); !vit.atEnd(); vit.advance())
403  {
404  // Parents are vit * 2 & vit * 2 + 1
405  UT_Vector4 pos_v, neg_v;
406  pos_v = 0;
407  neg_v = 0;
408 
409  for (int dz = 0; dz <= 1; dz++)
410  {
411  int z = vit.z() * 2 + dz;
412  if (z >= div_vox->getZRes())
413  continue;
414  for (int dy = 0; dy <= 1; dy++)
415  {
416  int y = vit.y() * 2 + dy;
417  if (y >= div_vox->getYRes())
418  continue;
419  for (int dx = 0; dx <= 1; dx++)
420  {
421  int x = vit.x() * 2 + dx;
422  if (x >= div_vox->getXRes())
423  continue;
424 
425  float div_v = (*div_vox)(x, y, z);
426 
427  // Note exact equality to zero is ignored.
428  if (div_v > MIN_DIV)
429  {
430  UT_Vector3 p = divxform.fromVoxelSpace(UT_Vector3(x, y, z));
431  p *= div_v;
432  pos_v.x() += p.x();
433  pos_v.y() += p.y();
434  pos_v.z() += p.z();
435  pos_v.w() += div_v;
436  }
437  else if (div_v < -MIN_DIV)
438  {
439  UT_Vector3 p = divxform.fromVoxelSpace(UT_Vector3(x, y, z));
440  p *= -div_v;
441  neg_v.x() += p.x();
442  neg_v.y() += p.y();
443  neg_v.z() += p.z();
444  neg_v.w() += -div_v;
445  }
446  }
447  }
448  }
449 
450  pos->setValue(vit.x(), vit.y(), vit.z(), pos_v);
451  neg->setValue(vit.x(), vit.y(), vit.z(), neg_v);
452  }
453  });
454 
455  // We've now built mip-map level 0, which is half the res of
456  // the incoming div but is of v4 dipole format. We now
457  // repeatedly decimate until it disappears.
458  while (1)
459  {
460  UT_VoxelArrayV4 *old_pos, *old_neg;
461  old_pos = pos_mip.last();
462  old_neg = neg_mip.last();
463 
464  // We compute 6 voxels on each layer, so as soon as we get down
465  // to max 6 on each side, there is no point going farther.
466  if (SYSmax(old_pos->getXRes(), old_pos->getYRes(), old_pos->getZRes()) <= 6)
467  {
468  // We've converged
469  break;
470  }
471 
472  pos_mip.append(new UT_VoxelArrayV4());
473  neg_mip.append(new UT_VoxelArrayV4());
474  pos = pos_mip.last();
475  neg = neg_mip.last();
476  pos->size((old_pos->getXRes()+1) / 2,
477  (old_pos->getYRes()+1) / 2,
478  (old_pos->getZRes()+1) / 2);
479  neg->size((old_neg->getXRes()+1) / 2,
480  (old_neg->getYRes()+1) / 2,
481  (old_neg->getZRes()+1) / 2);
482 
483  UT_VoxelArrayIteratorV4 vit(pos);
484  for (vit.rewind(); !vit.atEnd(); vit.advance())
485  {
486  // Parents are vit * 2 & vit * 2 + 1
487  UT_Vector4 pos_v, neg_v;
488  pos_v = 0;
489  neg_v = 0;
490 
491  for (int dz = 0; dz <= 1; dz++)
492  {
493  int z = vit.z() * 2 + dz;
494  if (z >= old_pos->getZRes())
495  continue;
496  for (int dy = 0; dy <= 1; dy++)
497  {
498  int y = vit.y() * 2 + dy;
499  if (y >= old_pos->getYRes())
500  continue;
501  for (int dx = 0; dx <= 1; dx++)
502  {
503  int x = vit.x() * 2 + dx;
504  if (x >= old_pos->getXRes())
505  continue;
506 
507  // Because they are already weighted,
508  // it is trivial to blend.
509  // (Maybe even use UT_MipMap here...)
510  pos_v += (*old_pos)(x, y, z);
511  neg_v += (*old_neg)(x, y, z);
512  }
513  }
514  }
515  // Write back.
516  pos->setValue(vit.x(), vit.y(), vit.z(), pos_v);
517  neg->setValue(vit.x(), vit.y(), vit.z(), neg_v);
518  }
519  }
520 
521  UTdebugPrintCd(none,"Build MipMap time:", watch.stop());
522  UTdebugPrintCd(none, "Build div mip map, levels", pos_mip.entries());
523 }
524 
525 static void
526 sop_applyMipMap(const SOP_NodeVerb::CookParms &cookparms,
527  int axis,
528  GEO_PrimVolume *prim_vel, UT_VoxelArrayF *vel,
529  const UT_VoxelArrayF *active,
530  const UT_Array<UT_VoxelArrayV4 *> &pos_mip,
531  const UT_Array<UT_VoxelArrayV4 *> &neg_mip)
532 {
533  auto &&sopparms = cookparms.parms<SOP_VolumeProjectParms>();
534 
535  UT_StopWatch watch;
536  watch.start();
537 
538  GEO_PrimVolumeXform velxform = prim_vel->getIndexSpaceTransform(*vel);
539 
540  float mipmagic = sopparms.getMIPMagic();
541  mipmagic = 1 / mipmagic;
542 
543  UT_Vector3 voxelsize = prim_vel->getVoxelSize();
544 
545  // See sop_applyMipMap4 for derivation.
546  mipmagic *= voxelsize.x();
547  mipmagic /= 4 * M_PI;
548 
550  [&](const UT_BlockedRange<exint> &r)
551  {
552  exint curtile = r.begin();
554  vit.setLinearTile(curtile, vel);
555  for (vit.rewind(); !vit.atEnd(); vit.advance())
556  {
557  if (active)
558  {
559  if (active->getValue(vit.x(), vit.y(), vit.z()) < 0.5)
560  {
561  continue;
562  }
563  }
564 
565  UT_Vector3 velpos = velxform.fromVoxelSpace(UT_Vector3(vit.x(), vit.y(), vit.z()));
566 
567  bool inspect = false;
568  if (vit.x() == 21 &&
569  vit.y() == 11 &&
570  vit.z() == 13)
571  {
572  inspect = false;
573  }
574 
575  // INCLUSIVE range of things already computed
576  UT_Vector3I minvalid, maxvalid;
577 
578  // Our LUT has already fully computed any of our tiles touched
579  // by -rad .. rad inclusive.
580  minvalid.x() = vit.x() - sopparms.getLUTRad();
581  maxvalid.x() = vit.x() + sopparms.getLUTRad();
582  minvalid.y() = vit.y() - sopparms.getLUTRad();
583  maxvalid.y() = vit.y() + sopparms.getLUTRad();
584  minvalid.z() = vit.z() - sopparms.getLUTRad();
585  maxvalid.z() = vit.z() + sopparms.getLUTRad();
586 
587  // It is correct to reduce this & round to zero. If the min
588  // is short, it would have been expanded, and if the max does
589  // not end with a 1, it would have been expanded.
590  minvalid.x() /= 2;
591  minvalid.y() /= 2;
592  minvalid.z() /= 2;
593  maxvalid.x() /= 2;
594  maxvalid.y() /= 2;
595  maxvalid.z() /= 2;
596 
597  if (inspect)
598  UTdebugPrintCd(none, "VIT:", vit.x(), vit.y(), vit.z());
599  if (inspect)
600  UTdebugPrintCd(none, "Already computed:", minvalid, maxvalid);
601 
602  if (inspect)
603  UTdebugPrintCd(none, "Vel pos", velpos);
604 
605  float delta = 0;
606  for (int pass = 0; pass < pos_mip.entries(); pass++)
607  {
608  auto && pos = pos_mip(pass);
609  auto && neg = neg_mip(pass);
610 
611  if (inspect)
612  UTdebugPrintCd(none, "pass", pass);
613 
614  // The minvalid now contains the inclusive box of all
615  // of our tile sthat are already computed.
616  // We need to find out what 6x6 neighbour hood in our
617  // box we need to compute for the next level to be happy.
618  //
619  // For the next level to be happy, we want a 3x3 neighbourhood
620  // centered on vit to already be evaluated. So if vit is 0,
621  // we want -1, 0, 1. Then, in local space, this is -2, 3
622  // as the inclusive range.
623  // | . -1 . | . 0 . | . 1 . |
624  // | -2 |-1 | 0 | 1 | 2 | 3 |
625  UT_Vector3I mingoal, maxgoal;
626  mingoal.x() = (vit.x() >> (pass+2)) - 1;
627  mingoal.y() = (vit.y() >> (pass+2)) - 1;
628  mingoal.z() = (vit.z() >> (pass+2)) - 1;
629  maxgoal.x() = (vit.x() >> (pass+2)) + 1;
630  maxgoal.y() = (vit.y() >> (pass+2)) + 1;
631  maxgoal.z() = (vit.z() >> (pass+2)) + 1;
632  // Convert from parent's space to ours.
633  mingoal *= 2;
634  maxgoal *= 2;
635  maxgoal += 1; // Inclusive.
636 
637  if (inspect)
638  UTdebugPrintCd(none, "Need to compute", mingoal, maxgoal);
639 
640  // Clamp.
641  mingoal.x() = SYSmax(mingoal.x(), 0);
642  mingoal.y() = SYSmax(mingoal.y(), 0);
643  mingoal.z() = SYSmax(mingoal.z(), 0);
644  maxgoal.x() = SYSmin(maxgoal.x(), pos->getXRes()-1);
645  maxgoal.y() = SYSmin(maxgoal.y(), pos->getYRes()-1);
646  maxgoal.z() = SYSmin(maxgoal.z(), pos->getZRes()-1);
647 
648  // Final pass needs to compute everything.
649  if (pass == pos_mip.entries()-1)
650  {
651  mingoal.x() = 0;
652  mingoal.y() = 0;
653  mingoal.z() = 0;
654  maxgoal.x() = pos->getXRes()-1;
655  maxgoal.y() = pos->getYRes()-1;
656  maxgoal.z() = pos->getZRes()-1;
657  }
658 
659  if (inspect)
660  UTdebugPrintCd(none, "Clamped to compute", mingoal, maxgoal);
661 
662  // Inclusive loop
663  for (int z = mingoal.z(); z <= maxgoal.z(); z++)
664  {
665  bool ztest = (z >= minvalid.z() && z <= maxvalid.z());
666 
667  for (int y = mingoal.y(); y <= maxgoal.y(); y++)
668  {
669  bool ytest = (y >= minvalid.y() && y <= maxvalid.y());
670  for (int x = mingoal.x(); x <= maxgoal.x(); x++)
671  {
672  bool xtest = (x >= minvalid.x() && x <= maxvalid.x());
673 
674  if (ztest && ytest && xtest)
675  {
676  // Already computed at base level
677  // (should be about 27 out of 216.)
678  continue;
679  }
680 
681  // Read our two dipoles, normalize, and compute
682  // a value.
683  UT_Vector4 pos_v = (*pos)(x, y, z);
684  if (pos_v.w())
685  {
686  UT_Vector3 p;
687  p = UT_Vector3(pos_v);
688  p /= pos_v.w();
689 
690  if (inspect)
691  UTdebugPrintCd(none,"Positive pole at", p, "weight", pos_v.w());
692 
693  // We now have the external position p,
694  // the local @P equivalent of velpos
695  p -= velpos;
696 
697  float displen = p.length();
698  float paxis = p(axis);
699 
700  // We want to divide normalized disp by
701  // r^2, so this comes to r^3
702  paxis /= (displen*displen*displen);
703  // Scale by magic
704  paxis *= mipmagic;
705 
706  delta += pos_v.w() * paxis;
707  }
708  UT_Vector4 neg_v = (*neg)(x, y, z);
709  if (neg_v.w())
710  {
711  UT_Vector3 p;
712  p = UT_Vector3(neg_v);
713  p /= neg_v.w();
714 
715  if (inspect)
716  UTdebugPrintCd(none,"Negative pole at", p, "weight", pos_v.w());
717  // We now have the external position p,
718  // the local @P equivalent of velpos
719  p -= velpos;
720 
721  float displen = p.length();
722  float paxis = p(axis);
723 
724  // We want to divide normalized disp by
725  // r^2, so this comes to r^3
726  paxis /= (displen*displen*displen);
727  // Scale by magic
728  paxis *= mipmagic;
729 
730  delta -= neg_v.w() * paxis;
731  }
732  }
733  }
734  }
735 
736  // We now have [mingoal..maxgoal] computed, so update our valid
737  // list.
738  minvalid = mingoal;
739  maxvalid = maxgoal;
740  minvalid.x() /= 2;
741  minvalid.y() /= 2;
742  minvalid.z() /= 2;
743  maxvalid.x() /= 2;
744  maxvalid.y() /= 2;
745  maxvalid.z() /= 2;
746  }
747 
748  // If we ended up with a delta, update our current voxel.
749  if (delta)
750  {
751  vit.setValue(vit.getValue() + delta);
752  }
753  }
754  });
755 
756  UTdebugPrintCd(none,"MipMap Time:", watch.stop());
757 }
758 
759 static void
760 sop_applyMipMap4(const SOP_NodeVerb::CookParms &cookparms,
761  int axis,
762  GEO_PrimVolume *prim_vel, UT_VoxelArrayF *vel,
763  const UT_VoxelArrayF *active,
764  const UT_Array<UT_VoxelArrayV4 *> &pos_mip,
765  const UT_Array<UT_VoxelArrayV4 *> &neg_mip)
766 {
767  auto &&sopparms = cookparms.parms<SOP_VolumeProjectParms>();
768 
769  GEO_PrimVolumeXform velxform = prim_vel->getIndexSpaceTransform(*vel);
770 
771  float mipmagic = sopparms.getMIPMagic();
772  mipmagic = 1 / mipmagic;
773 
774  // We compute 1/r^2, but this is with normalized voxels.
775  // So must apply voxel^2 to normalize.
776  // Divergence is normalized as well, however, so we cancel out
777  // one voxel size from this.
778  // Finally, surface of a sphere is 4 * M_PI.
779 
780  UT_Vector3 voxelsize = prim_vel->getVoxelSize();
781 
782  mipmagic *= voxelsize.x();
783 
784  // Unit of divergence applied to surface of a sphere gives 4 PI
785  mipmagic /= 4 * M_PI;
786 
787  UT_StopWatch watch;
788  watch.start();
789 
791  [&](const UT_BlockedRange<exint> &r)
792  {
793  exint curtile = r.begin();
795  vit.setLinearTile(curtile, vel);
796  for (vit.rewind(); !vit.atEnd(); vit.advance())
797  {
798  if (active)
799  {
800  if (active->getValue(vit.x(), vit.y(), vit.z()) < 0.5)
801  {
802  continue;
803  }
804  }
805 
806  UT_Vector3 velpos = velxform.fromVoxelSpace(UT_Vector3(vit.x(), vit.y(), vit.z()));
807 
808  bool inspect = false;
809  if (vit.x() == 21 &&
810  vit.y() == 11 &&
811  vit.z() == 13)
812  {
813  inspect = false;
814  }
815 
816  // INCLUSIVE range of things already computed
817  UT_Vector3I minvalid, maxvalid;
818 
819  // Our LUT has already fully computed any of our tiles touched
820  // by -rad .. rad inclusive.
821  minvalid.x() = vit.x() - sopparms.getLUTRad();
822  maxvalid.x() = vit.x() + sopparms.getLUTRad();
823  minvalid.y() = vit.y() - sopparms.getLUTRad();
824  maxvalid.y() = vit.y() + sopparms.getLUTRad();
825  minvalid.z() = vit.z() - sopparms.getLUTRad();
826  maxvalid.z() = vit.z() + sopparms.getLUTRad();
827 
828  // It is correct to reduce this & round to zero. If the min
829  // is short, it would have been expanded, and if the max does
830  // not end with a 1, it would have been expanded.
831  minvalid.x() /= 2;
832  minvalid.y() /= 2;
833  minvalid.z() /= 2;
834  maxvalid.x() /= 2;
835  maxvalid.y() /= 2;
836  maxvalid.z() /= 2;
837 
838  if (inspect)
839  UTdebugPrintCd(none, "VIT:", vit.x(), vit.y(), vit.z());
840  if (inspect)
841  UTdebugPrintCd(none, "Already computed:", minvalid, maxvalid);
842 
843  if (inspect)
844  UTdebugPrintCd(none, "Vel pos", velpos);
845 
846  float delta = 0;
847  for (int pass = 0; pass < pos_mip.entries(); pass++)
848  {
849  auto && pos = pos_mip(pass);
850  auto && neg = neg_mip(pass);
851 
852  if (inspect)
853  UTdebugPrintCd(none, "pass", pass);
854 
855  // The minvalid now contains the inclusive box of all
856  // of our tile sthat are already computed.
857  // We need to find out what 4x4 neighbour hood in our
858  // box we need to compute for the next level to be happy.
859  //
860  // For the next level to be happy, we want a 2x2 neighbourhood
861  // so vit is closest to that neighbours node.
862  // We want a [-1, 0] if we will round down and
863  // [0, 1] if we will round up.
864  // So start iwth [-1, 0] and add one if vit will round up,
865  // which is if its first decimal bit is 1.
866  UT_Vector3I mingoal, maxgoal;
867  mingoal.x() = (vit.x() >> (pass+2)) - 1;
868  mingoal.y() = (vit.y() >> (pass+2)) - 1;
869  mingoal.z() = (vit.z() >> (pass+2)) - 1;
870  maxgoal.x() = (vit.x() >> (pass+2));
871  maxgoal.y() = (vit.y() >> (pass+2));
872  maxgoal.z() = (vit.z() >> (pass+2));
873  if ( (vit.x() >> (pass+1)) & 1)
874  {
875  // Round up.
876  mingoal.x()++;
877  maxgoal.x()++;
878  }
879  if ( (vit.y() >> (pass+1)) & 1)
880  {
881  // Round up.
882  mingoal.y()++;
883  maxgoal.y()++;
884  }
885  if ( (vit.z() >> (pass+1)) & 1)
886  {
887  // Round up.
888  mingoal.z()++;
889  maxgoal.z()++;
890  }
891  // Convert from parent's space to ours.
892  mingoal *= 2;
893  maxgoal *= 2;
894  maxgoal += 1; // Inclusive.
895 
896  if (inspect)
897  UTdebugPrintCd(none, "Need to compute", mingoal, maxgoal);
898 
899  // Clamp.
900  mingoal.x() = SYSmax(mingoal.x(), 0);
901  mingoal.y() = SYSmax(mingoal.y(), 0);
902  mingoal.z() = SYSmax(mingoal.z(), 0);
903  maxgoal.x() = SYSmin(maxgoal.x(), pos->getXRes()-1);
904  maxgoal.y() = SYSmin(maxgoal.y(), pos->getYRes()-1);
905  maxgoal.z() = SYSmin(maxgoal.z(), pos->getZRes()-1);
906 
907  // Final pass needs to compute everything.
908  if (pass == pos_mip.entries()-1)
909  {
910  mingoal.x() = 0;
911  mingoal.y() = 0;
912  mingoal.z() = 0;
913  maxgoal.x() = pos->getXRes()-1;
914  maxgoal.y() = pos->getYRes()-1;
915  maxgoal.z() = pos->getZRes()-1;
916  }
917 
918  if (inspect)
919  UTdebugPrintCd(none, "Clamped to compute", mingoal, maxgoal);
920 
921  // Inclusive loop
922  for (int z = mingoal.z(); z <= maxgoal.z(); z++)
923  {
924  bool ztest = (z >= minvalid.z() && z <= maxvalid.z());
925 
926  for (int y = mingoal.y(); y <= maxgoal.y(); y++)
927  {
928  bool ytest = (y >= minvalid.y() && y <= maxvalid.y());
929  for (int x = mingoal.x(); x <= maxgoal.x(); x++)
930  {
931  bool xtest = (x >= minvalid.x() && x <= maxvalid.x());
932 
933  if (ztest && ytest && xtest)
934  {
935  // Already computed at base level
936  // (should be about 8 out of 64.)
937  continue;
938  }
939 
940  // Read our two dipoles, normalize, and compute
941  // a value.
942  UT_Vector4 pos_v = (*pos)(x, y, z);
943  if (pos_v.w())
944  {
945  UT_Vector3 p;
946  p = UT_Vector3(pos_v);
947  p /= pos_v.w();
948 
949  if (inspect)
950  UTdebugPrintCd(none,"Positive pole at", p, "weight", pos_v.w());
951 
952  // We now have the external position p,
953  // the local @P equivalent of velpos
954  p -= velpos;
955 
956  float displen = p.length();
957  float paxis = p(axis);
958 
959  // We want to divide normalized disp by
960  // r^2, so this comes to r^3
961  paxis /= (displen*displen*displen);
962  // Scale by magic
963  paxis *= mipmagic;
964 
965  delta += pos_v.w() * paxis;
966  }
967  UT_Vector4 neg_v = (*neg)(x, y, z);
968  if (neg_v.w())
969  {
970  UT_Vector3 p;
971  p = UT_Vector3(neg_v);
972  p /= neg_v.w();
973 
974  if (inspect)
975  UTdebugPrintCd(none,"Negative pole at", p, "weight", pos_v.w());
976  // We now have the external position p,
977  // the local @P equivalent of velpos
978  p -= velpos;
979 
980  float displen = p.length();
981  float paxis = p(axis);
982 
983  // We want to divide normalized disp by
984  // r^2, so this comes to r^3
985  paxis /= (displen*displen*displen);
986  // Scale by magic
987  paxis *= mipmagic;
988 
989  delta -= neg_v.w() * paxis;
990  }
991  }
992  }
993  }
994 
995  // We now have [mingoal..maxgoal] computed, so update our valid
996  // list.
997  minvalid = mingoal;
998  maxvalid = maxgoal;
999  minvalid.x() /= 2;
1000  minvalid.y() /= 2;
1001  minvalid.z() /= 2;
1002  maxvalid.x() /= 2;
1003  maxvalid.y() /= 2;
1004  maxvalid.z() /= 2;
1005  }
1006 
1007  // If we ended up with a delta, update our current voxel.
1008  if (delta)
1009  {
1010  vit.setValue(vit.getValue() + delta);
1011  }
1012  }
1013  });
1014 
1015  UTdebugPrintCd(none,"MipMap4 Time:", watch.stop());
1016 }
1017 
1018 
1019 void
1021 {
1022  auto &&sopparms = cookparms.parms<SOP_VolumeProjectParms>();
1023  GU_Detail *gdp = cookparms.gdh().gdpNC();
1024 
1025  UT_Interrupt *boss = UTgetInterrupt();
1026  const GA_PrimitiveGroup *velgrp;
1027  const GA_PrimitiveGroup *divgrp;
1028  const GA_PrimitiveGroup *lutgrp;
1029  const GA_PrimitiveGroup *activegrp;
1030  UT_Array<GEO_PrimVolume *> velx, vely, velz;
1033  GOP_Manager gop;
1034 
1035  velgrp = 0;
1036  if (sopparms.getGroup().isstring())
1037  {
1038  velgrp = gop.parsePrimitiveGroups(sopparms.getGroup(),
1039  GOP_Manager::GroupCreator(gdp, false),
1040  /*numok=*/true,
1041  /*ordered=*/true);
1042  if (!velgrp)
1043  {
1044  cookparms.sopAddWarning(SOP_ERR_BADGROUP, sopparms.getGroup());
1045  return;
1046  }
1047  }
1048  notifyGroupParmListeners(cookparms.getNode(), 0, -1, gdp, velgrp);
1049  cookparms.selectInputGroup(velgrp, GA_GROUP_PRIMITIVE);
1050 
1051  divgrp = 0;
1052  if (sopparms.getDivGroup().isstring())
1053  {
1054  divgrp = gop.parsePrimitiveGroups(sopparms.getDivGroup(),
1055  GOP_Manager::GroupCreator(cookparms.inputGeo(1)),
1056  /*numok=*/true,
1057  /*ordered=*/true);
1058  if (!divgrp)
1059  {
1060  cookparms.sopAddWarning(SOP_ERR_BADGROUP, sopparms.getDivGroup());
1061  return;
1062  }
1063  }
1064 
1065  lutgrp = 0;
1066  if (sopparms.getLUTGroup().isstring())
1067  {
1068  divgrp = gop.parsePrimitiveGroups(sopparms.getLUTGroup(),
1069  GOP_Manager::GroupCreator(cookparms.inputGeo(2)),
1070  /*numok=*/true,
1071  /*ordered=*/true);
1072  if (!lutgrp)
1073  {
1074  cookparms.sopAddWarning(SOP_ERR_BADGROUP, sopparms.getLUTGroup());
1075  return;
1076  }
1077  }
1078 
1079  activegrp = 0;
1080  if (sopparms.getActiveGroup().isstring())
1081  {
1082  activegrp = gop.parsePrimitiveGroups(sopparms.getActiveGroup(),
1083  GOP_Manager::GroupCreator(cookparms.inputGeo(3)),
1084  /*numok=*/true,
1085  /*ordered=*/true);
1086  if (!activegrp)
1087  {
1088  cookparms.sopAddWarning(SOP_ERR_BADGROUP, sopparms.getActiveGroup());
1089  return;
1090  }
1091  }
1092 
1093  GEO_PrimVolume *vel[3] = { 0, 0, 0 };
1094 
1095  // Each velocity triple is collated.
1096  GEO_Primitive *prim;
1097  int numvel = 0;
1098  GA_FOR_ALL_OPT_GROUP_PRIMITIVES(gdp, velgrp, prim)
1099  {
1100  if (prim->getTypeId() == GEO_PRIMVOLUME)
1101  {
1102  vel[numvel++] = (GEO_PrimVolume *)prim;
1103 
1104  if (numvel == 3)
1105  {
1106  // Complete set
1107  velx.append(vel[0]);
1108  vely.append(vel[1]);
1109  velz.append(vel[2]);
1110  numvel = 0;
1111  }
1112  }
1113  }
1114 
1115  // Check to see if we have a dangling pair.
1116  if (numvel)
1117  {
1118  cookparms.sopAddWarning(SOP_MESSAGE, "Unmatched veloicty volume ignored; provide matching triples of velocity volumes.");
1119  }
1120 
1121  const GEO_Primitive *cprim;
1122  GA_FOR_ALL_OPT_GROUP_PRIMITIVES(cookparms.inputGeo(1), divgrp, cprim)
1123  {
1124  if (cprim->getTypeId() == GEO_PRIMVOLUME)
1125  {
1126  div.append((const GEO_PrimVolume *) cprim);
1127  }
1128  }
1129 
1130  if (cookparms.inputGeo(3))
1131  {
1132  GA_FOR_ALL_OPT_GROUP_PRIMITIVES(cookparms.inputGeo(3), activegrp, cprim)
1133  {
1134  if (cprim->getTypeId() == GEO_PRIMVOLUME)
1135  {
1136  active.append((const GEO_PrimVolume *) cprim);
1137  }
1138  }
1139  }
1140 
1141  const GEO_PrimVolume *lut[3] = { 0, 0, 0 };
1142  int numlut = 0;
1143  GA_FOR_ALL_OPT_GROUP_PRIMITIVES(cookparms.inputGeo(2), lutgrp, cprim)
1144  {
1145  if (cprim->getTypeId() == GEO_PRIMVOLUME)
1146  {
1147  lut[numlut++] = (const GEO_PrimVolume *) cprim;
1148 
1149  if (numlut == 3)
1150  break;
1151  }
1152  }
1153 
1154  if (numlut < 3)
1155  {
1156  cookparms.sopAddError(SOP_MESSAGE, "Insufficient lut volume specified.");
1157  return;
1158  }
1159 
1160  if (velx.size() != div.size())
1161  {
1162  cookparms.sopAddWarning(SOP_MESSAGE, "Unmatched sets of velocity and divergence volumes specified. Unmatched ignored.");
1163  }
1164 
1165 
1166  if (active.size() && (active.size() != div.size()))
1167  {
1168  cookparms.sopAddError(SOP_MESSAGE, "Unmatched sets of divergence and active volumes specified. Abandoning.");
1169  return;
1170  }
1171 
1172  int npass = SYSmin(velx.size(), div.size());
1173 
1174 
1175  for (int pass = 0; pass < npass; pass++)
1176  {
1177  if (boss->opInterrupt())
1178  break;
1179 
1180  UT_VoxelArrayReadHandleF divh = div(pass)->getVoxelHandle();
1181  UT_VoxelArrayReadHandleF activeh;
1182  UT_VoxelArrayReadHandleF lutxh = lut[0]->getVoxelHandle();
1183  UT_VoxelArrayReadHandleF lutyh = lut[1]->getVoxelHandle();
1184  UT_VoxelArrayReadHandleF lutzh = lut[2]->getVoxelHandle();
1185 
1186  UT_VoxelArrayWriteHandleF vxh = velx(pass)->getVoxelWriteHandle();
1187  UT_VoxelArrayWriteHandleF vyh = vely(pass)->getVoxelWriteHandle();
1188  UT_VoxelArrayWriteHandleF vzh = velz(pass)->getVoxelWriteHandle();
1189 
1190  const UT_VoxelArrayF *activev = 0;
1191  if (pass < active.size())
1192  {
1193  activeh = active(pass)->getVoxelHandle();
1194  activev = &*activeh;
1195  }
1196 
1197  // First, we apply our LUT approximation for the near field.
1198  sop_applyVelLUT(cookparms, &*vxh, &*divh, activev, &*lutxh, velx(pass)->getVoxelSize());
1199  sop_applyVelLUT(cookparms, &*vyh, &*divh, activev, &*lutyh, vely(pass)->getVoxelSize());
1200  sop_applyVelLUT(cookparms, &*vzh, &*divh, activev, &*lutzh, velz(pass)->getVoxelSize());
1201 
1202  if (sopparms.getDoMIP())
1203  {
1204  UT_Array<UT_VoxelArrayV4 *> pos_mip, neg_mip;
1205 
1206  sop_buildMipMap(pos_mip, neg_mip, div(pass));
1207 
1208  if (sopparms.getMipBy4())
1209  {
1210  sop_applyMipMap4(cookparms, 0,
1211  velx(pass), &*vxh, activev,
1212  pos_mip, neg_mip);
1213  sop_applyMipMap4(cookparms, 1,
1214  vely(pass), &*vyh, activev,
1215  pos_mip, neg_mip);
1216  sop_applyMipMap4(cookparms, 2,
1217  velz(pass), &*vzh, activev,
1218  pos_mip, neg_mip);
1219  }
1220  else
1221  {
1222  sop_applyMipMap(cookparms, 0,
1223  velx(pass), &*vxh, activev,
1224  pos_mip, neg_mip);
1225  sop_applyMipMap(cookparms, 1,
1226  vely(pass), &*vyh, activev,
1227  pos_mip, neg_mip);
1228  sop_applyMipMap(cookparms, 2,
1229  velz(pass), &*vzh, activev,
1230  pos_mip, neg_mip);
1231  }
1232 
1233  for (auto && map : pos_mip)
1234  delete map;
1235  for (auto && map : neg_mip)
1236  delete map;
1237  }
1238  }
1239  gdp->bumpAllDataIds();
1240 }
static PRM_ChoiceList primGroupMenu
Definition: SOP_Node.h:1189
int x() const
Retrieve the current location of the iterator.
#define SYSmax(a, b)
Definition: SYS_Math.h:1538
T & last()
Definition: UT_Array.h:796
GA_API const UT_StringHolder div
SOP_Node * getNode() const
Definition: SOP_NodeVerb.h:347
#define UTdebugPrintCd(...)
Definition: UT_Debug.h:147
bool atEnd() const
Returns true if we have iterated over all of the voxels.
void setChoiceListPtr(const UT_StringRef &name, PRM_ChoiceList *list)
UT_ErrorSeverity sopAddWarning(int code, const char *msg=0, const UT_SourceLocation *loc=0) const
Definition: SOP_NodeVerb.h:498
GOP_GroupParse::GroupCreator GroupCreator
Definition: GOP_Manager.h:35
void setValue(UT_Vector3I index, T value)
constexpr SYS_FORCE_INLINE T & y() noexcept
Definition: UT_Vector4.h:493
void UTparallelForEachNumber(IntType nitems, const Body &body, const bool force_use_task_scope=true)
#define M_PI
Definition: fmath.h:90
UT_Vector3T< float > UT_Vector3
GLdouble GLdouble GLdouble z
Definition: glcorearb.h:848
constexpr SYS_FORCE_INLINE T & z() noexcept
Definition: UT_Vector3.h:667
int64 exint
Definition: SYS_Types.h:125
UT_ErrorSeverity
Definition: UT_Error.h:25
SYS_FORCE_INLINE const GA_PrimitiveTypeId & getTypeId() const
Definition: GA_Primitive.h:177
UT_VoxelArray< UT_Vector4 > UT_VoxelArrayV4
GEO_PrimVolumeXform getIndexSpaceTransform() const
GLint y
Definition: glcorearb.h:103
constexpr SYS_FORCE_INLINE T length() const noexcept
Definition: UT_Vector3.h:361
bool addOperator(OP_Operator *op, std::ostream *err=nullptr)
bool atEnd() const
Returns true if we have iterated over all of the voxels.
exint size() const
Definition: UT_Array.h:646
UT_Vector3 fromVoxelSpace(UT_Vector3 pos) const
void size(int xres, int yres, int zres, bool reset=true)
#define GA_FOR_ALL_OPT_GROUP_PRIMITIVES(gdp, grp, prim)
Definition: GA_GBMacros.h:81
constexpr SYS_FORCE_INLINE T & x() noexcept
Definition: UT_Vector4.h:491
UT_ErrorSeverity sopAddError(int code, const char *msg=0, const UT_SourceLocation *loc=0) const
Definition: SOP_NodeVerb.h:505
void rewind()
Resets the iterator to point to the first voxel.
const SOP_NodeVerb * cookVerb() const overridefinal
virtual CookMode cookMode(const SOP_NodeParms *parms) const
int opInterrupt(int percent=-1)
const T & parms() const
Definition: SOP_NodeVerb.h:412
Constructs a PRM_Template list from an embedded .ds file or an istream.
void advance()
Advances the iterator to point to the next voxel.
void setValue(T t) const
Sets the voxel we are currently pointing to the given value.
void rewind()
Resets the iterator to point to the first voxel.
int getYRes() const
int numTiles() const
constexpr SYS_FORCE_INLINE T & z() noexcept
Definition: UT_Vector4.h:495
PRM_Template * templates() const
const GA_PrimitiveGroup * parsePrimitiveGroups(const char *pat, const GroupCreator &creator, bool numok=true, bool ordered=false, bool strict=false, GA_Index prim_offset=GA_Index(0), ParseInfo *info=0)
OP_ERROR cookMyselfAsVerb(OP_Context &context)
void selectInputGroup(const GA_Group *group, GA_GroupType grouptype) const
Definition: SOP_NodeVerb.h:571
int getXRes() const
GLuint const GLchar * name
Definition: glcorearb.h:786
GLint GLenum GLint x
Definition: glcorearb.h:409
GU_Detail * gdpNC()
GLenum GLenum GLsizei void * table
Definition: glad.h:5129
exint append()
Definition: UT_Array.h:142
void newSopOperator(OP_OperatorTable *table)
exint entries() const
Alias of size(). size() is preferred.
Definition: UT_Array.h:648
int getZRes() const
void setLinearTile(exint lineartilenum, UT_VoxelArray< T > *array)
T getValue(int x, int y, int z) const
virtual SOP_NodeParms * allocParms() const
UT_API UT_Interrupt * UTgetInterrupt()
Obtain global UT_Interrupt singleton.
constexpr SYS_FORCE_INLINE T & w() noexcept
Definition: UT_Vector4.h:497
GLuint GLfloat * val
Definition: glcorearb.h:1608
const GU_Detail * inputGeo(exint idx) const
Definition: SOP_NodeVerb.h:376
UT_Vector3 getVoxelSize() const
Returns the length of the voxel when you take an x, y, and z step.
OP_ERROR cookMySop(OP_Context &context) overridefinal
#define UT_ASSERT(ZZ)
Definition: UT_Assert.h:156
virtual UT_StringHolder name() const
fpreal64 stop()
GLboolean r
Definition: glcorearb.h:1222
UT_VoxelArrayHandleF getVoxelHandle() const
#define MIN_DIV
constexpr SYS_FORCE_INLINE T & y() noexcept
Definition: UT_Vector3.h:665
#define SYSmin(a, b)
Definition: SYS_Math.h:1539
Declare prior to use.
GU_DetailHandle & gdh() const
The initial state of gdh depends on the cookMode()
Definition: SOP_NodeVerb.h:341
virtual void cook(const CookParms &cookparms) const
Compute the output geometry.
void advance()
Advances the iterator to point to the next voxel.
constexpr SYS_FORCE_INLINE T & x() noexcept
Definition: UT_Vector3.h:663