Explorar o código

Added animated and multiple sprites capability.

Josh Wilson %!s(int64=20) %!d(string=hai) anos
pai
achega
acdba7e61b

+ 228 - 28
panda/src/particlesystem/spriteParticleRenderer.I

@@ -16,17 +16,6 @@
 //
 //
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
-////////////////////////////////////////////////////////////////////
-//    Function : SpriteParticleRenderer::get_source_type
-//      Access : public
-// Description : Returns an indication of whether the texture for this
-//               renderer was set via a call to set_texture(), or via
-//               set_from_node().
-////////////////////////////////////////////////////////////////////
-INLINE SpriteParticleRenderer::SourceType SpriteParticleRenderer::
-get_source_type() const {
-  return _source_type;
-}
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //    Function : SpriteParticleRenderer::set_texture
 //    Function : SpriteParticleRenderer::set_texture
@@ -35,20 +24,83 @@ get_source_type() const {
 //               image.  The scale of each particle is based on the
 //               image.  The scale of each particle is based on the
 //               size of the texture in each dimension, modified by
 //               size of the texture in each dimension, modified by
 //               texels_per_unit.
 //               texels_per_unit.
+//
+//               Used to set the size of the particles.  Will clear
+//               all previously loaded textures and animations.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
+INLINE void SpriteParticleRenderer::
+set_texture(Texture *tex, const string &tex_path, float texels_per_unit) {
+  set_texture(tex,texels_per_unit);
+  get_last_anim()->set_source_info(tex_path);
+}
+
 INLINE void SpriteParticleRenderer::
 INLINE void SpriteParticleRenderer::
 set_texture(Texture *tex, float texels_per_unit) {
 set_texture(Texture *tex, float texels_per_unit) {
-  _texture = tex;
-  set_ll_uv(TexCoordf(0.0f, 0.0f));
-  set_ur_uv(TexCoordf(1.0f, 1.0f));
-  _source_type = ST_texture;
-
-  // We scale the particle size by the size of the texture.
-  if (_texture != (Texture *)NULL) {
-    set_size(_texture->get_x_size() / texels_per_unit,
-             _texture->get_y_size() / texels_per_unit);
+  if (tex != (Texture *)NULL) {
+    // Clear all texture information
+    _anims.clear();
+
+    // Insert the single texture
+    _anims.push_back(new SpriteAnim(tex,TexCoordf(0.0f, 0.0f),TexCoordf(1.0f, 1.0f)));
+
+    // We scale the particle size by the size of the texture.
+    set_size(tex->get_x_size() / texels_per_unit,
+             tex->get_y_size() / texels_per_unit);
+  }
+  init_geoms();
+}
+
+////////////////////////////////////////////////////////////////////
+//    Function : SpriteParticleRenderer::add_texture
+//      Access : Published
+// Description : Adds texture to image pool, effectively creating a 
+//               single frame animation that can be selected at 
+//               particle birth.  This should only be called after
+//               a previous call to set_texture().
+////////////////////////////////////////////////////////////////////
+INLINE void SpriteParticleRenderer::
+add_texture(Texture *tex, const string &tex_path, float texels_per_unit, bool resize) {
+  add_texture(tex,texels_per_unit,resize);
+  get_last_anim()->set_source_info(tex_path);
+}
+
+INLINE void SpriteParticleRenderer::
+add_texture(Texture *tex, float texels_per_unit, bool resize) {
+  if (_anims.size() == 0) {
+    set_texture(tex, texels_per_unit);
+  } else {
+    if(tex != (Texture *)NULL) {
+      if (tex != (Texture *)NULL) {
+        _anims.push_back(new SpriteAnim(tex,TexCoordf(0.0f, 0.0f),TexCoordf(1.0f, 1.0f)));
+      }
+      if(resize) {
+        // We scale the particle size by the size of the texture.
+        set_size(tex->get_x_size() / texels_per_unit,
+                 tex->get_y_size() / texels_per_unit);
+      }
+      init_geoms();
+    }
   }
   }
+}
 
 
+////////////////////////////////////////////////////////////////////
+//    Function : SpriteParticleRenderer::remove_animation
+//      Access : Published
+// Description : Removes an animation texture set from the renderer.
+////////////////////////////////////////////////////////////////////
+INLINE void SpriteParticleRenderer::
+remove_animation(const int n) {
+  nassertv(n < (int)_anims.size());
+  int i,j;
+
+  for (i = 0; i < (int)_anims.size(); ++i) {
+    for (j = 0; j < (int)_anim_size[i]; ++j) {
+      _sprites[i][j]->clear_vertices();
+    }
+  }
+
+  _anims.erase(_anims.begin()+n);
+  _animation_removed = true;
   init_geoms();
   init_geoms();
 }
 }
 
 
@@ -62,7 +114,22 @@ set_texture(Texture *tex, float texels_per_unit) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void SpriteParticleRenderer::
 INLINE void SpriteParticleRenderer::
 set_ll_uv(const TexCoordf &ll_uv) {
 set_ll_uv(const TexCoordf &ll_uv) {
-  _ll_uv = ll_uv;
+  set_ll_uv(ll_uv,0,0);
+}
+
+////////////////////////////////////////////////////////////////////
+//    Function : SpriteParticleRenderer::set_ll_uv
+//      Access : public
+// Description : Sets the UV coordinate of the lower-left corner of
+//               all the sprites generated by this renderer.  Normally
+//               this is (0, 0), but it might be set to something else
+//               to use only a portion of the texture.
+////////////////////////////////////////////////////////////////////
+INLINE void SpriteParticleRenderer::
+set_ll_uv(const TexCoordf &ll_uv, const int anim, const int frame) {
+  if(anim < (int)_anims.size() && frame < (int)_anims[anim]->get_num_frames()) {
+    _anims[anim]->set_ll(frame,ll_uv);
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -75,7 +142,22 @@ set_ll_uv(const TexCoordf &ll_uv) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void SpriteParticleRenderer::
 INLINE void SpriteParticleRenderer::
 set_ur_uv(const TexCoordf &ur_uv) {
 set_ur_uv(const TexCoordf &ur_uv) {
-  _ur_uv = ur_uv;
+  set_ur_uv(ur_uv,0,0);
+}
+
+////////////////////////////////////////////////////////////////////
+//    Function : SpriteParticleRenderer::set_ur_uv
+//      Access : public
+// Description : Sets the UV coordinate of the upper-right corner of
+//               all the sprites generated by this renderer.  Normally
+//               this is (1, 1), but it might be set to something else
+//               to use only a portion of the texture.
+////////////////////////////////////////////////////////////////////
+INLINE void SpriteParticleRenderer::
+set_ur_uv(const TexCoordf &ur_uv, const int anim, const int frame) {
+  if(anim < (int)_anims.size() && frame < (int)_anims[anim]->get_num_frames()) {
+    _anims[anim]->set_ur(frame,ur_uv);
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -195,13 +277,77 @@ set_alpha_disable(bool ad) {
   _alpha_disable = ad;
   _alpha_disable = ad;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+// Function : SpriteParticleRenderer::set_animate_frames_enable
+//   Access : public
+////////////////////////////////////////////////////////////////////
+INLINE void SpriteParticleRenderer::
+set_animate_frames_enable(bool an) {
+  _animate_frames = an;
+}
+
+////////////////////////////////////////////////////////////////////
+// Function : SpriteParticleRenderer::set_animate_frames_rate
+//   Access : public
+////////////////////////////////////////////////////////////////////
+INLINE void SpriteParticleRenderer::
+set_animate_frames_rate(float r) {
+  nassertv( r >= 0.0);
+  _animate_frames_rate = r;
+}
+
+////////////////////////////////////////////////////////////////////
+// Function : SpriteParticleRenderer::set_animate_frames_index
+//   Access : public
+//  Purpose : Sets the frame to be used when animation is disabled.
+////////////////////////////////////////////////////////////////////
+INLINE void SpriteParticleRenderer::
+set_animate_frames_index(int i) {
+  nassertv(i < (int)_anims[0]->get_num_frames());
+  _animate_frames_index = i;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 // Function : SpriteParticleRenderer::get_texture
 // Function : SpriteParticleRenderer::get_texture
 //   Access : public
 //   Access : public
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE Texture *SpriteParticleRenderer::
 INLINE Texture *SpriteParticleRenderer::
 get_texture() const {
 get_texture() const {
-  return _texture;
+  return get_texture(0,0);
+}
+
+////////////////////////////////////////////////////////////////////
+// Function : SpriteParticleRenderer::get_texture
+//   Access : public
+////////////////////////////////////////////////////////////////////
+INLINE Texture *SpriteParticleRenderer::
+get_texture(const int anim, const int frame) const {
+  if(_anims.size() == 0) {
+    return (Texture*)NULL;
+  }
+  nassertr(anim < (int)_anims.size() && anim >= 0, (Texture*)NULL);
+  nassertr(frame < (int)_anims[anim]->get_num_frames() && frame >= 0,_anims[anim]->get_frame(0));
+  return _anims[anim]->get_frame(frame);
+}
+
+INLINE int SpriteParticleRenderer::
+get_num_anims() const {
+  return _anims.size();
+}
+
+INLINE SpriteAnim *SpriteParticleRenderer::
+get_anim(const int n) const {
+  nassertr(n < (int)_anims.size(), (SpriteAnim*)NULL);
+  return _anims[n];
+}
+
+INLINE SpriteAnim *SpriteParticleRenderer::
+get_last_anim() const {
+  if (_anims.size()) {
+    return *(_anims.end()-1);
+  } else {
+    return (SpriteAnim *)NULL;
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -210,20 +356,46 @@ get_texture() const {
 // Description : Returns the UV coordinate of the lower-left corner;
 // Description : Returns the UV coordinate of the lower-left corner;
 //               see set_ll_uv().
 //               see set_ll_uv().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE const TexCoordf &SpriteParticleRenderer::
+INLINE TexCoordf SpriteParticleRenderer::
 get_ll_uv() const {
 get_ll_uv() const {
-  return _ll_uv;
+  return get_ll_uv(0,0);
+}
+
+////////////////////////////////////////////////////////////////////
+//    Function : SpriteParticleRenderer::get_ll_uv
+//      Access : public
+// Description : Returns the UV coordinate of the lower-left corner;
+//               see set_ll_uv().
+////////////////////////////////////////////////////////////////////
+INLINE TexCoordf SpriteParticleRenderer::
+get_ll_uv(const int anim, const int frame) const {
+  int a = anim < (int)_anims.size()?anim:0;
+  int f = frame < (int)_anims[a]->get_num_frames()?frame:0;
+  return _anims[a]->get_ll(f);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //    Function : SpriteParticleRenderer::get_ur_uv
 //    Function : SpriteParticleRenderer::get_ur_uv
 //      Access : public
 //      Access : public
-// Description : Returns the UV coordinate of the upper-right corner;
+// Description : Returns the UV coordinate of the lower-left corner;
 //               see set_ur_uv().
 //               see set_ur_uv().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE const TexCoordf &SpriteParticleRenderer::
+INLINE TexCoordf SpriteParticleRenderer::
 get_ur_uv() const {
 get_ur_uv() const {
-  return _ur_uv;
+  return get_ur_uv(0,0);
+}
+
+////////////////////////////////////////////////////////////////////
+//    Function : SpriteParticleRenderer::get_ur_uv
+//      Access : public
+// Description : Returns the UV coordinate of the upper-right corner;
+//               see set_ur_uv().
+////////////////////////////////////////////////////////////////////
+INLINE TexCoordf SpriteParticleRenderer::
+get_ur_uv(const int anim, const int frame) const {
+  int a = anim < (int)_anims.size()?anim:0;
+  int f = frame < (int)_anims[a]->get_num_frames()?frame:0;
+  return _anims[a]->get_ur(f);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -345,6 +517,34 @@ get_alpha_disable() const {
   return _alpha_disable;
   return _alpha_disable;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+// Function : SpriteParticleRenderer::get_animate_frames_enable
+//   Access : public
+////////////////////////////////////////////////////////////////////
+INLINE bool SpriteParticleRenderer::
+get_animate_frames_enable() const {
+  return _animate_frames;
+}
+
+////////////////////////////////////////////////////////////////////
+// Function : SpriteParticleRenderer::get_animate_frames_rate
+//   Access : public
+////////////////////////////////////////////////////////////////////
+INLINE float SpriteParticleRenderer::
+get_animate_frames_rate() const {
+  return _animate_frames_rate;
+}
+
+////////////////////////////////////////////////////////////////////
+// Function : SpriteParticleRenderer::get_animate_frames_index
+//   Access : public
+//  Purpose : Gets the frame to be used when animation is disabled.
+////////////////////////////////////////////////////////////////////
+INLINE int SpriteParticleRenderer::
+get_animate_frames_index() const {
+  return _animate_frames_index;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 // Function : SpriteParticleRenderer::get_color_interpolation_manager
 // Function : SpriteParticleRenderer::get_color_interpolation_manager
 //   Access : public
 //   Access : public

+ 540 - 187
panda/src/particlesystem/spriteParticleRenderer.cxx

@@ -19,6 +19,7 @@
 #include "spriteParticleRenderer.h"
 #include "spriteParticleRenderer.h"
 #include "boundingSphere.h"
 #include "boundingSphere.h"
 #include "geomNode.h"
 #include "geomNode.h"
+#include "sequenceNode.h"
 #include "nodePath.h"
 #include "nodePath.h"
 #include "dcast.h"
 #include "dcast.h"
 #include "geom.h"
 #include "geom.h"
@@ -28,6 +29,8 @@
 #include "texMatrixAttrib.h"
 #include "texMatrixAttrib.h"
 #include "texGenAttrib.h"
 #include "texGenAttrib.h"
 #include "textureAttrib.h"
 #include "textureAttrib.h"
+#include "textureCollection.h"
+#include "nodePathCollection.h"
 #include "indent.h"
 #include "indent.h"
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -39,8 +42,6 @@ SpriteParticleRenderer::
 SpriteParticleRenderer(Texture *tex) :
 SpriteParticleRenderer(Texture *tex) :
   BaseParticleRenderer(PR_ALPHA_NONE),
   BaseParticleRenderer(PR_ALPHA_NONE),
   _color(Colorf(1.0f, 1.0f, 1.0f, 1.0f)),
   _color(Colorf(1.0f, 1.0f, 1.0f, 1.0f)),
-  _ll_uv(0.0f, 0.0f),
-  _ur_uv(1.0f, 1.0f),
   _height(1.0f),
   _height(1.0f),
   _width(1.0f),
   _width(1.0f),
   _initial_x_scale(0.02f),
   _initial_x_scale(0.02f),
@@ -50,15 +51,18 @@ SpriteParticleRenderer(Texture *tex) :
   _theta(0.0f),
   _theta(0.0f),
   _base_y_scale(1.0f),
   _base_y_scale(1.0f),
   _aspect_ratio(1.0f),
   _aspect_ratio(1.0f),
+  _animate_frames_rate(0.0f),
+  _animate_frames_index(0),
   _animate_x_ratio(false),
   _animate_x_ratio(false),
   _animate_y_ratio(false),
   _animate_y_ratio(false),
   _animate_theta(false),
   _animate_theta(false),
   _alpha_disable(false),
   _alpha_disable(false),
+  _animate_frames(false),
+  _animation_removed(true),
   _blend_method(PP_BLEND_LINEAR),
   _blend_method(PP_BLEND_LINEAR),
   _color_interpolation_manager(new ColorInterpolationManager(_color)),
   _color_interpolation_manager(new ColorInterpolationManager(_color)),
-  _pool_size(0),
-  _source_type(ST_texture)
-{
+  _pool_size(0) {
+  set_texture(tex);
   init_geoms();  
   init_geoms();  
 }
 }
 
 
@@ -69,21 +73,30 @@ SpriteParticleRenderer(Texture *tex) :
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 SpriteParticleRenderer::
 SpriteParticleRenderer::
 SpriteParticleRenderer(const SpriteParticleRenderer& copy) :
 SpriteParticleRenderer(const SpriteParticleRenderer& copy) :
-  BaseParticleRenderer(copy), _pool_size(0) {
-  _texture = copy._texture;
-  _animate_x_ratio = copy._animate_x_ratio;
-  _animate_y_ratio = copy._animate_y_ratio;
-  _animate_theta = copy._animate_theta;
-  _alpha_disable = copy._alpha_disable;
-  _blend_method = copy._blend_method;
-  _ll_uv = copy._ll_uv;
-  _ur_uv = copy._ur_uv;
-  _initial_x_scale = copy._initial_x_scale;
-  _final_x_scale = copy._final_x_scale;
-  _initial_y_scale = copy._initial_y_scale;
-  _final_y_scale = copy._final_y_scale;
-  _theta = copy._theta;
-  _color = copy._color;
+  BaseParticleRenderer(copy), 
+  _color(copy._color),
+  _height(copy._height),
+  _width(copy._width),
+  _initial_x_scale(copy._initial_x_scale),
+  _final_x_scale(copy._final_x_scale),
+  _initial_y_scale(copy._initial_y_scale),
+  _final_y_scale(copy._final_y_scale),
+  _theta(copy._theta),
+  _base_y_scale(copy._base_y_scale),
+  _aspect_ratio(copy._aspect_ratio),
+  _animate_frames_rate(copy._animate_frames_rate),
+  _animate_frames_index(copy._animate_frames_index),
+  _animate_x_ratio(copy._animate_x_ratio),
+  _animate_y_ratio(copy._animate_y_ratio),
+  _animate_theta(copy._animate_theta),
+  _alpha_disable(copy._alpha_disable),
+  _animate_frames(copy._animate_frames),
+  _animation_removed(true),
+  _blend_method(copy._blend_method),
+  _color_interpolation_manager(copy._color_interpolation_manager),
+  _pool_size(0),
+  _anims(copy._anims),
+  _birth_list(copy._birth_list) {
   init_geoms();
   init_geoms();
 }
 }
 
 
@@ -94,6 +107,7 @@ SpriteParticleRenderer(const SpriteParticleRenderer& copy) :
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 SpriteParticleRenderer::
 SpriteParticleRenderer::
 ~SpriteParticleRenderer() {
 ~SpriteParticleRenderer() {
+  get_render_node()->remove_all_geoms();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -106,143 +120,366 @@ make_copy() {
   return new SpriteParticleRenderer(*this);
   return new SpriteParticleRenderer(*this);
 }
 }
 
 
+
+////////////////////////////////////////////////////////////////////
+//    Function : SpriteParticleRenderer::extract_textures_from_node
+//      Access : public
+// Description : Pull either a set of textures from a SequenceNode or
+//               a single texture from a GeomNode.  This function is called
+//               in both set_from_node() and add_from_node().  Notice the
+//               second parameter.  This nodepath will reference the GeomNode
+//               holding the first texture in the returned TextureCollection.
+////////////////////////////////////////////////////////////////////
+int SpriteParticleRenderer::
+extract_textures_from_node(const NodePath &node_path, NodePathCollection &np_col, TextureCollection &tex_col) {
+  NodePath tex_node_path = node_path, geom_node_path;
+
+  // Look for a sequence node first, in case they want animated texture sprites
+  if (tex_node_path.node()->get_type() != SequenceNode::get_class_type()) {
+    tex_node_path = node_path.find("**/+SequenceNode");
+  }
+
+  // Nodepath contains a sequence node, attempt to read its textures.
+  if (!tex_node_path.is_empty()) {
+    int frame_count = tex_node_path.get_num_children();
+    // We do it this way in order to preserve the order of the textures in the sequence.
+    // If we use a find_all_textures() that order is lost.
+    for (int i = 0; i < frame_count; ++i) {
+      geom_node_path = tex_node_path.get_child(i);
+      if (!geom_node_path.is_empty()) {
+        // Since this is a SequenceNode, there will be only one texture on this geom_node_path.
+        tex_col.add_textures_from(geom_node_path.find_all_textures());
+        np_col.add_path(geom_node_path);
+      }
+    }
+    // If unsuccessful, try again as if the node were a normal GeomNode.
+    if (tex_col.get_num_textures() == 0) {
+      geom_node_path = NodePath();
+      tex_col.clear();
+      np_col.clear();
+    }
+  }
+
+  // If a sequence node is not found, we just want to look for a regular geom node.
+  if (geom_node_path.is_empty()) {  
+    // Find the first GeomNode.
+    if (node_path.node()->get_type() != GeomNode::get_class_type()) {
+      geom_node_path = node_path.find("**/+GeomNode");
+      if (geom_node_path.is_empty()) {
+        particlesystem_cat.error();
+        return 0;
+      }  
+    } else {
+      geom_node_path = node_path;
+    }
+
+    // Grab the first texture.
+    tex_col.add_texture(geom_node_path.find_texture("*"));
+    if (tex_col.get_num_textures() < 1) {
+      particlesystem_cat.error()
+        << geom_node_path << " does not contain a texture.\n";
+      return 0;
+    } else {
+      np_col.add_path(geom_node_path);
+    }
+  }
+  return 1;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //    Function : SpriteParticleRenderer::set_from_node
 //    Function : SpriteParticleRenderer::set_from_node
 //      Access : public
 //      Access : public
 // Description : Sets the properties on this render from the geometry
 // Description : Sets the properties on this render from the geometry
 //               referenced by the indicated NodePath.  This should be
 //               referenced by the indicated NodePath.  This should be
-//               a reference to a GeomNode; it extracts out the
-//               Texture and UV range from the GeomNode.
+//               a reference to a GeomNode or a SequenceNode; it
+//               extracts out the texture and UV range from the node.
+//
+//               If node_path references a SequenceNode with multiple
+//               GeomNodes beneath it, the size data will correspond
+//               to the first GeomNode found with a valid texture, and
+//               the texture and UV information will be stored for each
+//               individual node.
 //
 //
 //               If size_from_texels is true, the particle size is
 //               If size_from_texels is true, the particle size is
 //               based on the number of texels in the source image;
 //               based on the number of texels in the source image;
-//               otherwise, it is based on the size of the polygon
-//               found in the GeomNode.
+//               otherwise, it is based on the size of the first 
+//               polygon found in the node.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
+void SpriteParticleRenderer::
+set_from_node(const NodePath &node_path, const string &model, const string &node, bool size_from_texels) {
+  set_from_node(node_path,size_from_texels);
+  get_last_anim()->set_source_info(model,node);
+}
+
 void SpriteParticleRenderer::
 void SpriteParticleRenderer::
 set_from_node(const NodePath &node_path, bool size_from_texels) {
 set_from_node(const NodePath &node_path, bool size_from_texels) {
   nassertv(!node_path.is_empty());
   nassertv(!node_path.is_empty());
 
 
-  // The bottom node must be a GeomNode.  If it is not, find the first
-  // GeomNode beneath it.
-  NodePath geom_node_path = node_path;
-  if (!geom_node_path.node()->is_geom_node()) {
-    geom_node_path = node_path.find("**/+GeomNode");
-    if (geom_node_path.is_empty()) {
-      particlesystem_cat.error()
-        << node_path << " does not contain a GeomNode.\n";
-      return;
+  NodePathCollection np_col;
+  TextureCollection tex_col;
+  pvector< TexCoordf > ll,ur;
+  GeomNode *gnode = NULL;
+  const Geom *geom;
+  const GeomPrimitive *primitive;
+  bool got_texcoord,got_vertex;
+
+  // Clear all texture information
+  _anims.clear();
+  
+  // Load the found textures into the renderer.
+  if (extract_textures_from_node(node_path,np_col,tex_col)) {    
+    for (int i = 0; i < np_col.get_num_paths(); ++i) {
+      // Get the node from which we'll extract the geometry information.
+      gnode = DCAST(GeomNode, np_col[i].node()); 
+    
+      // Now examine the UV's of the first Geom within the GeomNode.
+      nassertv(gnode->get_num_geoms() > 0);
+      geom = gnode->get_geom(0);
+    
+      got_texcoord = false;
+      TexCoordf min_uv(0.0f, 0.0f);
+      TexCoordf max_uv(0.0f, 0.0f);
+      
+      GeomVertexReader texcoord(geom->get_vertex_data(),
+                                InternalName::get_texcoord());
+      if (texcoord.has_column()) {
+        for (int pi = 0; pi < geom->get_num_primitives(); ++pi) {
+          primitive = geom->get_primitive(pi);
+          for (int vi = 0; vi < primitive->get_num_vertices(); ++vi) {
+            int vert = primitive->get_vertex(vi);
+            texcoord.set_row(vert);
+            
+            if (!got_texcoord) {
+              min_uv = max_uv = texcoord.get_data2f();
+              got_texcoord = true;
+              
+            } else {
+              const LVecBase2f &uv = texcoord.get_data2f();
+              
+              min_uv[0] = min(min_uv[0], uv[0]);
+              max_uv[0] = max(max_uv[0], uv[0]);
+              min_uv[1] = min(min_uv[1], uv[1]);
+              max_uv[1] = max(max_uv[1], uv[1]);
+            }
+          }
+        }
+      }
+    
+      if (got_texcoord) {
+        // We don't really pay attention to orientation of UV's here; a
+        // minor flaw.  We assume the minimum is in the lower-left, and
+        // the maximum is in the upper-right.
+        ll.push_back(min_uv);
+        ur.push_back(max_uv);
+        //        set_ll_uv(min_uv);
+        //        set_ur_uv(max_uv);
+      }
     }
     }
-  }
-  GeomNode *gnode = DCAST(GeomNode, geom_node_path.node());
 
 
-  // Get the texture off the node.  We'll take just the first texture.
-  Texture *tex = geom_node_path.find_texture("*");
+    _anims.push_back(new SpriteAnim(tex_col,ll,ur));
 
 
-  if (tex == (Texture *)NULL) {
-    particlesystem_cat.error()
-      << geom_node_path << " has no texture.\n";
-    return;
-  }
+    gnode = DCAST(GeomNode, np_col[0].node());
+    geom = gnode->get_geom(0);
 
 
-  // Now examine the UV's of the first Geom within the GeomNode.
-  nassertv(gnode->get_num_geoms() > 0);
-  const Geom *geom = gnode->get_geom(0);
-
-  bool got_texcoord = false;
-  TexCoordf min_uv(0.0f, 0.0f);
-  TexCoordf max_uv(0.0f, 0.0f);
-
-  bool got_vertex = false;
-  Vertexf min_xyz(0.0f, 0.0f, 0.0f);
-  Vertexf max_xyz(0.0f, 0.0f, 0.0f);
-
-  GeomVertexReader texcoord(geom->get_vertex_data(),
-                              InternalName::get_texcoord());
-  if (texcoord.has_column()) {
-    for (int pi = 0; pi < geom->get_num_primitives(); ++pi) {
-      const GeomPrimitive *primitive = geom->get_primitive(pi);
-      for (int vi = 0; vi < primitive->get_num_vertices(); ++vi) {
-        int vert = primitive->get_vertex(vi);
-        texcoord.set_row(vert);
-        
-        if (!got_texcoord) {
-          min_uv = max_uv = texcoord.get_data2f();
-          got_texcoord = true;
-          
-        } else {
-          const LVecBase2f &uv = texcoord.get_data2f();
+    got_vertex = false;
+    Vertexf min_xyz(0.0f, 0.0f, 0.0f);
+    Vertexf max_xyz(0.0f, 0.0f, 0.0f);
+    
+    GeomVertexReader vertex(geom->get_vertex_data(),
+                            InternalName::get_vertex());
+    if (vertex.has_column()) {
+      for (int pi = 0; pi < geom->get_num_primitives(); ++pi) {
+        const GeomPrimitive *primitive = geom->get_primitive(pi);
+        for (int vi = 0; vi < primitive->get_num_vertices(); ++vi) {
+          int vert = primitive->get_vertex(vi);
+          vertex.set_row(vert);
           
           
-          min_uv[0] = min(min_uv[0], uv[0]);
-          max_uv[0] = max(max_uv[0], uv[0]);
-          min_uv[1] = min(min_uv[1], uv[1]);
-          max_uv[1] = max(max_uv[1], uv[1]);
+          if (!got_vertex) {
+            min_xyz = max_xyz = vertex.get_data3f();
+            got_vertex = true;
+            
+          } else {
+            const LVecBase3f &xyz = vertex.get_data3f();
+            
+            min_xyz[0] = min(min_xyz[0], xyz[0]);
+            max_xyz[0] = max(max_xyz[0], xyz[0]);
+            min_xyz[1] = min(min_xyz[1], xyz[1]);
+            max_xyz[1] = max(max_xyz[1], xyz[1]);
+            min_xyz[2] = min(min_xyz[2], xyz[2]);
+            max_xyz[2] = max(max_xyz[2], xyz[2]);
+          }
         }
         }
       }
       }
     }
     }
-  }
-  
-  GeomVertexReader vertex(geom->get_vertex_data(),
-                            InternalName::get_vertex());
-  if (vertex.has_column()) {
-    for (int pi = 0; pi < geom->get_num_primitives(); ++pi) {
-      const GeomPrimitive *primitive = geom->get_primitive(pi);
-      for (int vi = 0; vi < primitive->get_num_vertices(); ++vi) {
-        int vert = primitive->get_vertex(vi);
-        vertex.set_row(vert);
+    
+    if (got_vertex) {
+      float width = max_xyz[0] - min_xyz[0];
+      float height = max(max_xyz[1] - min_xyz[1],
+                         max_xyz[2] - min_xyz[2]);
+      
+      if (size_from_texels && got_texcoord) {
+        // If size_from_texels is true, we get the particle size from the
+        // number of texels in the source image.
+        float y_texels = _anims[0]->get_frame(0)->get_y_size() * fabs(_anims[0]->get_ur(0)[1] - _anims[0]->get_ll(0)[1]);
+        set_size(y_texels * width / height, y_texels);
         
         
-        if (!got_vertex) {
-          min_xyz = max_xyz = vertex.get_data3f();
-          got_vertex = true;
-          
-        } else {
-          const LVecBase3f &xyz = vertex.get_data3f();
-          
-          min_xyz[0] = min(min_xyz[0], xyz[0]);
-          max_xyz[0] = max(max_xyz[0], xyz[0]);
-          min_xyz[1] = min(min_xyz[1], xyz[1]);
-          max_xyz[1] = max(max_xyz[1], xyz[1]);
-          min_xyz[2] = min(min_xyz[2], xyz[2]);
-          max_xyz[2] = max(max_xyz[2], xyz[2]);
-        }
+      } else {
+        // If size_from_texels is false, we get the particle size from
+        // the size of the polygon.
+        set_size(width, height);
       }
       }
+      
+    } else {
+      // With no vertices, just punt.
+      set_size(1.0f, 1.0f);
     }
     }
+    
+    init_geoms();
   }
   }
+}
 
 
-  _texture = tex;
-  if (got_texcoord) {
-    // We don't really pay attention to orientation of UV's here; a
-    // minor flaw.  We assume the minimum is in the lower-left, and
-    // the maximum is in the upper-right.
-    set_ll_uv(min_uv);
-    set_ur_uv(max_uv);
-  }
 
 
-  if (got_vertex) {
-    float width = max_xyz[0] - min_xyz[0];
-    float height = max(max_xyz[1] - min_xyz[1],
-                       max_xyz[2] - min_xyz[2]);
-    
-    if (size_from_texels && got_texcoord) {
-      // If size_from_texels is true, we get the particle size from the
-      // number of texels in the source image.
-      float y_texels = _texture->get_y_size() * fabs(_ur_uv[1] - _ll_uv[1]);
-      set_size(y_texels * width / height, y_texels);
+////////////////////////////////////////////////////////////////////
+//    Function : SpriteParticleRenderer::add_from_node
+//      Access : public
+// Description : Sets the properties on this render from the geometry
+//               referenced by the indicated NodePath.  This should be
+//               a reference to a GeomNode; it extracts out the
+//               Texture and UV range from the GeomNode.
+//
+//               If size_from_texels is true, the particle size is
+//               based on the number of texels in the source image;
+//               otherwise, it is based on the size of the polygon
+//               found in the GeomNode.
+////////////////////////////////////////////////////////////////////
+void SpriteParticleRenderer::
+add_from_node(const NodePath &node_path, const string &model, const string &node, bool size_from_texels, bool resize) {
+  add_from_node(node_path,size_from_texels,resize);
+  get_last_anim()->set_source_info(model,node);
+}
+
+void SpriteParticleRenderer::
+add_from_node(const NodePath &node_path, bool size_from_texels, bool resize) {
+  nassertv(!node_path.is_empty());
+
+  pvector< TexCoordf > ll,ur;
+  GeomNode *gnode = NULL;
+  NodePathCollection np_col;
+  TextureCollection tex_col;
+  const Geom *geom;
+  const GeomPrimitive *primitive;
+  bool got_texcoord,got_vertex;
+
+  // Load the found textures into the renderer.
+  if (extract_textures_from_node(node_path,np_col,tex_col)) {
+    for (int i = 0; i < np_col.get_num_paths(); ++i) {
+      // Get the node from which we'll extract the geometry information.
+      gnode = DCAST(GeomNode, np_col[i].node()); 
       
       
-    } else {
-      // If size_from_texels is false, we get the particle size from
-      // the size of the polygon.
-      set_size(width, height);
+      // Now examine the UV's of the first Geom within the GeomNode.
+      nassertv(gnode->get_num_geoms() > 0);
+      geom = gnode->get_geom(0);
+      
+      got_texcoord = false;
+      TexCoordf min_uv(0.0f, 0.0f);
+      TexCoordf max_uv(0.0f, 0.0f);
+      
+      GeomVertexReader texcoord(geom->get_vertex_data(),
+                                InternalName::get_texcoord());
+      if (texcoord.has_column()) {
+        for (int pi = 0; pi < geom->get_num_primitives(); ++pi) {
+          primitive = geom->get_primitive(pi);
+          for (int vi = 0; vi < primitive->get_num_vertices(); ++vi) {
+            int vert = primitive->get_vertex(vi);
+            texcoord.set_row(vert);
+            
+            if (!got_texcoord) {
+                min_uv = max_uv = texcoord.get_data2f();
+                got_texcoord = true;
+                
+            } else {
+              const LVecBase2f &uv = texcoord.get_data2f();
+              
+              min_uv[0] = min(min_uv[0], uv[0]);
+              max_uv[0] = max(max_uv[0], uv[0]);
+              min_uv[1] = min(min_uv[1], uv[1]);
+              max_uv[1] = max(max_uv[1], uv[1]);
+            }
+          }
+        }
+      }
+        
+      if (got_texcoord) {
+        // We don't really pay attention to orientation of UV's here; a
+        // minor flaw.  We assume the minimum is in the lower-left, and
+        // the maximum is in the upper-right.
+        ll.push_back(min_uv);
+        ur.push_back(max_uv);
+      }
     }
     }
+    
+    _anims.push_back(new SpriteAnim(tex_col,ll,ur));
 
 
-  } else {
-    // With no vertices, just punt.
-    set_size(1.0f, 1.0f);
-  }
+    if (resize) {
+      gnode = DCAST(GeomNode, np_col[0].node());
+      geom = gnode->get_geom(0);
 
 
-  _source_type = ST_from_node;
+      got_vertex = false;
+      Vertexf min_xyz(0.0f, 0.0f, 0.0f);
+      Vertexf max_xyz(0.0f, 0.0f, 0.0f);
+      
+      GeomVertexReader vertex(geom->get_vertex_data(),
+                              InternalName::get_vertex());
+      if (vertex.has_column()) {
+        for (int pi = 0; pi < geom->get_num_primitives(); ++pi) {
+          const GeomPrimitive *primitive = geom->get_primitive(pi);
+          for (int vi = 0; vi < primitive->get_num_vertices(); ++vi) {
+            int vert = primitive->get_vertex(vi);
+            vertex.set_row(vert);
+            
+            if (!got_vertex) {
+              min_xyz = max_xyz = vertex.get_data3f();
+              got_vertex = true;
+              
+            } else {
+              const LVecBase3f &xyz = vertex.get_data3f();
+              
+              min_xyz[0] = min(min_xyz[0], xyz[0]);
+              max_xyz[0] = max(max_xyz[0], xyz[0]);
+              min_xyz[1] = min(min_xyz[1], xyz[1]);
+              max_xyz[1] = max(max_xyz[1], xyz[1]);
+              min_xyz[2] = min(min_xyz[2], xyz[2]);
+              max_xyz[2] = max(max_xyz[2], xyz[2]);
+            }
+          }
+        }
+      }     
 
 
-  init_geoms();
+      if (got_vertex) {
+        float width = max_xyz[0] - min_xyz[0];
+        float height = max(max_xyz[1] - min_xyz[1],
+                           max_xyz[2] - min_xyz[2]);
+      
+        if (size_from_texels && got_texcoord) {
+          // If size_from_texels is true, we get the particle size from the
+          // number of texels in the source image.
+          float y_texels = _anims[0]->get_frame(0)->get_y_size() * fabs(_anims[0]->get_ur(0)[1] - _anims[0]->get_ll(0)[1]);
+          set_size(y_texels * width / height, y_texels);
+          
+        } else {
+          // If size_from_texels is false, we get the particle size from
+          // the size of the polygon.
+          set_size(width, height);
+        }
+        
+      } else {
+        // With no vertices, just punt.
+        set_size(1.0f, 1.0f);
+      }
+    }
+
+    init_geoms();
+    }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -253,15 +490,14 @@ set_from_node(const NodePath &node_path, bool size_from_texels) {
 void SpriteParticleRenderer::
 void SpriteParticleRenderer::
 resize_pool(int new_size) {
 resize_pool(int new_size) {
   if (new_size != _pool_size) {
   if (new_size != _pool_size) {
-    _pool_size = new_size;
-    
+    _pool_size = new_size;    
     init_geoms();
     init_geoms();
   }
   }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //    Function : SpriteParticleRenderer::init_geoms
 //    Function : SpriteParticleRenderer::init_geoms
-//      Access : private
+//      Access : public
 // Description : initializes everything, called on traumatic events
 // Description : initializes everything, called on traumatic events
 //               such as construction and serious particlesystem
 //               such as construction and serious particlesystem
 //               modifications
 //               modifications
@@ -269,10 +505,11 @@ resize_pool(int new_size) {
 void SpriteParticleRenderer::
 void SpriteParticleRenderer::
 init_geoms() {
 init_geoms() {
   CPT(RenderState) state = _render_state;
   CPT(RenderState) state = _render_state;
+  SpriteAnim *anim;
+  int anim_count = _anims.size();
+  int i,j;
 
 
-  PT(Geom) geom = new Geom; 
-  _sprite_primitive = geom;
-  
+  // Setup format
   PT(GeomVertexArrayFormat) array_format = new GeomVertexArrayFormat
   PT(GeomVertexArrayFormat) array_format = new GeomVertexArrayFormat
     (InternalName::get_vertex(), 3, Geom::NT_float32, Geom::C_point,
     (InternalName::get_vertex(), 3, Geom::NT_float32, Geom::C_point,
      InternalName::get_color(), 1, Geom::NT_packed_dabc, Geom::C_color);
      InternalName::get_color(), 1, Geom::NT_packed_dabc, Geom::C_color);
@@ -281,7 +518,7 @@ init_geoms() {
     array_format->add_column
     array_format->add_column
       (InternalName::get_rotate(), 1, Geom::NT_float32, Geom::C_other);
       (InternalName::get_rotate(), 1, Geom::NT_float32, Geom::C_other);
   }
   }
-  
+
   _base_y_scale = _initial_y_scale;
   _base_y_scale = _initial_y_scale;
   _aspect_ratio = _width / _height;
   _aspect_ratio = _width / _height;
   
   
@@ -304,35 +541,73 @@ init_geoms() {
   CPT(GeomVertexFormat) format = GeomVertexFormat::register_format
   CPT(GeomVertexFormat) format = GeomVertexFormat::register_format
     (new GeomVertexFormat(array_format));
     (new GeomVertexFormat(array_format));
   
   
-  _vdata = new GeomVertexData
-    ("particles", format, Geom::UH_dynamic);
-  geom->set_vertex_data(_vdata);
-  _sprites = new GeomPoints(Geom::UH_dynamic);
-  geom->add_primitive(_sprites);
-  
-  state = state->add_attrib(RenderModeAttrib::make(RenderModeAttrib::M_unchanged, _base_y_scale * _height, true));
-  
-  if (_texture != (Texture *)NULL) {
-    state = state->add_attrib(TextureAttrib::make(_texture));
-    state = state->add_attrib(TexGenAttrib::make(TextureStage::get_default(), TexGenAttrib::M_point_sprite));
-    
-    // Build a matrix to convert the texture coordinates to the ul, lr
-    // space.
-    LPoint2f ul(_ll_uv[0], _ur_uv[1]);
-    LPoint2f lr(_ur_uv[0], _ll_uv[1]);
-    LVector2f sc = lr - ul;
-    
-    LMatrix4f mat
-      (sc[0], 0.0f, 0.0f, 0.0f,
-       0.0f, sc[1], 0.0f, 0.0f,
-       0.0f, 0.0f,  1.0f, 0.0f,
-       ul[0], ul[1], 0.0f, 1.0f);
-    state = state->add_attrib(TexMatrixAttrib::make(mat));
+  // Reset render() data structures
+  for (i = 0; i < (int)_ttl_count.size(); ++i) {
+    delete [] _ttl_count[i];
   }
   }
+  _anim_size.resize(anim_count);
+  _ttl_count.clear();
+  _ttl_count.resize(anim_count);
+
+  // Reset sprite primitive data in order to prepare for next pass.
+  _sprite_primitive.clear();
+  _sprites.clear();
+  _vdata.clear();
+  _sprite_writer.clear();
 
 
   GeomNode *render_node = get_render_node();
   GeomNode *render_node = get_render_node();
   render_node->remove_all_geoms();
   render_node->remove_all_geoms();
-  render_node->add_geom(_sprite_primitive, state);
+
+  // For each animation...
+  for (i = 0; i < anim_count; ++i) {
+    anim = _anims[i];
+    _anim_size[i] = anim->get_num_frames();    
+
+    _sprite_primitive.push_back(pvector<PT(Geom)>());
+    _sprites.push_back(pvector<PT(GeomPoints)>());
+    _vdata.push_back(pvector<PT(GeomVertexData)>());
+    _sprite_writer.push_back(pvector<SpriteWriter>());
+
+    // For each frame of the animation...
+    for (j = 0; j < _anim_size[i]; ++j) {
+      _ttl_count[i] = new int[_anim_size[i]];
+      PT(Geom) geom = new Geom;
+      _sprite_primitive[i].push_back((Geom*)geom);
+      _vdata[i].push_back(new GeomVertexData("particles", format, Geom::UH_dynamic));
+      geom->set_vertex_data(_vdata[i][j]);
+      _sprites[i].push_back(new GeomPoints(Geom::UH_dynamic));
+      geom->add_primitive(_sprites[i][j]);
+      
+      // This will be overwritten in render(), but we had to have some initial value 
+      // since there are no default constructors for GeomVertexWriter.
+      _sprite_writer[i].push_back(SpriteWriter(GeomVertexWriter(_vdata[i][j], InternalName::get_vertex()),
+                                               GeomVertexWriter(_vdata[i][j], InternalName::get_color()),
+                                               GeomVertexWriter(_vdata[i][j], InternalName::get_rotate()),
+                                               GeomVertexWriter(_vdata[i][j], InternalName::get_size()),
+                                               GeomVertexWriter(_vdata[i][j], InternalName::get_aspect_ratio())));
+
+      state = state->add_attrib(RenderModeAttrib::make(RenderModeAttrib::M_unchanged, _base_y_scale * _height, true));
+      if (anim->get_frame(j) != (Texture *)NULL) {
+        state = state->add_attrib(TextureAttrib::make(anim->get_frame(j)));
+        state = state->add_attrib(TexGenAttrib::make(TextureStage::get_default(), TexGenAttrib::M_point_sprite));
+        
+        // Build a matrix to convert the texture coordinates to the ll, ur
+        // space.
+        LPoint2f ul(anim->get_ur(j)[0], anim->get_ur(j)[1]);
+        LPoint2f lr(anim->get_ll(j)[0], anim->get_ll(j)[1]);
+        LVector2f sc = lr - ul;
+        
+        LMatrix4f mat
+          (sc[0], 0.0f, 0.0f, 0.0f,
+           0.0f, sc[1], 0.0f, 0.0f,
+           0.0f, 0.0f,  1.0f, 0.0f,
+           ul[0], ul[1], 0.0f, 1.0f);
+        state = state->add_attrib(TexMatrixAttrib::make(mat));
+        
+        render_node->add_geom(_sprite_primitive[i][j], state);
+      }
+    }
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -343,7 +618,8 @@ init_geoms() {
 //               out we don't really want it.
 //               out we don't really want it.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void SpriteParticleRenderer::
 void SpriteParticleRenderer::
-birth_particle(int) {
+birth_particle(int index) {
+  _birth_list.push_back(index);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -362,16 +638,52 @@ kill_particle(int) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void SpriteParticleRenderer::
 void SpriteParticleRenderer::
 render(pvector< PT(PhysicsObject) >& po_vector, int ttl_particles) {
 render(pvector< PT(PhysicsObject) >& po_vector, int ttl_particles) {
+  // There is no texture data available, exit.
+  if (_anims.empty()) {
+    return;
+  }
+  
   BaseParticle *cur_particle;
   BaseParticle *cur_particle;
-
   int remaining_particles = ttl_particles;
   int remaining_particles = ttl_particles;
-  int i;
-
-  GeomVertexWriter vertex(_vdata, InternalName::get_vertex());
-  GeomVertexWriter color(_vdata, InternalName::get_color());
-  GeomVertexWriter rotate(_vdata, InternalName::get_rotate());
-  GeomVertexWriter size(_vdata, InternalName::get_size());
-  GeomVertexWriter aspect_ratio(_vdata, InternalName::get_aspect_ratio());
+  int i,j;                                  // loop counters
+  int anim_count = _anims.size();           // number of animations
+  int frame;                                // frame index, used in indicating which frame to use when not animated
+  // First, since this is the only time we have access to the actual particles, do some delayed initialization.
+  if (_animate_frames || anim_count) {
+    if (!_birth_list.empty()) {
+      for (pvector<int>::iterator vIter = _birth_list.begin(); vIter != _birth_list.end(); ++vIter) {
+        cur_particle = (BaseParticle*)po_vector[*vIter].p();
+        i = int(NORMALIZED_RAND()*anim_count);
+        
+        // If there are multiple animations to choose from, choose one at random for this new particle
+        cur_particle->set_index(i < anim_count?i:i-1);
+        
+        // This is an experimental age offset so that the animations don't appear synchronized.
+        // If we are using animations, try to vary the frame flipping a bit for particles in the same litter.
+        // A similar effect might be a achieved by using a small lifespan spread value on the factory.
+        if (_animate_frames) {
+          cur_particle->set_age(cur_particle->get_age()+i/10.0*cur_particle->get_lifespan());
+        }
+      }
+    }
+  }
+  _birth_list.clear();
+  
+  // Create vertex writers for each of the possible geoms.
+  // Could possibly be changed to only create writers for geoms that would be used 
+  //    according to the animation configuration.
+  for (i = 0; i < anim_count; ++i) {
+    for (j = 0; j < _anim_size[i]; ++j) {
+      // Set the particle per frame counts to 0.
+      memset(_ttl_count[i],NULL,_anim_size[i]*sizeof(int));
+      
+      _sprite_writer[i][j].vertex = GeomVertexWriter(_vdata[i][j], InternalName::get_vertex());
+      _sprite_writer[i][j].color = GeomVertexWriter(_vdata[i][j], InternalName::get_color());
+      _sprite_writer[i][j].rotate = GeomVertexWriter(_vdata[i][j], InternalName::get_rotate());
+      _sprite_writer[i][j].size = GeomVertexWriter(_vdata[i][j], InternalName::get_size());
+      _sprite_writer[i][j].aspect_ratio = GeomVertexWriter(_vdata[i][j], InternalName::get_aspect_ratio());
+    }
+  }
 
 
   // init the aabb
   // init the aabb
   _aabb_min.set(99999.0f, 99999.0f, 99999.0f);
   _aabb_min.set(99999.0f, 99999.0f, 99999.0f);
@@ -381,8 +693,9 @@ render(pvector< PT(PhysicsObject) >& po_vector, int ttl_particles) {
   for (i = 0; i < (int)po_vector.size(); i++) {
   for (i = 0; i < (int)po_vector.size(); i++) {
     cur_particle = (BaseParticle *) po_vector[i].p();
     cur_particle = (BaseParticle *) po_vector[i].p();
 
 
-    if (!cur_particle->get_alive())
+    if (!cur_particle->get_alive()) {
       continue;
       continue;
+    }
 
 
     LPoint3f position = cur_particle->get_position();
     LPoint3f position = cur_particle->get_position();
 
 
@@ -406,12 +719,32 @@ render(pvector< PT(PhysicsObject) >& po_vector, int ttl_particles) {
 
 
 
 
     float t = cur_particle->get_parameterized_age();
     float t = cur_particle->get_parameterized_age();
+    int anim_index = cur_particle->get_index();
+
+    if(_animation_removed && (anim_index >= anim_count)) {
+      anim_index = int(NORMALIZED_RAND()*anim_count);
+      anim_index = anim_index<anim_count?anim_index:anim_index-1;
+      cur_particle->set_index(anim_index);
+    }
+
+    // Find the frame
+    if (_animate_frames) {
+      if (_animate_frames_rate == 0.0f) {
+        frame = (int)(t*_anim_size[anim_index]);
+      } else {
+        frame = (int)fmod(cur_particle->get_age()*_animate_frames_rate+1,_anim_size[anim_index]);
+      }
+    } else {
+      frame = _animate_frames_index;
+    }
+
+    // Quick check make sure our math above didn't result in an invalid frame.
+    frame = (frame < _anim_size[anim_index]) ? frame : (_anim_size[anim_index]-1);
+    ++_ttl_count[anim_index][frame];
 
 
     // Calculate the color
     // Calculate the color
     // This is where we'll want to give the renderer the new color
     // This is where we'll want to give the renderer the new color
-    //Colorf c = _color;
-    Colorf c = _color_interpolation_manager->generateColor(t);
-    
+    Colorf c = _color_interpolation_manager->generateColor(t);    
 
 
     int alphamode=get_alpha_mode();
     int alphamode=get_alpha_mode();
     if (alphamode != PR_ALPHA_NONE) {
     if (alphamode != PR_ALPHA_NONE) {
@@ -428,14 +761,15 @@ render(pvector< PT(PhysicsObject) >& po_vector, int ttl_particles) {
       }
       }
     }
     }
     
     
-    vertex.add_data3f(position);
-    color.add_data4f(c);
-
+    // Send the data on its way...
+    //    if(anim_index>_anims.size() || frame > _sprite_writer[anim_index].size()) 
+    _sprite_writer[anim_index][frame].vertex.add_data3f(position);
+    _sprite_writer[anim_index][frame].color.add_data4f(c);
+    
     float current_x_scale = _initial_x_scale;
     float current_x_scale = _initial_x_scale;
     float current_y_scale = _initial_y_scale;
     float current_y_scale = _initial_y_scale;
     
     
     if (_animate_x_ratio || _animate_y_ratio) {
     if (_animate_x_ratio || _animate_y_ratio) {
-      float t = cur_particle->get_parameterized_age();
       if (_blend_method == PP_BLEND_CUBIC) {
       if (_blend_method == PP_BLEND_CUBIC) {
         t = CUBIC_T(t);
         t = CUBIC_T(t);
       }
       }
@@ -450,17 +784,16 @@ render(pvector< PT(PhysicsObject) >& po_vector, int ttl_particles) {
       }
       }
     }
     }
     
     
-    if (size.has_column()) {
-      size.add_data1f(current_y_scale * _height);
+    if (_sprite_writer[anim_index][frame].size.has_column()) {
+      _sprite_writer[anim_index][frame].size.add_data1f(current_y_scale * _height);
     }
     }
-    if (aspect_ratio.has_column()) {
-      aspect_ratio.add_data1f(_aspect_ratio * current_x_scale / current_y_scale);
+    if (_sprite_writer[anim_index][frame].aspect_ratio.has_column()) {
+      _sprite_writer[anim_index][frame].aspect_ratio.add_data1f(_aspect_ratio * current_x_scale / current_y_scale);
     }
     }
-    
     if (_animate_theta) {
     if (_animate_theta) {
-      rotate.add_data1f(cur_particle->get_theta());
-    } else if (rotate.has_column()) {
-      rotate.add_data1f(_theta);
+      _sprite_writer[anim_index][frame].rotate.add_data1f(cur_particle->get_theta());
+    } else if (_sprite_writer[anim_index][frame].rotate.has_column()) {
+      _sprite_writer[anim_index][frame].rotate.add_data1f(_theta);
     }
     }
 
 
     // maybe jump out early?
     // maybe jump out early?
@@ -470,15 +803,36 @@ render(pvector< PT(PhysicsObject) >& po_vector, int ttl_particles) {
     }
     }
   }
   }
 
 
-  _sprites->clear_vertices();
-  _sprites->add_next_vertices(ttl_particles);
+  for (i = 0; i < anim_count; ++i) {
+    for (j = 0; j < _anim_size[i]; ++j) {
+      _sprites[i][j]->clear_vertices();
+    }
+  }
+
+  if (_animate_frames) {
+    for (i = 0; i < anim_count; ++i) {
+      for (j = 0; j < _anim_size[i]; ++j) {
+        _sprites[i][j]->add_next_vertices(_ttl_count[i][j]);
+      }
+    }
+  } else {
+    for (i = 0; i < anim_count; ++i) {
+      _sprites[i][_animate_frames_index]->add_next_vertices(_ttl_count[i][_animate_frames_index]);
+    }
+  }
 
 
   // done filling geompoint node, now do the bb stuff
   // done filling geompoint node, now do the bb stuff
   LPoint3f aabb_center = _aabb_min + ((_aabb_max - _aabb_min) * 0.5f);
   LPoint3f aabb_center = _aabb_min + ((_aabb_max - _aabb_min) * 0.5f);
   float radius = (aabb_center - _aabb_min).length();
   float radius = (aabb_center - _aabb_min).length();
 
 
-  _sprite_primitive->set_bound(BoundingSphere(aabb_center, radius));
+  for (i = 0; i < anim_count; ++i) {
+    for (j = 0; j < _anim_size[i]; ++j) {
+      _sprite_primitive[i][j]->set_bound(BoundingSphere(aabb_center, radius));
+    }
+  }
+
   get_render_node()->mark_bound_stale();
   get_render_node()->mark_bound_stale();
+  _animation_removed = false;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -503,7 +857,7 @@ output(ostream &out) const {
 void SpriteParticleRenderer::
 void SpriteParticleRenderer::
 write(ostream &out, int indent_level) const {
 write(ostream &out, int indent_level) const {
   indent(out, indent_level) << "SpriteParticleRenderer:\n";
   indent(out, indent_level) << "SpriteParticleRenderer:\n";
-  indent(out, indent_level + 2) << "_sprite_primitive "<<_sprite_primitive<<"\n";
+  //  indent(out, indent_level + 2) << "_sprite_primitive "<<_sprite_primitive<<"\n";
   indent(out, indent_level + 2) << "_color "<<_color<<"\n";
   indent(out, indent_level + 2) << "_color "<<_color<<"\n";
   indent(out, indent_level + 2) << "_initial_x_scale "<<_initial_x_scale<<"\n";
   indent(out, indent_level + 2) << "_initial_x_scale "<<_initial_x_scale<<"\n";
   indent(out, indent_level + 2) << "_final_x_scale "<<_final_x_scale<<"\n";
   indent(out, indent_level + 2) << "_final_x_scale "<<_final_x_scale<<"\n";
@@ -517,6 +871,5 @@ write(ostream &out, int indent_level) const {
   indent(out, indent_level + 2) << "_aabb_min "<<_aabb_min<<"\n";
   indent(out, indent_level + 2) << "_aabb_min "<<_aabb_min<<"\n";
   indent(out, indent_level + 2) << "_aabb_max "<<_aabb_max<<"\n";
   indent(out, indent_level + 2) << "_aabb_max "<<_aabb_max<<"\n";
   indent(out, indent_level + 2) << "_pool_size "<<_pool_size<<"\n";
   indent(out, indent_level + 2) << "_pool_size "<<_pool_size<<"\n";
-  indent(out, indent_level + 2) << "_source_type "<<_source_type<<"\n";
   BaseParticleRenderer::write(out, indent_level + 2);
   BaseParticleRenderer::write(out, indent_level + 2);
 }
 }

+ 153 - 20
panda/src/particlesystem/spriteParticleRenderer.h

@@ -20,6 +20,7 @@
 #define SPRITEPARTICLERENDERER_H
 #define SPRITEPARTICLERENDERER_H
 
 
 #include "pandabase.h"
 #include "pandabase.h"
+#include "pvector.h"
 #include "baseParticleRenderer.h"
 #include "baseParticleRenderer.h"
 #include "baseParticle.h"
 #include "baseParticle.h"
 #include "texture.h"
 #include "texture.h"
@@ -28,9 +29,121 @@
 #include "geomVertexData.h"
 #include "geomVertexData.h"
 #include "geomPoints.h"
 #include "geomPoints.h"
 #include "colorInterpolationManager.h"
 #include "colorInterpolationManager.h"
+#include "geomVertexWriter.h"
+#include "textureCollection.h"
+#include "nodePathCollection.h"
 
 
 class NodePath;
 class NodePath;
 
 
+class SpriteWriter {
+public:
+  SpriteWriter(GeomVertexWriter v,
+               GeomVertexWriter c,
+               GeomVertexWriter r,
+               GeomVertexWriter s,
+               GeomVertexWriter a):
+    vertex(v),
+    color(c),
+    rotate(r),
+    size(s),
+    aspect_ratio(a){
+    };
+
+  SpriteWriter(const SpriteWriter &copy):
+    vertex(copy.vertex),
+    color(copy.color),
+    rotate(copy.rotate),
+    size(copy.size),
+    aspect_ratio(copy.aspect_ratio) {
+  };
+
+  GeomVertexWriter vertex;
+  GeomVertexWriter color;
+  GeomVertexWriter rotate;
+  GeomVertexWriter size;
+  GeomVertexWriter aspect_ratio;
+};
+
+class SpriteAnim : public ReferenceCount{
+PUBLISHED:
+  enum SourceType {
+    ST_texture,
+    ST_from_node,
+  };
+
+  void set_source_info(const string &tex) {
+    _source_type = ST_texture;
+    _source_tex = tex;
+  }
+
+  void set_source_info(const string &model, const string &node) {
+    _source_type = ST_from_node;
+    _source_model = model;
+    _source_node = node;
+  }
+
+  SourceType get_source_type() const {
+    return _source_type;
+  }
+
+  string get_tex_source() const {
+    return _source_tex;
+  }
+
+  string get_model_source() const {
+    return _source_model;
+  }
+
+  string get_node_source() const {
+    return _source_node;
+  }
+
+  int get_num_frames(void) const {
+    return textures.size();
+  }
+
+public:
+  SpriteAnim(Texture* t, TexCoordf ll, TexCoordf ur) {
+    textures.push_back(t);
+    this->ll.push_back(ll);
+    this->ur.push_back(ur);
+  };
+
+  SpriteAnim(const TextureCollection &t, const pvector< TexCoordf > &lls, const pvector< TexCoordf > &urs) :
+    ll(lls),
+    ur(urs) {
+    for (int i = 0; i < t.get_num_textures(); ++i) {
+      textures.push_back(t.get_texture(i));
+    }
+  };
+  
+  void set_ll(const int n, TexCoordf c) {
+    ll[n] = c;
+  }
+
+  void set_ur(const int n, TexCoordf c) {
+    ur[n] = c;
+  }
+
+  Texture *get_frame(const int n) const {
+    return textures[n];
+  };
+
+  TexCoordf get_ll(const int n) const {
+    return ll[n];
+  }
+
+  TexCoordf get_ur(const int n) const {
+    return ur[n];
+  }
+
+private:
+  pvector< PT(Texture) > textures;
+  pvector< TexCoordf > ll,ur;
+  SourceType _source_type;
+  string _source_tex,_source_model,_source_node;
+};
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : SpriteParticleRenderer
 //       Class : SpriteParticleRenderer
 // Description : Renders a particle system with high-speed nasty
 // Description : Renders a particle system with high-speed nasty
@@ -38,44 +151,52 @@ class NodePath;
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDAPHYSICS SpriteParticleRenderer : public BaseParticleRenderer {
 class EXPCL_PANDAPHYSICS SpriteParticleRenderer : public BaseParticleRenderer {
 PUBLISHED:
 PUBLISHED:
-  // This enumerated type indicates the source of the sprite texture:
-  // whether it came from an explicit call to set_texture(), or
-  // whether from a call to set_from_node().
-  enum SourceType {
-    ST_texture,
-    ST_from_node,
-  };
-
   SpriteParticleRenderer(Texture *tex = (Texture *) NULL);
   SpriteParticleRenderer(Texture *tex = (Texture *) NULL);
   SpriteParticleRenderer(const SpriteParticleRenderer &copy);
   SpriteParticleRenderer(const SpriteParticleRenderer &copy);
   virtual ~SpriteParticleRenderer();
   virtual ~SpriteParticleRenderer();
 
 
   virtual BaseParticleRenderer *make_copy();
   virtual BaseParticleRenderer *make_copy();
 
 
-  INLINE SourceType get_source_type() const;
-
   void set_from_node(const NodePath &node_path, bool size_from_texels = true);
   void set_from_node(const NodePath &node_path, bool size_from_texels = true);
+  void set_from_node(const NodePath &node_path, const string &model, const string &node, bool size_from_texels = true);
+  void add_from_node(const NodePath &node_path, bool size_from_texels = true, bool resize = false);
+  void add_from_node(const NodePath &node_path, const string &model, const string &node, bool size_from_texels = true, bool resize = false);
 
 
-  INLINE void set_texture(Texture *tex, float texels_per_unit = 1.0);
+  INLINE void set_texture(Texture *tex, float texels_per_unit = 1.0f);
+  INLINE void set_texture(Texture *tex, const string &tex_path, float texels_per_unit = 1.0f);
+  INLINE void add_texture(Texture *tex, float texels_per_unit = 1.0f, bool resize = false);
+  INLINE void add_texture(Texture *tex, const string &tex_path, float texels_per_unit = 1.0f, bool resize = false);
+  INLINE void remove_animation(const int n);
   INLINE void set_ll_uv(const TexCoordf &ll_uv);
   INLINE void set_ll_uv(const TexCoordf &ll_uv);
+  INLINE void set_ll_uv(const TexCoordf &ll_uv, const int anim, const int frame);
   INLINE void set_ur_uv(const TexCoordf &ur_uv);
   INLINE void set_ur_uv(const TexCoordf &ur_uv);
+  INLINE void set_ur_uv(const TexCoordf &ur_uv, const int anim, const int frame);
   INLINE void set_size(float width, float height);
   INLINE void set_size(float width, float height);
   INLINE void set_color(const Colorf &color);
   INLINE void set_color(const Colorf &color);
   INLINE void set_x_scale_flag(bool animate_x_ratio);
   INLINE void set_x_scale_flag(bool animate_x_ratio);
   INLINE void set_y_scale_flag(bool animate_y_ratio);
   INLINE void set_y_scale_flag(bool animate_y_ratio);
   INLINE void set_anim_angle_flag(bool animate_theta);
   INLINE void set_anim_angle_flag(bool animate_theta);
   INLINE void set_initial_x_scale(float initial_x_scale);
   INLINE void set_initial_x_scale(float initial_x_scale);
-  INLINE void set_final_x_scale(float final_x_scale);
+  INLINE void set_final_x_scale(float final_x_scale);  
   INLINE void set_initial_y_scale(float initial_y_scale);
   INLINE void set_initial_y_scale(float initial_y_scale);
   INLINE void set_final_y_scale(float final_y_scale);
   INLINE void set_final_y_scale(float final_y_scale);
   INLINE void set_nonanimated_theta(float theta);
   INLINE void set_nonanimated_theta(float theta);
   INLINE void set_alpha_blend_method(ParticleRendererBlendMethod bm);
   INLINE void set_alpha_blend_method(ParticleRendererBlendMethod bm);
   INLINE void set_alpha_disable(bool ad);
   INLINE void set_alpha_disable(bool ad);
+  INLINE void set_animate_frames_enable(bool an);
+  INLINE void set_animate_frames_rate(float r);
+  INLINE void set_animate_frames_index(int i);
 
 
   INLINE Texture *get_texture() const;
   INLINE Texture *get_texture() const;
+  INLINE Texture *get_texture(const int anim, const int frame) const;
+  INLINE int get_num_anims() const;
+  INLINE SpriteAnim *get_anim(const int n) const;
+  INLINE SpriteAnim *get_last_anim() const;
   INLINE ColorInterpolationManager* get_color_interpolation_manager() const;
   INLINE ColorInterpolationManager* get_color_interpolation_manager() const;
-  INLINE const TexCoordf &get_ll_uv() const;
-  INLINE const TexCoordf &get_ur_uv() const;
+  INLINE TexCoordf get_ll_uv() const;
+  INLINE TexCoordf get_ll_uv(const int anim, const int frame) const;
+  INLINE TexCoordf get_ur_uv() const;
+  INLINE TexCoordf get_ur_uv(const int anim, const int frame) const;
   INLINE float get_width() const;
   INLINE float get_width() const;
   INLINE float get_height() const;
   INLINE float get_height() const;
   INLINE Colorf get_color() const;
   INLINE Colorf get_color() const;
@@ -89,20 +210,23 @@ PUBLISHED:
   INLINE float get_nonanimated_theta() const;
   INLINE float get_nonanimated_theta() const;
   INLINE ParticleRendererBlendMethod get_alpha_blend_method() const;
   INLINE ParticleRendererBlendMethod get_alpha_blend_method() const;
   INLINE bool get_alpha_disable() const;  
   INLINE bool get_alpha_disable() const;  
+  INLINE bool get_animate_frames_enable() const;  
+  INLINE float get_animate_frames_rate() const;
+  INLINE int get_animate_frames_index() const;
 
 
   virtual void output(ostream &out) const;
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level = 0) const;
   virtual void write(ostream &out, int indent_level = 0) const;
 
 
 private:
 private:
-  PT(Geom) _sprite_primitive;
-  PT(GeomPoints) _sprites;
-  PT(Texture) _texture;
+  pvector< pvector< PT(Geom) > > _sprite_primitive;
+  pvector< pvector< PT(GeomPoints) > > _sprites;
+  pvector< pvector< SpriteWriter > > _sprite_writer;
+  pvector< pvector< PT(GeomVertexData) > > _vdata;
 
 
-  PT(GeomVertexData) _vdata;
+  pvector< PT(SpriteAnim) > _anims;            // Stores texture references and UV info for each geom.
 
 
   Colorf _color;
   Colorf _color;
 
 
-  TexCoordf _ll_uv, _ur_uv;
   float _height;
   float _height;
   float _width;
   float _width;
   float _initial_x_scale;
   float _initial_x_scale;
@@ -112,11 +236,15 @@ private:
   float _theta;
   float _theta;
   float _base_y_scale;
   float _base_y_scale;
   float _aspect_ratio;
   float _aspect_ratio;
+  float _animate_frames_rate;
+  int _animate_frames_index;
 
 
   bool _animate_x_ratio;
   bool _animate_x_ratio;
   bool _animate_y_ratio;
   bool _animate_y_ratio;
   bool _animate_theta;
   bool _animate_theta;
   bool _alpha_disable;
   bool _alpha_disable;
+  bool _animate_frames;
+  bool _animation_removed;
 
 
   ParticleRendererBlendMethod _blend_method;
   ParticleRendererBlendMethod _blend_method;
   PT(ColorInterpolationManager) _color_interpolation_manager;
   PT(ColorInterpolationManager) _color_interpolation_manager;
@@ -125,7 +253,6 @@ private:
   Vertexf _aabb_max;
   Vertexf _aabb_max;
 
 
   int _pool_size;
   int _pool_size;
-  SourceType _source_type;
 
 
   virtual void birth_particle(int index);
   virtual void birth_particle(int index);
   virtual void kill_particle(int index);
   virtual void kill_particle(int index);
@@ -133,6 +260,12 @@ private:
   virtual void render(pvector< PT(PhysicsObject) > &po_vector,
   virtual void render(pvector< PT(PhysicsObject) > &po_vector,
                       int ttl_particles);
                       int ttl_particles);
   virtual void resize_pool(int new_size);
   virtual void resize_pool(int new_size);
+  int extract_textures_from_node(const NodePath &node_path, NodePathCollection &np_col, TextureCollection &tex_col);
+
+  pvector<int> _anim_size;   // Holds the number of frames in each animation.
+  pvector<int*> _ttl_count;  // _ttl_count[i][j] holds the number of particles attached to animation 'i' at frame 'j'.
+  pvector<int> _birth_list;  // Holds the list of particles that need a new random animation to start on.
+
 };
 };
 
 
 #include "spriteParticleRenderer.I"
 #include "spriteParticleRenderer.I"