HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SOP_SParticle.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  * Simple Particle demonstration code. Includes sample collision detection...
27  */
28 
29 #include "SOP_SParticle.h"
30 
31 #include <GU/GU_Detail.h>
32 #include <GU/GU_RayIntersect.h>
33 
34 #include <GEO/GEO_PrimPart.h>
35 
36 #include <OP/OP_AutoLockInputs.h>
37 #include <OP/OP_Director.h>
38 #include <OP/OP_Operator.h>
39 #include <OP/OP_OperatorTable.h>
40 
41 #include <PRM/PRM_Include.h>
42 
43 #include <UT/UT_DSOVersion.h>
44 #include <UT/UT_Interrupt.h>
45 #include <UT/UT_Vector3.h>
46 #include <UT/UT_Vector4.h>
47 
48 using namespace HDK_Sample;
49 
50 void
52 {
53  table->addOperator(new OP_Operator(
54  "hdk_sparticle",
55  "Simple Particle",
58  1, // Min required sources
59  2, // Maximum sources
60  0));
61 }
62 
63 // The names here have to match the inline evaluation functions
64 static PRM_Name names[] = {
65  PRM_Name("reset", "Reset Frame"),
66  PRM_Name("birth", "Birth Rate"),
67  PRM_Name("force", "Force"),
68 };
69 
70 static PRM_Default birthRate(10);
71 
74  PRM_Template(PRM_INT, 1, &names[0], PRMoneDefaults),
75  PRM_Template(PRM_INT_J, 1, &names[1], &birthRate),
76  PRM_Template(PRM_XYZ_J, 3, &names[2]),
77  PRM_Template(),
78 };
79 
80 int *
81 SOP_SParticle::myOffsets = 0;
82 
83 OP_Node *
85 {
86  return new SOP_SParticle(net, name, op);
87 }
88 
90  : SOP_Node(net, name, op)
91  , mySystem(NULL)
92 {
93  // Make sure that our offsets are allocated. Here we allow up to 32
94  // parameters, no harm in over allocating. The definition for this
95  // function is in OP/OP_Parameters.h
96  if (!myOffsets)
97  myOffsets = allocIndirect(32);
98 
99  // Now, flag that nothing has been built yet...
100  myVelocity.clear();
101 }
102 
104 
105 void
107 {
108  // Strictly speaking, we should be using mySource->getPointMap() for the
109  // initial invalid point, but mySource may be NULL.
110  GA_Offset srcptoff = GA_INVALID_OFFSET;
111  GA_Offset vtxoff = mySystem->giveBirth();
112  if (mySource)
113  {
114  if (mySourceNum >= mySource->getPointMap().indexSize())
115  mySourceNum = 0;
116  if (mySource->getPointMap().indexSize() > 0) // No points in the input
117  srcptoff = mySource->pointOffset(mySourceNum);
118  mySourceNum++; // Move on to the next source point...
119  }
120  GA_Offset ptoff = gdp->vertexPoint(vtxoff);
121  if (GAisValid(srcptoff))
122  {
123  gdp->setPos3(ptoff, mySource->getPos3(srcptoff));
124  if (mySourceVel.isValid())
125  myVelocity.set(ptoff, mySourceVel.get(srcptoff));
126  else
127  myVelocity.set(ptoff, UT_Vector3(0, 0, 0));
128  }
129  else
130  {
131  gdp->setPos3(ptoff, SYSdrand48()-.5, SYSdrand48()-.5, SYSdrand48()-.5);
132  myVelocity.set(ptoff, UT_Vector3(0, 0, 0));
133  }
134  // First index of the life variable represents how long the particle has
135  // been alive (set to 0).
136  myLife.set(ptoff, 0, 0);
137  // The second index of the life variable represents how long the particle
138  // will live (in frames)
139  myLife.set(ptoff, 1, 30+30*SYSdrand48());
140 }
141 
142 int
144 {
145  float life = myLife.get(ptoff, 0);
146  float death = myLife.get(ptoff, 1);
147  life += 1;
148  myLife.set(ptoff, life, 0); // Store back in point
149  if (life >= death)
150  return 0; // The particle should die!
151 
152  float tinc = 1./30.; // Hardwire 1/30 of a second time inc...
153 
154  // Adjust the velocity (based on the force) - of course, the multiplies
155  // can be pulled out of the loop...
156  UT_Vector3 vel = myVelocity.get(ptoff);
157  vel += tinc*force;
158  myVelocity.set(ptoff, vel);
159 
160  // Now adjust the point positions
161 
162  if (myCollision)
163  {
164  UT_Vector3 dir = vel * tinc;
165 
166  // here, we only allow hits within the length of the velocity vector
167  GU_RayInfo info(dir.normalize());
168 
169  UT_Vector3 start = gdp->getPos3(ptoff);
170  if (myCollision->sendRay(start, dir, info) > 0)
171  return 0; // We hit something, so kill the particle
172  }
173 
174  UT_Vector3 pos = gdp->getPos3(ptoff);
175  pos += tinc*vel;
176  gdp->setPos3(ptoff, pos);
177 
178  return 1;
179 }
180 
181 void
183 {
184  UT_Vector3 force(FX(now), FY(now), FZ(now));
185  int nbirth = BIRTH(now);
186 
187  if (error() >= UT_ERROR_ABORT)
188  return;
189 
190  for (int i = 0; i < nbirth; ++i)
191  birthParticle();
192 
193  for (GA_Size i = 0; i < mySystem->getNumParticles(); i++)
194  {
195  if (!moveParticle(mySystem->vertexPoint(i), force))
196  mySystem->deadParticle(i);
197  }
198  mySystem->deleteDead();
199 }
200 
201 void
203 {
204  if (!gdp) gdp = new GU_Detail;
205 
206  // Check to see if we really need to reset everything
207  if (gdp->getPointMap().indexSize() > 0 || myVelocity.isInvalid())
208  {
209  mySourceNum = 0;
210  gdp->clearAndDestroy();
212  mySystem->clearAndDestroy();
213 
214  // A vector attribute will be transformed correctly by following
215  // SOPs. Use float types for stuff like color...
216  myVelocity = GA_RWHandleV3(gdp->addFloatTuple(GA_ATTRIB_POINT, "v", 3));
217  if (myVelocity.isValid())
218  myVelocity.getAttribute()->setTypeInfo(GA_TYPE_VECTOR);
219  myLife = GA_RWHandleF(gdp->addFloatTuple(GA_ATTRIB_POINT, "life", 2));
220  }
221 }
222 
223 OP_ERROR
225 {
226  // We must lock our inputs before we try to access their geometry.
227  // OP_AutoLockInputs will automatically unlock our inputs when we return.
228  // NOTE: Don't call unlockInputs yourself when using this!
229  OP_AutoLockInputs inputs(this);
230  if (inputs.lock(context) >= UT_ERROR_ABORT)
231  return error();
232 
233  // Now, indicate that we are time dependent (i.e. have to cook every
234  // time the current frame changes).
235  OP_Node::flags().setTimeDep(true);
236 
237  // Channel manager has time info for us
239 
240  // This is the frame that we're cooking at...
241  fpreal currframe = chman->getSample(context.getTime());
242  fpreal reset = RESET(); // Find our reset frame...
243 
244  if (currframe <= reset || !mySystem)
245  {
246  myLastCookTime = reset;
247  initSystem();
248  }
249  else
250  {
251  // Set up the collision detection object
252  const GU_Detail *collision = inputGeo(1, context);
253  if (collision)
254  {
255  myCollision = new GU_RayIntersect;
256  myCollision->init(collision);
257  }
258  else myCollision = 0;
259 
260  // Set up our source information...
261  mySource = inputGeo(0, context);
262  if (mySource)
263  {
264  mySourceVel = GA_ROHandleV3(mySource->findFloatTuple(GA_ATTRIB_POINT, "v", 3));
265 
266  // If there's no velocity, pick up the velocity from the normal
267  if (mySourceVel.isInvalid())
268  mySourceVel = GA_ROHandleV3(mySource->findFloatTuple(GA_ATTRIB_POINT, "N", 3));
269  }
270 
271  // This is where we notify our handles (if any) if the inputs have
272  // changed. This is normally done in cookInputGroups, but since there
273  // is no input group, NULL is passed as the fourth argument.
274  notifyGroupParmListeners(0, -1, mySource, NULL);
275 
276  // Now cook the geometry up to our current time
277  // Here, we could actually re-cook the source input to get a moving
278  // source... But this is just an example ;-)
279 
280  currframe += 0.05; // Add a bit to avoid floating point error
281  while (myLastCookTime < currframe)
282  {
283  // Here we have to convert our frame number to the actual time.
284  timeStep(chman->getTime(myLastCookTime));
285  myLastCookTime += 1;
286  }
287 
288  if (myCollision) delete myCollision;
289 
290  // Set the node selection for the generated particles. This will
291  // highlight all the points generated by this node, but only if the
292  // highlight flag is on and the node is selected.
294  }
295 
296  return error();
297 }
298 
299 const char *
300 SOP_SParticle::inputLabel(unsigned inum) const
301 {
302  switch (inum)
303  {
304  case 0: return "Particle Source Geometry";
305  case 1: return "Collision Object";
306  }
307  return "Unknown source";
308 }
int sendRay(const UT_Vector3 &org, const UT_Vector3 &dir, GU_RayInfo &hitinfo) const
void init(const GU_Detail *gdp, const GA_PrimitiveGroup *group=nullptr, bool picking=false, bool polyline=false, bool harden=false, bool usevisibility=false, bool solidtet=false)
virtual OP_ERROR error()
const OP_NodeFlags & flags() const
Definition: OP_Node.h:1400
OP_ERROR lock(OP_Context &context)
Locks all inputs.
GA_Attribute * addFloatTuple(GA_AttributeOwner owner, GA_AttributeScope scope, const UT_StringHolder &name, int tuple_size, const GA_Defaults &defaults=GA_Defaults(0.0), const UT_Options *creation_args=0, const GA_AttributeOptions *attribute_options=0, GA_Storage storage=GA_STORE_REAL32, const GA_ReuseStrategy &reuse=GA_ReuseStrategy())
static PRM_Template myTemplateList[]
Definition: SOP_SParticle.h:53
fpreal getTime() const
Definition: OP_Context.h:62
void notifyGroupParmListeners(int parm_index, int group_type_index, const GU_Detail *pgdp, const GA_Group *group)
GLuint start
Definition: glcorearb.h:475
int getNumParticles() const
Returns the number of live particles.
Definition: GEO_PrimPart.h:191
void clearAndDestroy()
Clear all the points/primitives out of this detail.
Definition: GEO_Detail.h:267
UT_Vector3T< float > UT_Vector3
UT_ErrorSeverity
Definition: UT_Error.h:25
SOP_SParticle(OP_Network *net, const char *name, OP_Operator *op)
Definition: SOP_SParticle.C:89
bool addOperator(OP_Operator *op, std::ostream *err=nullptr)
SYS_FORCE_INLINE UT_Vector3 getPos3(GA_Offset ptoff) const
The ptoff passed is the point offset.
Definition: GA_Detail.h:185
PRM_API const PRM_Type PRM_XYZ_J
PRM_API const PRM_Type PRM_INT_J
SYS_FORCE_INLINE bool GAisValid(GA_Size v)
Definition: GA_Types.h:655
exint GA_Size
Defines the bit width for index and offset types in GA.
Definition: GA_Types.h:236
GA_ATINumeric * getAttribute() const
Definition: GA_Handle.h:339
GA_ROHandleT< UT_Vector3F > GA_ROHandleV3
Definition: GA_Handle.h:1381
void newSopOperator(OP_OperatorTable *table)
Definition: SOP_SParticle.C:51
#define GA_INVALID_OFFSET
Definition: GA_Types.h:687
GA_Offset giveBirth()
Resurrect a particle or give birth to a new one.
CH_Manager * getChannelManager()
Definition: OP_Director.h:113
GA_RWHandleT< UT_Vector3F > GA_RWHandleV3
Definition: GA_Handle.h:1382
PRM_API const PRM_Type PRM_INT
OP_ERROR cookMySop(OP_Context &context) override
GA_Size GA_Offset
Definition: GA_Types.h:646
void deadParticle(GEO_ParticleVertexIndex vtxindex)
const GA_IndexMap & getPointMap() const
Definition: GA_Detail.h:744
GLboolean reset
Definition: glad.h:5138
fpreal getSample(fpreal t) const
Definition: CH_Manager.h:1239
void clear()
Definition: GA_Handle.h:180
const GU_Detail * inputGeo(int index, OP_Context &)
Definition: SOP_Node.h:1150
SYS_FORCE_INLINE T get(GA_Offset off, int comp=0) const
Definition: GA_Handle.h:203
SYS_FORCE_INLINE GA_Offset vertexPoint(GA_Offset vertex) const
Given a vertex, return the point it references.
Definition: GA_Detail.h:529
SYS_API double SYSdrand48()
GLuint const GLchar * name
Definition: glcorearb.h:786
SYS_FORCE_INLINE bool isInvalid() const
Definition: GA_Handle.h:191
GLenum GLenum GLsizei void * table
Definition: glad.h:5129
GU_Detail * gdp
Definition: SOP_Node.h:1625
SYS_FORCE_INLINE bool isValid() const
Definition: GA_Handle.h:187
const GA_Attribute * findFloatTuple(GA_AttributeOwner owner, GA_AttributeScope scope, const UT_StringRef &name, int min_size=1, int max_size=-1) const
PRM_API PRM_Default PRMoneDefaults[]
SYS_FORCE_INLINE void setPos3(GA_Offset ptoff, const UT_Vector3 &pos)
Set P from a UT_Vector3.
Definition: GA_Detail.h:237
SIM_API const UT_StringHolder force
SYS_FORCE_INLINE void set(GA_Offset off, const T &val) const
Definition: GA_Handle.h:354
void setTimeDep(bool on_off)
Definition: OP_NodeFlags.h:111
SYS_FORCE_INLINE GA_Index indexSize() const
Definition: GA_IndexMap.h:103
fpreal64 fpreal
Definition: SYS_Types.h:277
void clearAndDestroy()
Definition: GEO_PrimPart.h:216
int * allocIndirect(int size=64)
SYS_FORCE_INLINE GEO_Primitive * appendPrimitive(const GA_PrimitiveTypeId &type)
Definition: GEO_Detail.h:1236
OP_API OP_Director * OPgetDirector()
GA_RWHandleT< fpreal32 > GA_RWHandleF
Definition: GA_Handle.h:1355
void deleteDead()
Delete all dead particles.
Data represents a direction vector. Token "vector".
Definition: GA_Types.h:112
int moveParticle(GA_Offset ptoff, const UT_Vector3 &force)
static OP_Node * myConstructor(OP_Network *, const char *, OP_Operator *)
Definition: SOP_SParticle.C:84
SYS_FORCE_INLINE UT_StorageMathFloat_t< T > normalize() noexcept
Definition: UT_Vector3.h:376
void select(GU_SelectionType stype)
GA_API const UT_StringHolder life
SYS_FORCE_INLINE void setTypeInfo(GA_TypeInfo type)
Definition: GA_Attribute.h:260
const char * inputLabel(unsigned idx) const override
SYS_FORCE_INLINE GA_Offset pointOffset(GA_Index index) const
Given a point's index (in append order), return its data offset.
Definition: GA_Detail.h:345
fpreal getTime(fpreal sample) const
Definition: CH_Manager.h:1229
void timeStep(fpreal now)
GA_Offset vertexPoint(GA_Size i) const
Definition: GEO_PrimPart.h:226