Panda3D
spriteParticleRenderer.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file spriteParticleRenderer.cxx
10  * @author charles
11  * @date 2000-07-13
12  */
13 
14 #include "spriteParticleRenderer.h"
15 #include "boundingSphere.h"
16 #include "geomNode.h"
17 #include "sequenceNode.h"
18 #include "nodePath.h"
19 #include "dcast.h"
20 #include "geom.h"
21 #include "geomVertexReader.h"
22 #include "geomVertexWriter.h"
23 #include "renderModeAttrib.h"
24 #include "texMatrixAttrib.h"
25 #include "texGenAttrib.h"
26 #include "textureAttrib.h"
27 #include "textureCollection.h"
28 #include "nodePathCollection.h"
29 #include "indent.h"
30 #include "config_particlesystem.h"
31 #include "pStatTimer.h"
32 
33 using std::max;
34 using std::min;
35 
36 PStatCollector SpriteParticleRenderer::_render_collector("App:Particles:Sprite:Render");
37 
38 /**
39  * constructor
40  */
43  BaseParticleRenderer(PR_ALPHA_NONE),
44  _color(LColor(1.0f, 1.0f, 1.0f, 1.0f)),
45  _height(1.0f),
46  _width(1.0f),
47  _initial_x_scale(1.0f),
48  _final_x_scale(1.0f),
49  _initial_y_scale(1.0f),
50  _final_y_scale(1.0f),
51  _theta(0.0f),
52  _base_y_scale(1.0f),
53  _aspect_ratio(1.0f),
54  _animate_frames_rate(0.0f),
55  _animate_frames_index(0),
56  _animate_x_ratio(false),
57  _animate_y_ratio(false),
58  _animate_theta(false),
59  _alpha_disable(false),
60  _animate_frames(false),
61  _animation_removed(true),
62  _blend_method(PP_BLEND_LINEAR),
63  _color_interpolation_manager(new ColorInterpolationManager(_color)),
64  _pool_size(0) {
65  set_texture(tex);
66  init_geoms();
67 }
68 
69 /**
70  * copy constructor
71  */
75  _anims(copy._anims),
76  _color(copy._color),
77  _height(copy._height),
78  _width(copy._width),
79  _initial_x_scale(copy._initial_x_scale),
80  _final_x_scale(copy._final_x_scale),
81  _initial_y_scale(copy._initial_y_scale),
82  _final_y_scale(copy._final_y_scale),
83  _theta(copy._theta),
84  _base_y_scale(copy._base_y_scale),
85  _aspect_ratio(copy._aspect_ratio),
86  _animate_frames_rate(copy._animate_frames_rate),
87  _animate_frames_index(copy._animate_frames_index),
88  _animate_x_ratio(copy._animate_x_ratio),
89  _animate_y_ratio(copy._animate_y_ratio),
90  _animate_theta(copy._animate_theta),
91  _alpha_disable(copy._alpha_disable),
92  _animate_frames(copy._animate_frames),
93  _animation_removed(true),
94  _blend_method(copy._blend_method),
95  _color_interpolation_manager(copy._color_interpolation_manager),
96  _pool_size(0),
97  _birth_list(copy._birth_list) {
98  init_geoms();
99 }
100 
101 /**
102  * destructor
103  */
107 }
108 
109 /**
110  * child dynamic copy
111  */
114  return new SpriteParticleRenderer(*this);
115 }
116 
117 
118 /**
119  * Pull either a set of textures from a SequenceNode or a single texture from
120  * a GeomNode. This function is called in both set_from_node() and
121  * add_from_node(). Notice the second parameter. This nodepath will
122  * reference the GeomNode holding the first texture in the returned
123  * TextureCollection.
124  */
125 int SpriteParticleRenderer::
126 extract_textures_from_node(const NodePath &node_path, NodePathCollection &np_col, TextureCollection &tex_col) {
127  NodePath tex_node_path = node_path;
128  NodePath geom_node_path;
129 
130  // Look for a sequence node first, in case they want animated texture
131  // sprites
132  if (!tex_node_path.is_empty() && tex_node_path.node()->get_type() != SequenceNode::get_class_type()) {
133  tex_node_path = node_path.find("**/+SequenceNode");
134  }
135 
136  // Nodepath contains a sequence node, attempt to read its textures.
137  if (!tex_node_path.is_empty()) {
138  int frame_count = tex_node_path.get_num_children();
139  // We do it this way in order to preserve the order of the textures in the
140  // sequence. If we use a find_all_textures() that order is lost.
141  for (int i = 0; i < frame_count; ++i) {
142  geom_node_path = tex_node_path.get_child(i);
143  if (!geom_node_path.is_empty()) {
144  // Since this is a SequenceNode, there will be only one texture on
145  // this geom_node_path.
146  tex_col.add_textures_from(geom_node_path.find_all_textures());
147  np_col.add_path(geom_node_path);
148  }
149  }
150  // If unsuccessful, try again as if the node were a normal GeomNode.
151  if (tex_col.get_num_textures() == 0) {
152  geom_node_path = NodePath();
153  tex_col.clear();
154  np_col.clear();
155  }
156  }
157 
158  // If a sequence node is not found, we just want to look for a regular geom
159  // node.
160  if (geom_node_path.is_empty()) {
161  // Find the first GeomNode.
162  if (!node_path.is_empty() && node_path.node()->get_type() != GeomNode::get_class_type()) {
163  geom_node_path = node_path.find("**/+GeomNode");
164  if (geom_node_path.is_empty()) {
165  particlesystem_cat.error();
166  return 0;
167  }
168  } else {
169  geom_node_path = node_path;
170  }
171 
172  // Grab the first texture.
173  tex_col.add_texture(geom_node_path.find_texture("*"));
174  if (tex_col.get_num_textures() < 1) {
175  particlesystem_cat.error()
176  << geom_node_path << " does not contain a texture.\n";
177  return 0;
178  } else {
179  np_col.add_path(geom_node_path);
180  }
181  }
182  return 1;
183 }
184 
185 /**
186  * If the source type is important, use this one.
187  *
188  * model and node should lead to node_path like this: node_path =
189  * loader.loadModel(model).find(node)
190  *
191  * This will remove all previously add textures and resize the renderer to
192  * match the new geometry.
193  */
195 set_from_node(const NodePath &node_path, const std::string &model, const std::string &node, bool size_from_texels) {
196  // Clear all texture information
197  _anims.clear();
198  add_from_node(node_path,model,node,size_from_texels,true);
199 }
200 
201 /**
202  * Sets the properties on this renderer from the geometry referenced by the
203  * indicated NodePath. This should be a reference to a GeomNode or a
204  * SequenceNode; it extracts out the texture and UV range from the node.
205  *
206  * This will remove all previously added textures and animations. It will
207  * also resize the renderer to match this new geometry.
208  *
209  * If node_path refers to a GeomNode(or has one beneath it) the texture, its
210  * size, and UV data will be extracted from that.
211  *
212  * If node_path references a SequenceNode(or has one beneath it) with multiple
213  * GeomNodes beneath it, the size data will correspond only to the first
214  * GeomNode found with a valid texture, while the texture and UV information
215  * will be stored for each individual node.
216  *
217  * If size_from_texels is true, the particle size is based on the number of
218  * texels in the source image; otherwise, it is based on the size of the first
219  * polygon found in the node.
220  *
221  * model and node are the two items used to construct node_path. If the
222  * source type is important, use set_from_node(NodePath,string,string,bool)
223  * instead.
224  */
226 set_from_node(const NodePath &node_path, bool size_from_texels) {
227  nassertv(!node_path.is_empty());
228  // Clear all texture information
229  _anims.clear();
230  add_from_node(node_path,size_from_texels,true);
231 }
232 
233 /**
234  * This will allow the renderer to randomly choose from more than one texture
235  * or sequence at particle birth.
236  *
237  * If the source type is important, use this one.
238  *
239  * model and node should lead to node_path like this: node_path =
240  * loader.loadModel(model).find(node)
241  *
242  * If resize is true, or if there are no textures currently on the renderer,
243  * it will force the renderer to use the size information from this node from
244  * now on. (Default is false)
245  */
247 add_from_node(const NodePath &node_path, const std::string &model, const std::string &node, bool size_from_texels, bool resize) {
248  int anim_count = _anims.size();
249  if (anim_count == 0)
250  resize = true;
251  add_from_node(node_path,size_from_texels,resize);
252  if (anim_count < (int)_anims.size()) {
253  get_last_anim()->set_source_info(model,node);
254  }
255 }
256 
257 /**
258  * This will allow the renderer to randomly choose from more than one texture
259  * or sequence at particle birth.
260  *
261  * If resize is true, or if there are no textures currently on the renderer,
262  * it will force the renderer to use the size information from this node from
263  * now on. (Default is false)
264  */
266 add_from_node(const NodePath &node_path, bool size_from_texels, bool resize) {
267  nassertv(!node_path.is_empty());
268 
269  NodePathCollection np_col;
270  TextureCollection tex_col;
271 
272  if (_anims.empty())
273  resize = true;
274 
275  // Load the found textures into the renderer.
276  if (extract_textures_from_node(node_path,np_col,tex_col)) {
277  pvector< LTexCoord > ll,ur;
278  GeomNode *gnode = nullptr;
279  const Geom *geom;
280  const GeomPrimitive *primitive;
281 
282  for (int i = 0; i < np_col.get_num_paths(); ++i) {
283  // Get the node from which we'll extract the geometry information.
284  gnode = DCAST(GeomNode, np_col[i].node());
285 
286  // Now examine the UV's of the first Geom within the GeomNode.
287  nassertv(gnode->get_num_geoms() > 0);
288  geom = gnode->get_geom(0);
289 
290  bool got_texcoord = false;
291  LTexCoord min_uv(0.0f, 0.0f);
292  LTexCoord max_uv(0.0f, 0.0f);
293 
294  GeomVertexReader texcoord(geom->get_vertex_data(),
295  InternalName::get_texcoord());
296  if (texcoord.has_column()) {
297  for (size_t pi = 0; pi < geom->get_num_primitives(); ++pi) {
298  primitive = geom->get_primitive(pi);
299  for (int vi = 0; vi < primitive->get_num_vertices(); ++vi) {
300  int vert = primitive->get_vertex(vi);
301  texcoord.set_row_unsafe(vert);
302 
303  if (!got_texcoord) {
304  min_uv = max_uv = texcoord.get_data2();
305  got_texcoord = true;
306 
307  } else {
308  const LVecBase2 &uv = texcoord.get_data2();
309 
310  min_uv[0] = min(min_uv[0], uv[0]);
311  max_uv[0] = max(max_uv[0], uv[0]);
312  min_uv[1] = min(min_uv[1], uv[1]);
313  max_uv[1] = max(max_uv[1], uv[1]);
314  }
315  }
316  }
317  }
318 
319  if (got_texcoord) {
320  // We don't really pay attention to orientation of UV's here; a minor
321  // flaw. We assume the minimum is in the lower-left, and the maximum
322  // is in the upper-right.
323  ll.push_back(min_uv);
324  ur.push_back(max_uv);
325  }
326  }
327 
328  _anims.push_back(new SpriteAnim(tex_col,ll,ur));
329 
330  if (resize) {
331  gnode = DCAST(GeomNode, np_col[0].node());
332  geom = gnode->get_geom(0);
333 
334  bool got_vertex = false;
335  LVertex min_xyz(0.0f, 0.0f, 0.0f);
336  LVertex max_xyz(0.0f, 0.0f, 0.0f);
337 
338  GeomVertexReader vertex(geom->get_vertex_data(),
339  InternalName::get_vertex());
340  if (vertex.has_column()) {
341  for (size_t pi = 0; pi < geom->get_num_primitives(); ++pi) {
342  primitive = geom->get_primitive(pi);
343  for (int vi = 0; vi < primitive->get_num_vertices(); ++vi) {
344  int vert = primitive->get_vertex(vi);
345  vertex.set_row_unsafe(vert);
346 
347  if (!got_vertex) {
348  min_xyz = max_xyz = vertex.get_data3();
349  got_vertex = true;
350 
351  } else {
352  const LVecBase3 &xyz = vertex.get_data3();
353 
354  min_xyz[0] = min(min_xyz[0], xyz[0]);
355  max_xyz[0] = max(max_xyz[0], xyz[0]);
356  min_xyz[1] = min(min_xyz[1], xyz[1]);
357  max_xyz[1] = max(max_xyz[1], xyz[1]);
358  min_xyz[2] = min(min_xyz[2], xyz[2]);
359  max_xyz[2] = max(max_xyz[2], xyz[2]);
360  }
361  }
362  }
363  }
364 
365  if (got_vertex) {
366  PN_stdfloat width = max_xyz[0] - min_xyz[0];
367  PN_stdfloat height = max(max_xyz[1] - min_xyz[1],
368  max_xyz[2] - min_xyz[2]);
369 
370  if (size_from_texels) {
371  // If size_from_texels is true, we get the particle size from the
372  // number of texels in the source image.
373  PN_stdfloat y_texels = _anims[0]->get_frame(0)->get_y_size() * fabs(_anims[0]->get_ur(0)[1] - _anims[0]->get_ll(0)[1]);
374  set_size(y_texels * width / height, y_texels);
375  } else {
376  // If size_from_texels is false, we get the particle size from the
377  // size of the polygon.
378  set_size(width, height);
379  }
380 
381  } else {
382  // With no vertices, just punt.
383  set_size(1.0f, 1.0f);
384  }
385  }
386  init_geoms();
387  }
388 }
389 
390 /**
391  * reallocate the vertex pool.
392  */
393 void SpriteParticleRenderer::
394 resize_pool(int new_size) {
395  if (new_size != _pool_size) {
396  _pool_size = new_size;
397  init_geoms();
398  }
399 }
400 
401 /**
402  * initializes everything, called on traumatic events such as construction and
403  * serious particlesystem modifications
404  */
405 void SpriteParticleRenderer::
406 init_geoms() {
407  CPT(RenderState) state = _render_state;
408  SpriteAnim *anim;
409  int anim_count = _anims.size();
410  int i,j;
411 
412  // Setup format
413  PT(GeomVertexArrayFormat) array_format = new GeomVertexArrayFormat
414  (InternalName::get_vertex(), 3, Geom::NT_stdfloat, Geom::C_point,
415  InternalName::get_color(), 1, Geom::NT_packed_dabc, Geom::C_color);
416 
417  if (_animate_theta || _theta != 0.0f) {
418  array_format->add_column
419  (InternalName::get_rotate(), 1, Geom::NT_stdfloat, Geom::C_other);
420  }
421 
422  _base_y_scale = _initial_y_scale;
423  _aspect_ratio = _width / _height;
424 
425  PN_stdfloat final_x_scale = _animate_x_ratio ? _final_x_scale : _initial_x_scale;
426  PN_stdfloat final_y_scale = _animate_y_ratio ? _final_y_scale : _initial_y_scale;
427 
428  if (_animate_y_ratio) {
429  _base_y_scale = max(_initial_y_scale, _final_y_scale);
430  array_format->add_column
431  (InternalName::get_size(), 1, Geom::NT_stdfloat, Geom::C_other);
432  }
433 
434  if (_aspect_ratio * _initial_x_scale != _initial_y_scale ||
435  _aspect_ratio * final_x_scale != final_y_scale) {
436  array_format->add_column
437  (InternalName::get_aspect_ratio(), 1, Geom::NT_stdfloat,
438  Geom::C_other);
439  }
440 
441  CPT(GeomVertexFormat) format = GeomVertexFormat::register_format
442  (new GeomVertexFormat(array_format));
443 
444  // Reset render() data structures
445  for (i = 0; i < (int)_ttl_count.size(); ++i) {
446  PANDA_FREE_ARRAY(_ttl_count[i]);
447  }
448  _anim_size.resize(anim_count);
449  _ttl_count.clear();
450  _ttl_count.resize(anim_count);
451 
452  // Reset sprite primitive data in order to prepare for next pass.
453  _sprite_primitive.clear();
454  _sprites.clear();
455  _vdata.clear();
456  _sprite_writer.clear();
457 
458  GeomNode *render_node = get_render_node();
459  render_node->remove_all_geoms();
460 
461  // For each animation...
462  for (i = 0; i < anim_count; ++i) {
463  anim = _anims[i];
464  _anim_size[i] = anim->get_num_frames();
465 
466  _sprite_primitive.push_back(pvector<PT(Geom)>());
467  _sprites.push_back(pvector<PT(GeomPoints)>());
468  _vdata.push_back(pvector<PT(GeomVertexData)>());
469  _sprite_writer.push_back(pvector<SpriteWriter>());
470 
471  // For each frame of the animation...
472  for (j = 0; j < _anim_size[i]; ++j) {
473  _ttl_count[i] = (int *)PANDA_MALLOC_ARRAY(_anim_size[i] * sizeof(int));
474  _vdata[i].push_back(new GeomVertexData("sprite_particles", format, Geom::UH_stream));
475  PT(Geom) geom = new Geom(_vdata[i][j]);
476  _sprite_primitive[i].push_back((Geom*)geom);
477  _sprites[i].push_back(new GeomPoints(Geom::UH_stream));
478  geom->add_primitive(_sprites[i][j]);
479 
480  // This will be overwritten in render(), but we had to have some initial
481  // value
482  _sprite_writer[i].push_back(SpriteWriter());
483 
484  state = state->add_attrib(RenderModeAttrib::make(RenderModeAttrib::M_unchanged, _base_y_scale * _height, true));
485  if (anim->get_frame(j) != nullptr) {
486  state = state->add_attrib(TextureAttrib::make(anim->get_frame(j)));
487  state = state->add_attrib(TexGenAttrib::make(TextureStage::get_default(), TexGenAttrib::M_point_sprite));
488 
489  // Build a transform to convert the texture coordinates to the ll, ur
490  // space.
491  LPoint2 ul(anim->get_ll(j)[0], anim->get_ur(j)[1]);
492  LPoint2 lr(anim->get_ur(j)[0], anim->get_ll(j)[1]);
493  LVector2 sc = lr - ul;
494 
495  CPT(TransformState) ts = TransformState::make_pos_rotate_scale2d(ul, 0.0f, sc);
496  state = state->add_attrib(TexMatrixAttrib::make(TextureStage::get_default(), ts));
497  }
498 
499  render_node->add_geom(_sprite_primitive[i][j], state);
500  }
501  }
502 
503  nassertv(render_node->check_valid());
504 }
505 
506 /**
507  * child birth, one of those 'there-if-we-want-it' things. not really too
508  * useful here, so it turns out we don't really want it.
509  */
510 void SpriteParticleRenderer::
511 birth_particle(int index) {
512  _birth_list.push_back(index);
513 }
514 
515 /**
516  * child death
517  */
518 void SpriteParticleRenderer::
519 kill_particle(int) {
520 }
521 
522 /**
523  * big child render. populates the geom node.
524  */
525 void SpriteParticleRenderer::
526 render(pvector< PT(PhysicsObject) >& po_vector, int ttl_particles) {
527  PStatTimer t1(_render_collector);
528  // There is no texture data available, exit.
529  if (_anims.empty()) {
530  return;
531  }
532 
533  BaseParticle *cur_particle;
534  int remaining_particles = ttl_particles;
535  int i,j; // loop counters
536  int anim_count = _anims.size(); // number of animations
537  int frame; // frame index, used in indicating which frame to use when not animated
538  // First, since this is the only time we have access to the actual
539  // particles, do some delayed initialization.
540  if (_animate_frames || anim_count) {
541  if (!_birth_list.empty()) {
542  for (vector_int::iterator vIter = _birth_list.begin(); vIter != _birth_list.end(); ++vIter) {
543  cur_particle = (BaseParticle*)po_vector[*vIter].p();
544  i = int(NORMALIZED_RAND()*anim_count);
545 
546  // If there are multiple animations to choose from, choose one at
547  // random for this new particle
548  cur_particle->set_index(i < anim_count?i:i-1);
549 
550  // This is an experimental age offset so that the animations don't
551  // appear synchronized. If we are using animations, try to vary the
552  // frame flipping a bit for particles in the same litter. A similar
553  // effect might be a achieved by using a small lifespan spread value
554  // on the factory.
555 
556  // Perhaps we should look into other methods. The age offset doesn't
557  // seem to be cutting it.
558  if (_animate_frames) {
559  cur_particle->set_age(cur_particle->get_age()+i/10.0*cur_particle->get_lifespan());
560  }
561  }
562  }
563  }
564  _birth_list.clear();
565 
566  // Create vertex writers for each of the possible geoms. Could possibly be
567  // changed to only create writers for geoms that would be used according to
568  // the animation configuration.
569  for (i = 0; i < anim_count; ++i) {
570  for (j = 0; j < _anim_size[i]; ++j) {
571  // Set the particle per frame counts to 0.
572  memset(_ttl_count[i], 0, _anim_size[i]*sizeof(int));
573  _sprite_writer[i][j].vertex = GeomVertexWriter(_vdata[i][j], InternalName::get_vertex());
574  _sprite_writer[i][j].color = GeomVertexWriter(_vdata[i][j], InternalName::get_color());
575  _sprite_writer[i][j].rotate = GeomVertexWriter(_vdata[i][j], InternalName::get_rotate());
576  _sprite_writer[i][j].size = GeomVertexWriter(_vdata[i][j], InternalName::get_size());
577  _sprite_writer[i][j].aspect_ratio = GeomVertexWriter(_vdata[i][j], InternalName::get_aspect_ratio());
578  }
579  }
580 
581  // init the aabb
582  _aabb_min.set(99999.0f, 99999.0f, 99999.0f);
583  _aabb_max.set(-99999.0f, -99999.0f, -99999.0f);
584 
585  // run through every filled slot
586  for (i = 0; i < (int)po_vector.size(); i++) {
587  cur_particle = (BaseParticle *) po_vector[i].p();
588 
589  if (!cur_particle->get_alive()) {
590  continue;
591  }
592 
593  LPoint3 position = cur_particle->get_position();
594 
595  // x aabb adjust
596  if (position[0] > _aabb_max[0])
597  _aabb_max[0] = position[0];
598  else if (position[0] < _aabb_min[0])
599  _aabb_min[0] = position[0];
600 
601  // y aabb adjust
602  if (position[1] > _aabb_max[1])
603  _aabb_max[1] = position[1];
604  else if (position[1] < _aabb_min[1])
605  _aabb_min[1] = position[1];
606 
607  // z aabb adjust
608  if (position[2] > _aabb_max[2])
609  _aabb_max[2] = position[2];
610  else if (position[2] < _aabb_min[2])
611  _aabb_min[2] = position[2];
612 
613 
614  PN_stdfloat t = cur_particle->get_parameterized_age();
615  int anim_index = cur_particle->get_index();
616 
617  // If an animation has been removed, we need to reassign those particles
618  // assigned to the removed animation.
619  if(_animation_removed && (anim_index >= anim_count)) {
620  anim_index = int(NORMALIZED_RAND()*anim_count);
621  anim_index = anim_index<anim_count?anim_index:anim_index-1;
622  cur_particle->set_index(anim_index);
623  }
624 
625  // Find the frame
626  if (_animate_frames) {
627  if (_animate_frames_rate == 0.0f) {
628  frame = (int)(t*_anim_size[anim_index]);
629  } else {
630  frame = (int)fmod(cur_particle->get_age()*_animate_frames_rate+1,_anim_size[anim_index]);
631  }
632  } else {
633  frame = _animate_frames_index;
634  }
635 
636  // Quick check make sure our math above didn't result in an invalid frame.
637  frame = (frame < _anim_size[anim_index]) ? frame : (_anim_size[anim_index]-1);
638  ++_ttl_count[anim_index][frame];
639 
640  // Calculate the color This is where we'll want to give the renderer the
641  // new color
642  LColor c = _color_interpolation_manager->generateColor(t);
643 
644  int alphamode=get_alpha_mode();
645  if (alphamode != PR_ALPHA_NONE) {
646  if (alphamode == PR_ALPHA_OUT)
647  c[3] *= (1.0f - t) * get_user_alpha();
648  else if (alphamode == PR_ALPHA_IN)
649  c[3] *= t * get_user_alpha();
650  else if (alphamode == PR_ALPHA_IN_OUT) {
651  c[3] *= 2.0f * min(t, 1.0f - t) * get_user_alpha();
652  }
653  else {
654  assert(alphamode == PR_ALPHA_USER);
655  c[3] *= get_user_alpha();
656  }
657  }
658 
659  // Send the data on its way...
660  _sprite_writer[anim_index][frame].vertex.add_data3(position);
661  _sprite_writer[anim_index][frame].color.add_data4(c);
662 
663  PN_stdfloat current_x_scale = _initial_x_scale;
664  PN_stdfloat current_y_scale = _initial_y_scale;
665 
666  if (_animate_x_ratio || _animate_y_ratio) {
667  if (_blend_method == PP_BLEND_CUBIC) {
668  t = CUBIC_T(t);
669  }
670 
671  if (_animate_x_ratio) {
672  current_x_scale = (_initial_x_scale +
673  (t * (_final_x_scale - _initial_x_scale)));
674  }
675  if (_animate_y_ratio) {
676  current_y_scale = (_initial_y_scale +
677  (t * (_final_y_scale - _initial_y_scale)));
678  }
679  }
680 
681  if (_sprite_writer[anim_index][frame].size.has_column()) {
682  _sprite_writer[anim_index][frame].size.add_data1f(current_y_scale * _height);
683  }
684  if (_sprite_writer[anim_index][frame].aspect_ratio.has_column()) {
685  _sprite_writer[anim_index][frame].aspect_ratio.add_data1f(_aspect_ratio * current_x_scale / current_y_scale);
686  }
687  if (_animate_theta) {
688  _sprite_writer[anim_index][frame].rotate.add_data1f(cur_particle->get_theta());
689  } else if (_sprite_writer[anim_index][frame].rotate.has_column()) {
690  _sprite_writer[anim_index][frame].rotate.add_data1f(_theta);
691  }
692 
693  // maybe jump out early?
694  remaining_particles--;
695  if (remaining_particles == 0) {
696  break;
697  }
698  }
699  int n = 0;
700  GeomNode *render_node = get_render_node();
701 
702  for (i = 0; i < anim_count; ++i) {
703  for (j = 0; j < _anim_size[i]; ++j) {
704  _sprites[i][j]->clear_vertices();
705  _sprite_writer[i][j].clear();
706 
707  // We have to reassign the GeomVertexData and GeomPrimitive to the Geom,
708  // and the Geom to the GeomNode, in case it got flattened away.
709  _sprite_primitive[i][j]->set_primitive(0, _sprites[i][j]);
710  _sprite_primitive[i][j]->set_vertex_data(_vdata[i][j]);
711 
712  render_node->set_geom(n, _sprite_primitive[i][j]);
713  ++n;
714  }
715  }
716 
717  if (_animate_frames) {
718  for (i = 0; i < anim_count; ++i) {
719  for (j = 0; j < _anim_size[i]; ++j) {
720  _sprites[i][j]->add_next_vertices(_ttl_count[i][j]);
721  }
722  }
723  } else {
724  for (i = 0; i < anim_count; ++i) {
725  _sprites[i][_animate_frames_index]->add_next_vertices(_ttl_count[i][_animate_frames_index]);
726  }
727  }
728 
729  // done filling geompoint node, now do the bb stuff
730  LPoint3 aabb_center = _aabb_min + ((_aabb_max - _aabb_min) * 0.5f);
731  PN_stdfloat radius = (aabb_center - _aabb_min).length();
732 
733  for (i = 0; i < anim_count; ++i) {
734  for (j = 0; j < _anim_size[i]; ++j) {
735  nassertv(_sprite_primitive[i][j]->check_valid());
736  BoundingSphere sphere(aabb_center, radius);
737  _sprite_primitive[i][j]->set_bounds(&sphere);
738  }
739  }
740 
741  get_render_node()->mark_internal_bounds_stale();
742  nassertv(render_node->check_valid());
743  _animation_removed = false;
744 }
745 
746 /**
747  * Write a string representation of this instance to <out>.
748  */
750 output(std::ostream &out) const {
751  #ifndef NDEBUG //[
752  out<<"SpriteParticleRenderer";
753  #endif //] NDEBUG
754 }
755 
756 /**
757  * Write a string representation of this instance to <out>.
758  */
760 write(std::ostream &out, int indent_level) const {
761  indent(out, indent_level) << "SpriteParticleRenderer:\n";
762  // indent(out, indent_level + 2) << "_sprite_primitive
763  // "<<_sprite_primitive<<"\n";
764  indent(out, indent_level + 2) << "_color "<<_color<<"\n";
765  indent(out, indent_level + 2) << "_initial_x_scale "<<_initial_x_scale<<"\n";
766  indent(out, indent_level + 2) << "_final_x_scale "<<_final_x_scale<<"\n";
767  indent(out, indent_level + 2) << "_initial_y_scale "<<_initial_y_scale<<"\n";
768  indent(out, indent_level + 2) << "_final_y_scale "<<_final_y_scale<<"\n";
769  indent(out, indent_level + 2) << "_theta "<<_theta<<"\n";
770  indent(out, indent_level + 2) << "_animate_x_ratio "<<_animate_x_ratio<<"\n";
771  indent(out, indent_level + 2) << "_animate_y_ratio "<<_animate_y_ratio<<"\n";
772  indent(out, indent_level + 2) << "_animate_theta "<<_animate_theta<<"\n";
773  indent(out, indent_level + 2) << "_blend_method "<<_blend_method<<"\n";
774  indent(out, indent_level + 2) << "_aabb_min "<<_aabb_min<<"\n";
775  indent(out, indent_level + 2) << "_aabb_max "<<_aabb_max<<"\n";
776  indent(out, indent_level + 2) << "_pool_size "<<_pool_size<<"\n";
777  BaseParticleRenderer::write(out, indent_level + 2);
778 }
int get_num_children(Thread *current_thread=Thread::get_current_thread()) const
Returns the number of children of the referenced node.
Definition: nodePath.I:328
GeomNode * get_render_node() const
Query the geomnode pointer.
Renders a particle system with high-speed nasty trick sprites.
void set_geom(int n, Geom *geom)
Replaces the nth Geom of the node with a new pointer.
Definition: geomNode.cxx:635
Helper class used by SpriteParticleRenderer to keep track of its textures and their respective UVs an...
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
NodePath find(const std::string &path) const
Searches for a node below the referenced node that matches the indicated string.
Definition: nodePath.cxx:314
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Indicates a coordinate-system transform on vertices.
virtual void write(std::ostream &out, int indent_level=0) const
Write a string representation of this instance to <out>.
void add_path(const NodePath &node_path)
Adds a new NodePath to the collection.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void write(std::ostream &out, int indent=0) const
Write a string representation of this instance to <out>.
Defines a series of disconnected points.
Definition: geomPoints.h:23
bool is_empty() const
Returns true if the NodePath contains no nodes.
Definition: nodePath.I:188
bool check_valid() const
Verifies that the each Geom within the GeomNode reference vertices that actually exist within its Geo...
Definition: geomNode.cxx:653
get_vertex
Returns the ith vertex index in the table.
Definition: geomPrimitive.h:99
void clear()
Removes all NodePaths from the collection.
This defines a bounding sphere, consisting of a center and a radius.
A body on which physics will be applied.
Definition: physicsObject.h:27
void add_from_node(const NodePath &node_path, bool size_from_texels=false, bool resize=false)
This will allow the renderer to randomly choose from more than one texture or sequence at particle bi...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition: texture.h:71
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Definition: geomPrimitive.h:56
get_num_vertices
Returns the number of indices used by all the primitives in this object.
Definition: geomPrimitive.h:99
void add_primitive(const GeomPrimitive *primitive)
Inserts a new GeomPrimitive structure to the Geom object.
Definition: geom.I:116
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition: pStatTimer.h:30
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void add_texture(Texture *texture)
Adds a new Texture to the collection.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual ~SpriteParticleRenderer()
destructor
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TextureCollection find_all_textures() const
Returns a list of a textures applied to geometry at this node and below.
Definition: nodePath.cxx:3918
A lightweight class that represents a single element that may be timed and/or counted via stats.
Pure virtual particle renderer base class.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: indent.cxx:20
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void set_from_node(const NodePath &node_path, bool size_from_texels=false)
Sets the properties on this renderer from the geometry referenced by the indicated NodePath.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
Texture * find_texture(const std::string &name) const
Returns the first texture found applied to geometry at this node or below that matches the indicated ...
Definition: nodePath.cxx:3897
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A container for geometry primitives.
Definition: geom.h:54
High level class for color interpolation.
Manages a list of Texture objects, as returned by TexturePool::find_all_textures().
virtual PN_stdfloat get_theta() const
for spriteParticleRenderer
PN_stdfloat get_user_alpha() const
gets alpha for "user" alpha mode
get_num_textures
Returns the number of Textures in the collection.
void clear()
Removes all Textures from the collection.
virtual BaseParticleRenderer * make_copy()
child dynamic copy
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
This class defines the physical layout of the vertex data stored within a Geom.
void add_textures_from(const TextureCollection &other)
Adds all the Textures indicated in the other collection to this texture.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PandaNode * node() const
Returns the referenced node of the path.
Definition: nodePath.I:227
An individual, physically-modelable particle abstract base class.
Definition: baseParticle.h:23
get_num_geoms
Returns the number of geoms in the node.
Definition: geomNode.h:71
This object provides a high-level interface for quickly reading a sequence of numeric values from a v...
LPoint3 get_position() const
Position Query.
This describes the structure of a single array within a Geom data.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Helper class used by SpriteParticleRenderer to keep track of the various GeomVertexWriters associated...
void set_texture(Texture *tex, PN_stdfloat texels_per_unit=1.0f)
Sets the renderer up to render the entire texture image.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:161
void set_size(PN_stdfloat width, PN_stdfloat height)
Sets the size of each particle in world units.
NodePath get_child(int n, Thread *current_thread=Thread::get_current_thread()) const
Returns a NodePath representing the nth child of the referenced node.
Definition: nodePath.I:337
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
void add_geom(Geom *geom, const RenderState *state=RenderState::make_empty())
Adds a new Geom to the node.
Definition: geomNode.cxx:584
virtual void output(std::ostream &out) const
Write a string representation of this instance to <out>.
SpriteParticleRenderer(Texture *tex=nullptr)
constructor
This is a set of zero or more NodePaths.
void remove_all_geoms()
Removes all the geoms from the node at once.
Definition: geomNode.I:126