Переглянути джерело

first pass at primitive bumpmapping support

David Rose 20 роки тому
батько
коміт
166e7414a6

+ 1 - 0
panda/src/display/graphicsStateGuardian.cxx

@@ -27,6 +27,7 @@
 #include "colorScaleAttrib.h"
 #include "lightAttrib.h"
 #include "textureAttrib.h"
+#include "texGenAttrib.h"
 #include "renderState.h"
 #include "depthWriteAttrib.h"
 #include "colorWriteAttrib.h"

+ 9 - 8
panda/src/framework/windowFramework.cxx

@@ -848,10 +848,10 @@ set_lighting(bool enable) {
     if (!_got_lights) {
       setup_lights();
     }
-    render.node()->set_attrib(LightAttrib::make(LightAttrib::O_add, 
-                                                _alight, _dlight));
+    render.set_light(_alight);
+    render.set_light(_dlight);
   } else {
-    render.node()->clear_attrib(LightAttrib::get_class_type());
+    render.clear_light();
   }
 
   _lighting_enabled = enable;
@@ -963,11 +963,12 @@ setup_lights() {
   NodePath camera_group = get_camera_group();
   NodePath light_group = camera_group.attach_new_node("lights");
 
-  _alight = new AmbientLight("ambient");
-  _alight->set_color(Colorf(0.2f, 0.2f, 0.2f, 1.0f));
-  _dlight = new DirectionalLight("directional");
-  light_group.attach_new_node(_alight);
-  light_group.attach_new_node(_dlight);
+  AmbientLight *alight = new AmbientLight("ambient");
+  alight->set_color(Colorf(0.2f, 0.2f, 0.2f, 1.0f));
+  DirectionalLight *dlight = new DirectionalLight("directional");
+
+  _alight = light_group.attach_new_node(alight);
+  _dlight = light_group.attach_new_node(dlight);
   
   _got_lights = true;
 }

+ 2 - 2
panda/src/framework/windowFramework.h

@@ -144,8 +144,8 @@ private:
   NodePath _mouse;
   PT(Trackball) _trackball;
 
-  AmbientLight *_alight;
-  DirectionalLight *_dlight;
+  NodePath _alight;
+  NodePath _dlight;
   
   bool _got_keyboard;
   bool _got_trackball;

+ 5 - 0
panda/src/gobj/geomEnums.h

@@ -132,6 +132,11 @@ PUBLISHED:
 
     // The union of the above shade model types.
     GR_shade_model_bits     = 0x6000,
+
+    // If there is a TexGenAttrib in effect with M_light_vector
+    // enabled, meaning we need to generate the tangent space light
+    // vector as the texture coordinates.
+    GR_texcoord_light_vector = 0x8000,
   };
 
   // The shade model specifies whether the per-vertex colors and

+ 110 - 2
panda/src/gobj/texture.cxx

@@ -92,12 +92,17 @@ void Texture::
 setup_texture(Texture::TextureType texture_type, int x_size, int y_size, 
               int z_size, Texture::ComponentType component_type, 
               Texture::Format format) {
-#ifndef NDEBUG
   if (texture_type == TT_cube_map) {
     // Cube maps must always consist of six square images.
     nassertv(x_size == y_size && z_size == 6);
+
+    // In principle the wrap mode shouldn't mean anything to a cube
+    // map, but some drivers seem to misbehave if it's other than
+    // WM_clamp.
+    _wrap_u = WM_clamp;
+    _wrap_v = WM_clamp;
+    _wrap_w = WM_clamp;
   }
-#endif
 
   _texture_type = texture_type;
   _x_size = x_size;
@@ -110,6 +115,109 @@ setup_texture(Texture::TextureType texture_type, int x_size, int y_size,
   _loaded_from_disk = false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::generate_normalization_cube_map
+//       Access: Published
+//  Description: Generates a special cube map image in the texture
+//               that can be used to apply bump mapping effects: for
+//               each texel in the cube map that is indexed by the 3-d
+//               texture coordinates (x, y, z), the resulting value is
+//               the normalized vector (x, y, z) (compressed from
+//               -1..1 into 0..1).
+//
+//               This also implicitly sets keep_ram_image to true.
+////////////////////////////////////////////////////////////////////
+void Texture::
+generate_normalization_cube_map(int size) {
+  setup_cube_map(size, T_unsigned_byte, F_rgb);
+  PTA_uchar image = modify_ram_image();
+
+  float half_size = (float)size * 0.5f;
+  float center = half_size - 0.5f;
+
+  LMatrix4f scale
+    (127.5f, 0.0f, 0.0f, 0.0f,
+     0.0f, 127.5f, 0.0f, 0.0f,
+     0.0f, 0.0f, 127.5f, 0.0f,
+     127.5f, 127.5f, 127.5f, 1.0f);
+
+  unsigned char *p = image;
+  int xi, yi;
+
+  // Page 0: positive X.
+  for (yi = 0; yi < size; ++yi) {
+    for (xi = 0; xi < size; ++xi) {
+      LVector3f vec(half_size, center - yi, center - xi);
+      vec.normalize();
+      vec = scale.xform_point(vec);
+
+      *p++ = (unsigned char)vec[0];
+      *p++ = (unsigned char)vec[1];
+      *p++ = (unsigned char)vec[2];
+    }
+  }
+
+  // Page 1: negative X.
+  for (yi = 0; yi < size; ++yi) {
+    for (xi = 0; xi < size; ++xi) {
+      LVector3f vec(-half_size, center - yi, xi - center);
+      vec.normalize();
+      vec = scale.xform_point(vec);
+      *p++ = (unsigned char)vec[0];
+      *p++ = (unsigned char)vec[1];
+      *p++ = (unsigned char)vec[2];
+    }
+  }
+
+  // Page 2: positive Y.
+  for (yi = 0; yi < size; ++yi) {
+    for (xi = 0; xi < size; ++xi) {
+      LVector3f vec(xi - center, half_size, yi - center);
+      vec.normalize();
+      vec = scale.xform_point(vec);
+      *p++ = (unsigned char)vec[0];
+      *p++ = (unsigned char)vec[1];
+      *p++ = (unsigned char)vec[2];
+    }
+  }
+
+  // Page 3: negative Y.
+  for (yi = 0; yi < size; ++yi) {
+    for (xi = 0; xi < size; ++xi) {
+      LVector3f vec(xi - center, -half_size, center - yi);
+      vec.normalize();
+      vec = scale.xform_point(vec);
+      *p++ = (unsigned char)vec[0];
+      *p++ = (unsigned char)vec[1];
+      *p++ = (unsigned char)vec[2];
+    }
+  }
+
+  // Page 4: positive Z.
+  for (yi = 0; yi < size; ++yi) {
+    for (xi = 0; xi < size; ++xi) {
+      LVector3f vec(xi - center, center - yi, half_size);
+      vec.normalize();
+      vec = scale.xform_point(vec);
+      *p++ = (unsigned char)vec[0];
+      *p++ = (unsigned char)vec[1];
+      *p++ = (unsigned char)vec[2];
+    }
+  }
+
+  // Page 5: negative Z.
+  for (yi = 0; yi < size; ++yi) {
+    for (xi = 0; xi < size; ++xi) {
+      LVector3f vec(center - xi, center - yi, -half_size);
+      vec.normalize();
+      vec = scale.xform_point(vec);
+      *p++ = (unsigned char)vec[0];
+      *p++ = (unsigned char)vec[1];
+      *p++ = (unsigned char)vec[2];
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::read
 //       Access: Published

+ 2 - 0
panda/src/gobj/texture.h

@@ -150,6 +150,8 @@ PUBLISHED:
   INLINE void setup_cube_map(int size,
                              ComponentType component_type, Format format);
 
+  void generate_normalization_cube_map(int size);
+
   bool read(const Filename &fullpath, int z = 0,
             int primary_file_num_channels = 0);
   bool read(const Filename &fullpath, const Filename &alpha_fullpath, 

+ 31 - 0
panda/src/pgraph/clipPlaneAttrib.cxx

@@ -23,6 +23,7 @@
 #include "bamWriter.h"
 #include "datagram.h"
 #include "datagramIterator.h"
+#include "config_pgraph.h"
 
 CPT(RenderAttrib) ClipPlaneAttrib::_empty_attrib;
 CPT(RenderAttrib) ClipPlaneAttrib::_all_off_attrib;
@@ -39,6 +40,9 @@ TypeHandle ClipPlaneAttrib::_type_handle;
 ////////////////////////////////////////////////////////////////////
 CPT(RenderAttrib) ClipPlaneAttrib::
 make(ClipPlaneAttrib::Operation op, PlaneNode *plane) {
+  pgraph_cat.warning()
+    << "Using deprecated ClipPlaneAttrib interface.\n";
+
   CPT(RenderAttrib) attrib;
 
   switch (op) {
@@ -73,6 +77,9 @@ make(ClipPlaneAttrib::Operation op, PlaneNode *plane) {
 ////////////////////////////////////////////////////////////////////
 CPT(RenderAttrib) ClipPlaneAttrib::
 make(ClipPlaneAttrib::Operation op, PlaneNode *plane1, PlaneNode *plane2) {
+  pgraph_cat.warning()
+    << "Using deprecated ClipPlaneAttrib interface.\n";
+
   CPT(RenderAttrib) attrib;
 
   switch (op) {
@@ -111,6 +118,9 @@ make(ClipPlaneAttrib::Operation op, PlaneNode *plane1, PlaneNode *plane2) {
 CPT(RenderAttrib) ClipPlaneAttrib::
 make(ClipPlaneAttrib::Operation op, PlaneNode *plane1, PlaneNode *plane2,
      PlaneNode *plane3) {
+  pgraph_cat.warning()
+    << "Using deprecated ClipPlaneAttrib interface.\n";
+
   CPT(RenderAttrib) attrib;
 
   switch (op) {
@@ -152,6 +162,9 @@ make(ClipPlaneAttrib::Operation op, PlaneNode *plane1, PlaneNode *plane2,
 CPT(RenderAttrib) ClipPlaneAttrib::
 make(ClipPlaneAttrib::Operation op, PlaneNode *plane1, PlaneNode *plane2,
      PlaneNode *plane3, PlaneNode *plane4) {
+  pgraph_cat.warning()
+    << "Using deprecated ClipPlaneAttrib interface.\n";
+
   CPT(RenderAttrib) attrib;
 
   switch (op) {
@@ -202,6 +215,9 @@ make(ClipPlaneAttrib::Operation op, PlaneNode *plane1, PlaneNode *plane2,
 ////////////////////////////////////////////////////////////////////
 ClipPlaneAttrib::Operation ClipPlaneAttrib::
 get_operation() const {
+  pgraph_cat.warning()
+    << "Using deprecated ClipPlaneAttrib interface.\n";
+
   if (has_all_off()) {
     return O_set;
 
@@ -225,6 +241,9 @@ get_operation() const {
 ////////////////////////////////////////////////////////////////////
 int ClipPlaneAttrib::
 get_num_planes() const {
+  pgraph_cat.warning()
+    << "Using deprecated ClipPlaneAttrib interface.\n";
+
   if (get_num_off_planes() == 0) {
     return get_num_on_planes();
   } else {
@@ -244,6 +263,9 @@ get_num_planes() const {
 ////////////////////////////////////////////////////////////////////
 PlaneNode *ClipPlaneAttrib::
 get_plane(int n) const {
+  pgraph_cat.warning()
+    << "Using deprecated ClipPlaneAttrib interface.\n";
+
   if (get_num_off_planes() == 0) {
     return DCAST(PlaneNode, get_on_plane(n).node());
   } else {
@@ -264,6 +286,9 @@ get_plane(int n) const {
 ////////////////////////////////////////////////////////////////////
 bool ClipPlaneAttrib::
 has_plane(PlaneNode *plane) const {
+  pgraph_cat.warning()
+    << "Using deprecated ClipPlaneAttrib interface.\n";
+
   if (get_num_off_planes() == 0) {
     return has_on_plane(NodePath(plane));
   } else {
@@ -282,6 +307,9 @@ has_plane(PlaneNode *plane) const {
 ////////////////////////////////////////////////////////////////////
 CPT(RenderAttrib) ClipPlaneAttrib::
 add_plane(PlaneNode *plane) const {
+  pgraph_cat.warning()
+    << "Using deprecated ClipPlaneAttrib interface.\n";
+
   if (get_num_off_planes() == 0) {
     return add_on_plane(NodePath(plane));
   } else {
@@ -301,6 +329,9 @@ add_plane(PlaneNode *plane) const {
 ////////////////////////////////////////////////////////////////////
 CPT(RenderAttrib) ClipPlaneAttrib::
 remove_plane(PlaneNode *plane) const {
+  pgraph_cat.warning()
+    << "Using deprecated ClipPlaneAttrib interface.\n";
+
   if (get_num_off_planes() == 0) {
     return remove_on_plane(NodePath(plane));
   } else {

+ 96 - 2
panda/src/pgraph/cullableObject.cxx

@@ -18,6 +18,7 @@
 
 #include "cullableObject.h"
 #include "textureAttrib.h"
+#include "texGenAttrib.h"
 #include "renderState.h"
 #include "clockObject.h"
 #include "cullTraverser.h"
@@ -28,8 +29,10 @@
 #include "geomVertexWriter.h"
 #include "geomVertexReader.h"
 #include "geomTriangles.h"
+#include "light.h"
 
 PStatCollector CullableObject::_munge_points_pcollector("*:Munge:Points");
+PStatCollector CullableObject::_munge_light_vector_pcollector("*:Munge:Light Vector");
 
 CullableObject *CullableObject::_deleted_chain = (CullableObject *)NULL;
 int CullableObject::_num_ever_allocated = 0;
@@ -75,6 +78,9 @@ munge_geom(GraphicsStateGuardianBase *gsg,
       }
       munge_points_to_quads(traverser);
     }
+    if (unsupported_bits & Geom::GR_texcoord_light_vector) {
+      munge_texcoord_light_vector(traverser);
+    }
 
     // Now invoke the munger to ensure the resulting geometry is in
     // a GSG-friendly form.
@@ -369,8 +375,8 @@ munge_points_to_quads(const CullTraverser *traverser) {
       LPoint2f c0(scale_x, scale_y);
       LPoint2f c1(-scale_x, scale_y);
 
-      if (has_rotate) {
-        // If we have a rotate factor, apply it to those two corners.
+      if (has_rotate) { 
+       // If we have a rotate factor, apply it to those two corners.
         rotate.set_row(*vi);
         float r = rotate.get_data1f();
         LMatrix3f mat = LMatrix3f::rotate_mat(r);
@@ -439,3 +445,91 @@ munge_points_to_quads(const CullTraverser *traverser) {
   _geom = new_geom.p();
   _munged_data = new_data;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullableObject::munge_texcoord_light_vector
+//       Access: Private
+//  Description: Generates the vector from each vertex to the
+//               indicated light as a 3-d texture coordinate.
+//
+//               This may replace _geom, _munged_data, and _state.
+////////////////////////////////////////////////////////////////////
+void CullableObject::
+munge_texcoord_light_vector(const CullTraverser *traverser) {
+  PStatTimer timer(_munge_light_vector_pcollector);
+
+  if (_modelview_transform->is_singular()) {
+    // If we're under a singular transform, never mind.
+    return;
+  }
+
+  CPT(TransformState) net_transform =
+    traverser->get_camera_transform()->compose(_modelview_transform);
+
+  if (!_munged_data->has_column(InternalName::get_vertex()) || 
+      !_munged_data->has_column(InternalName::get_normal())) {
+    // No vertex or normal; can't compute light vector.
+    return;
+  }
+
+  CPT(TexGenAttrib) tex_gen = _state->get_tex_gen();
+  nassertv(tex_gen != (TexGenAttrib *)NULL);
+
+  const TexGenAttrib::LightVectors &light_vectors = tex_gen->get_light_vectors();
+  TexGenAttrib::LightVectors::const_iterator lvi;
+  for (lvi = light_vectors.begin();
+       lvi != light_vectors.end();
+       ++lvi) {
+    TextureStage *stage = (*lvi).first;
+    const NodePath &light = (*lvi).second;
+    nassertv(!light.is_empty());
+    Light *light_obj = light.node()->as_light();
+    nassertv(light_obj != (Light *)NULL);
+
+    // Determine the names of the tangent and binormal columns
+    // associated with the stage's texcoord name.
+    CPT(InternalName) texcoord_name = stage->get_texcoord_name();
+    string basename;
+    if (texcoord_name != InternalName::get_texcoord()) {
+      basename = texcoord_name->get_basename();
+    }
+
+    CPT(InternalName) tangent_name = InternalName::get_tangent_name(basename);
+    CPT(InternalName) binormal_name = InternalName::get_binormal_name(basename);
+
+    if (_munged_data->has_column(tangent_name) &&
+        _munged_data->has_column(binormal_name)) {
+      // Create a new column for the new texcoords.
+      PT(GeomVertexData) new_data = _munged_data->replace_column
+        (texcoord_name, 3, Geom::NT_float32, Geom::C_texcoord);
+      _munged_data = new_data;
+
+      // Remove this TexGen stage from the state, since we're handling
+      // it now.
+      _state = _state->add_attrib(tex_gen->remove_stage(stage));
+      
+      // Get the transform from the light to the object.
+      CPT(TransformState) light_transform =
+        net_transform->invert_compose(light.get_net_transform());
+      const LMatrix4f &light_mat = light_transform->get_mat();
+
+      GeomVertexWriter texcoord(new_data, texcoord_name);
+      GeomVertexReader vertex(new_data, InternalName::get_vertex());
+      GeomVertexReader tangent(new_data, tangent_name);
+      GeomVertexReader binormal(new_data, binormal_name);
+      GeomVertexReader normal(new_data, InternalName::get_normal());
+      
+      while (!vertex.is_at_end()) {
+        LPoint3f p = vertex.get_data3f();
+        LVector3f t = tangent.get_data3f();
+        LVector3f b = binormal.get_data3f();
+        LVector3f n = normal.get_data3f();
+
+        LVector3f lv;
+        if (light_obj->get_vector_to_light(lv, p, light_mat)) {
+          texcoord.add_data3f(lv.dot(t), lv.dot(b), lv.dot(n));
+        }
+      }
+    }
+  }
+}

+ 2 - 0
panda/src/pgraph/cullableObject.h

@@ -85,6 +85,7 @@ public:
 
 private:
   void munge_points_to_quads(const CullTraverser *traverser);
+  void munge_texcoord_light_vector(const CullTraverser *traverser);
 
 private:
   // This class is used internally by munge_points_to_quads().
@@ -105,6 +106,7 @@ private:
   static int _num_ever_allocated;
 
   static PStatCollector _munge_points_pcollector;
+  static PStatCollector _munge_light_vector_pcollector;
 
 public:
   static TypeHandle get_class_type() {

+ 26 - 0
panda/src/pgraph/directionalLight.cxx

@@ -133,6 +133,32 @@ write(ostream &out, int indent_level) const {
     << "direction " << get_direction() << "\n";
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DirectionalLight::get_vector_to_light
+//       Access: Public, Virtual
+//  Description: Computes the vector from a particular vertex to this
+//               light.  The exact vector depends on the type of light
+//               (e.g. point lights return a different result than
+//               directional lights).
+//
+//               The input parameters are the vertex position in
+//               question, expressed in object space, and the matrix
+//               which converts from light space to object space.  The
+//               result is expressed in object space.
+//
+//               The return value is true if the result is successful,
+//               or false if it cannot be computed (e.g. for an
+//               ambient light).
+////////////////////////////////////////////////////////////////////
+bool DirectionalLight::
+get_vector_to_light(LVector3f &result, const LPoint3f &, 
+                    const LMatrix4f &to_object_space) {
+  CDReader cdata(_cycler);
+  result = cdata->_direction * to_object_space;
+
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: DirectionalLight::bind
 //       Access: Public, Virtual

+ 4 - 0
panda/src/pgraph/directionalLight.h

@@ -40,6 +40,10 @@ public:
   virtual void xform(const LMatrix4f &mat);
   virtual void write(ostream &out, int indent_level) const;
 
+  virtual bool get_vector_to_light(LVector3f &result,
+                                   const LPoint3f &from_object_point, 
+                                   const LMatrix4f &to_object_space);
+
 PUBLISHED:
   INLINE const Colorf &get_specular_color() const;
   INLINE void set_specular_color(const Colorf &color);

+ 22 - 0
panda/src/pgraph/light.cxx

@@ -67,6 +67,28 @@ Light::
 ~Light() {
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Light::get_vector_to_light
+//       Access: Public, Virtual
+//  Description: Computes the vector from a particular vertex to this
+//               light.  The exact vector depends on the type of light
+//               (e.g. point lights return a different result than
+//               directional lights).
+//
+//               The input parameters are the vertex position in
+//               question, expressed in object space, and the matrix
+//               which converts from light space to object space.  The
+//               result is expressed in object space.
+//
+//               The return value is true if the result is successful,
+//               or false if it cannot be computed (e.g. for an
+//               ambient light).
+////////////////////////////////////////////////////////////////////
+bool Light::
+get_vector_to_light(LVector3f &, const LPoint3f &, const LMatrix4f &) {
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Light::get_viz
 //       Access: Public

+ 4 - 0
panda/src/pgraph/light.h

@@ -62,6 +62,10 @@ public:
   virtual void bind(GraphicsStateGuardianBase *gsg, const NodePath &light,
                     int light_id)=0;
 
+  virtual bool get_vector_to_light(LVector3f &result,
+                                   const LPoint3f &from_object_point, 
+                                   const LMatrix4f &to_object_space);
+
   GeomNode *get_viz();
 
 protected:

+ 32 - 1
panda/src/pgraph/lightAttrib.cxx

@@ -24,6 +24,7 @@
 #include "bamWriter.h"
 #include "datagram.h"
 #include "datagramIterator.h"
+#include "config_pgraph.h"
 
 CPT(RenderAttrib) LightAttrib::_empty_attrib;
 CPT(RenderAttrib) LightAttrib::_all_off_attrib;
@@ -40,6 +41,9 @@ TypeHandle LightAttrib::_type_handle;
 ////////////////////////////////////////////////////////////////////
 CPT(RenderAttrib) LightAttrib::
 make(LightAttrib::Operation op, Light *light) {
+  pgraph_cat.warning()
+    << "Using deprecated LightAttrib interface.\n";
+
   CPT(RenderAttrib) attrib;
 
   switch (op) {
@@ -74,6 +78,9 @@ make(LightAttrib::Operation op, Light *light) {
 ////////////////////////////////////////////////////////////////////
 CPT(RenderAttrib) LightAttrib::
 make(LightAttrib::Operation op, Light *light1, Light *light2) {
+  pgraph_cat.warning()
+    << "Using deprecated LightAttrib interface.\n";
+
   CPT(RenderAttrib) attrib;
 
   switch (op) {
@@ -111,7 +118,10 @@ make(LightAttrib::Operation op, Light *light1, Light *light2) {
 ////////////////////////////////////////////////////////////////////
 CPT(RenderAttrib) LightAttrib::
 make(LightAttrib::Operation op, Light *light1, Light *light2,
-     Light *light3) {
+     Light *light3) { 
+  pgraph_cat.warning()
+    << "Using deprecated LightAttrib interface.\n";
+
   CPT(RenderAttrib) attrib;
 
   switch (op) {
@@ -153,6 +163,9 @@ make(LightAttrib::Operation op, Light *light1, Light *light2,
 CPT(RenderAttrib) LightAttrib::
 make(LightAttrib::Operation op, Light *light1, Light *light2,
      Light *light3, Light *light4) {
+  pgraph_cat.warning()
+    << "Using deprecated LightAttrib interface.\n";
+
   CPT(RenderAttrib) attrib;
 
   switch (op) {
@@ -203,6 +216,9 @@ make(LightAttrib::Operation op, Light *light1, Light *light2,
 ////////////////////////////////////////////////////////////////////
 LightAttrib::Operation LightAttrib::
 get_operation() const {
+  pgraph_cat.warning()
+    << "Using deprecated LightAttrib interface.\n";
+
   if (has_all_off()) {
     return O_set;
 
@@ -226,6 +242,9 @@ get_operation() const {
 ////////////////////////////////////////////////////////////////////
 int LightAttrib::
 get_num_lights() const {
+  pgraph_cat.warning()
+    << "Using deprecated LightAttrib interface.\n";
+
   if (get_num_off_lights() == 0) {
     return get_num_on_lights();
   } else {
@@ -245,6 +264,9 @@ get_num_lights() const {
 ////////////////////////////////////////////////////////////////////
 Light *LightAttrib::
 get_light(int n) const {
+  pgraph_cat.warning()
+    << "Using deprecated LightAttrib interface.\n";
+
   if (get_num_off_lights() == 0) {
     return get_on_light(n).node()->as_light();
   } else {
@@ -265,6 +287,9 @@ get_light(int n) const {
 ////////////////////////////////////////////////////////////////////
 bool LightAttrib::
 has_light(Light *light) const {
+  pgraph_cat.warning()
+    << "Using deprecated LightAttrib interface.\n";
+
   if (get_num_off_lights() == 0) {
     return has_on_light(NodePath(light->as_node()));
   } else {
@@ -283,6 +308,9 @@ has_light(Light *light) const {
 ////////////////////////////////////////////////////////////////////
 CPT(RenderAttrib) LightAttrib::
 add_light(Light *light) const {
+  pgraph_cat.warning()
+    << "Using deprecated LightAttrib interface.\n";
+
   if (get_num_off_lights() == 0) {
     return add_on_light(NodePath(light->as_node()));
   } else {
@@ -302,6 +330,9 @@ add_light(Light *light) const {
 ////////////////////////////////////////////////////////////////////
 CPT(RenderAttrib) LightAttrib::
 remove_light(Light *light) const {
+  pgraph_cat.warning()
+    << "Using deprecated LightAttrib interface.\n";
+
   if (get_num_off_lights() == 0) {
     return remove_on_light(NodePath(light->as_node()));
   } else {

+ 55 - 2
panda/src/pgraph/nodePath.cxx

@@ -26,6 +26,7 @@
 #include "cullBinAttrib.h"
 #include "textureAttrib.h"
 #include "texMatrixAttrib.h"
+#include "texGenAttrib.h"
 #include "materialAttrib.h"
 #include "lightAttrib.h"
 #include "clipPlaneAttrib.h"
@@ -3226,7 +3227,7 @@ get_tex_transform(const NodePath &other, TextureStage *stage) const {
 //               the indicated texture stage.
 ////////////////////////////////////////////////////////////////////
 void NodePath::
-set_tex_gen(TextureStage *stage, TexGenAttrib::Mode mode, int priority) {
+set_tex_gen(TextureStage *stage, RenderAttrib::TexGenMode mode, int priority) {
   nassertv_always(!is_empty());
 
   const RenderAttrib *attrib =
@@ -3246,6 +3247,36 @@ set_tex_gen(TextureStage *stage, TexGenAttrib::Mode mode, int priority) {
   node()->set_attrib(tga->add_stage(stage, mode), priority);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: NodePath::set_tex_gen
+//       Access: Published
+//  Description: Enables automatic texture coordinate generation for
+//               the indicated texture stage.  This version of this
+//               method is useful when setting M_light_vector, which
+//               requires a specific light.
+////////////////////////////////////////////////////////////////////
+void NodePath::
+set_tex_gen(TextureStage *stage, RenderAttrib::TexGenMode mode, 
+            const NodePath &light, int priority) {
+  nassertv_always(!is_empty());
+
+  const RenderAttrib *attrib =
+    node()->get_attrib(TexGenAttrib::get_class_type());
+
+  CPT(TexGenAttrib) tga;
+
+  if (attrib != (const RenderAttrib *)NULL) {
+    priority = max(priority,
+                   node()->get_state()->get_override(TextureAttrib::get_class_type()));
+    tga = DCAST(TexGenAttrib, attrib);
+
+  } else {
+    tga = DCAST(TexGenAttrib, TexGenAttrib::make());
+  }
+
+  node()->set_attrib(tga->add_stage(stage, mode, light), priority);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: NodePath::clear_tex_gen
 //       Access: Published
@@ -3311,7 +3342,7 @@ has_tex_gen(TextureStage *stage) const {
 //               the given stage, or M_off if there is no explicit
 //               mode set for the given stage.
 ////////////////////////////////////////////////////////////////////
-TexGenAttrib::Mode NodePath::
+RenderAttrib::TexGenMode NodePath::
 get_tex_gen(TextureStage *stage) const {
   nassertr_always(!is_empty(), TexGenAttrib::M_off);
 
@@ -3325,6 +3356,28 @@ get_tex_gen(TextureStage *stage) const {
   return TexGenAttrib::M_off;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: NodePath::get_tex_gen_light
+//       Access: Published
+//  Description: Returns the particular Light set for the indicated
+//               texgen mode's texture stage, or empty NodePath if no
+//               light is set.  This is only meaningful if the texgen
+//               mode (returned by get_tex_gen()) is M_light_vector.
+////////////////////////////////////////////////////////////////////
+NodePath NodePath::
+get_tex_gen_light(TextureStage *stage) const {
+  nassertr_always(!is_empty(), NodePath::fail());
+
+  const RenderAttrib *attrib =
+    node()->get_attrib(TexGenAttrib::get_class_type());
+  if (attrib != (const RenderAttrib *)NULL) {
+    const TexGenAttrib *tga = DCAST(TexGenAttrib, attrib);
+    return tga->get_light(stage);
+  }
+
+  return NodePath();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: NodePath::set_tex_projector
 //       Access: Published

+ 4 - 3
panda/src/pgraph/nodePath.h

@@ -24,7 +24,6 @@
 #include "pandaNode.h"
 #include "renderState.h"
 #include "transformState.h"
-#include "texGenAttrib.h"
 #include "renderModeAttrib.h"
 #include "transparencyAttrib.h"
 #include "nodePathComponent.h"
@@ -581,11 +580,13 @@ PUBLISHED:
   INLINE float get_tex_rotate(const NodePath &other, TextureStage *stage) const;
   INLINE LVecBase2f get_tex_scale(const NodePath &other, TextureStage *stage) const;
 
-  void set_tex_gen(TextureStage *stage, TexGenAttrib::Mode mode, int priority = 0);
+  void set_tex_gen(TextureStage *stage, RenderAttrib::TexGenMode mode, int priority = 0);
+  void set_tex_gen(TextureStage *stage, RenderAttrib::TexGenMode mode, const NodePath &light, int priority = 0);
   void clear_tex_gen();
   void clear_tex_gen(TextureStage *stage);
   bool has_tex_gen(TextureStage *stage) const;
-  TexGenAttrib::Mode get_tex_gen(TextureStage *stage) const;
+  RenderAttrib::TexGenMode get_tex_gen(TextureStage *stage) const;
+  NodePath get_tex_gen_light(TextureStage *stage) const;
 
   void set_tex_projector(TextureStage *stage, const NodePath &from, const NodePath &to);
   void clear_tex_projector(TextureStage *stage);

+ 27 - 0
panda/src/pgraph/pointLight.cxx

@@ -132,6 +132,33 @@ write(ostream &out, int indent_level) const {
     << "attenuation " << get_attenuation() << "\n";
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PointLight::get_vector_to_light
+//       Access: Public, Virtual
+//  Description: Computes the vector from a particular vertex to this
+//               light.  The exact vector depends on the type of light
+//               (e.g. point lights return a different result than
+//               directional lights).
+//
+//               The input parameters are the vertex position in
+//               question, expressed in object space, and the matrix
+//               which converts from light space to object space.  The
+//               result is expressed in object space.
+//
+//               The return value is true if the result is successful,
+//               or false if it cannot be computed (e.g. for an
+//               ambient light).
+////////////////////////////////////////////////////////////////////
+bool PointLight::
+get_vector_to_light(LVector3f &result, const LPoint3f &from_object_point, 
+                    const LMatrix4f &to_object_space) {
+  CDReader cdata(_cycler);
+  LPoint3f point = cdata->_point * to_object_space;
+
+  result = point - from_object_point;
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PointLight::bind
 //       Access: Public, Virtual

+ 4 - 0
panda/src/pgraph/pointLight.h

@@ -40,6 +40,10 @@ public:
   virtual void xform(const LMatrix4f &mat);
   virtual void write(ostream &out, int indent_level) const;
 
+  virtual bool get_vector_to_light(LVector3f &result,
+                                   const LPoint3f &from_object_point, 
+                                   const LMatrix4f &to_object_space);
+
 PUBLISHED:
   INLINE const Colorf &get_specular_color() const;
   INLINE void set_specular_color(const Colorf &color);

+ 68 - 0
panda/src/pgraph/renderAttrib.h

@@ -92,6 +92,74 @@ PUBLISHED:
     M_always            // Always draw.  
   };
 
+  // This is the enumerated type for TexGenAttrib.  It is inherited
+  // into TexGenAttrib.  It is defined up at this level only to avoid
+  // circular dependencies in the header files.
+  enum TexGenMode {
+    M_off,
+
+    // In the types below, "eye" means the coordinate space of the
+    // observing camera, "object" means the local coordinate space of
+    // the object, and "world" means world coordinates, e.g. the
+    // coordinate space of the root of the graph.
+
+    // Sphere maps are classic static reflection maps.  They are
+    // supported on just about any hardware, and require a precomputed
+    // 180-degree fisheye image.  Sphere maps only make sense in eye
+    // coordinate space.
+    M_eye_sphere_map,
+
+    // Cube maps are a modern improvement on the sphere map; they
+    // don't suffer from any polar singularities, but they require six
+    // texture images.  They can also be generated dynamically for
+    // real-time reflections (see GraphicsOutput::make_cube_map()).
+    // Typically, a statically-generated cube map will be in eye
+    // space, while a dynamically-generated map will be in world space
+    // or object space (depending on where the camera rig that
+    // generates the map is parented).
+
+    // Cube mapping is not supported on all hardware.
+    M_world_cube_map,
+    M_eye_cube_map,
+
+    // Normal maps are most useful for applying diffuse lighting
+    // effects via a pregenerated cube map.
+    M_world_normal,
+    M_eye_normal,
+
+    // Position maps convert XYZ coordinates directly to texture
+    // coordinates.  This is particularly useful for implementing
+    // projective texturing (see NodePath::project_texture()).
+    M_world_position,
+    M_object_position,
+    M_eye_position,
+
+    // With M_point_sprite, texture coordinates will be generated for
+    // large points in the range (0,0) - (1,1) from upper-left to
+    // lower-right across the point's face.  Without this, each point
+    // will have just a single uniform texture coordinate value across
+    // its face.
+
+    // Unfortunately, the generated texture coordinates are inverted
+    // (upside-down) from Panda's usual convention, but this is what
+    // the graphics card manufacturers decided to use.  You could use
+    // a texture matrix to re-invert the texture, but that will
+    // probably force software rendering.  You'll have to paint your
+    // textures upside-down if you want true hardware sprites.
+    M_point_sprite,
+
+    // M_light_vector generates special 3-d texture coordinates that
+    // represent the vector to a particular Light in the scene graph,
+    // expressed in each vertex's tangent space.  This is used to
+    // implement bumpmapping.
+
+    // This requires a Light to be specified to the TexGenAttrib.  It
+    // also requires each vertex to define a normal, as well as a
+    // tangent and binormal for the particular named texture
+    // coordinate set.
+    M_light_vector,
+  };
+
 protected:
   static CPT(RenderAttrib) return_new(RenderAttrib *attrib);
   virtual int compare_to_impl(const RenderAttrib *other) const;

+ 0 - 23
panda/src/pgraph/renderState.I

@@ -336,29 +336,6 @@ get_render_mode() const {
   return _render_mode;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: RenderState::get_geom_rendering
-//       Access: Published
-//  Description: Returns the union of the Geom::GeomRendering bits
-//               that will be required once this RenderState is
-//               applied to a geom which includes the indicated
-//               geom_rendering bits.
-////////////////////////////////////////////////////////////////////
-INLINE int RenderState::
-get_geom_rendering(int geom_rendering) const {
-  if (get_render_mode() != (const RenderModeAttrib *)NULL) {
-    geom_rendering = _render_mode->get_geom_rendering(geom_rendering);
-  }
-  if (get_tex_gen() != (const TexGenAttrib *)NULL) {
-    geom_rendering = _tex_gen->get_geom_rendering(geom_rendering);
-  }
-  if (get_tex_matrix() != (const TexMatrixAttrib *)NULL) {
-    geom_rendering = _tex_matrix->get_geom_rendering(geom_rendering);
-  }
-
-  return geom_rendering;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: RenderState::set_destructing
 //       Access: Private

+ 23 - 0
panda/src/pgraph/renderState.cxx

@@ -930,6 +930,29 @@ validate_states() {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: RenderState::get_geom_rendering
+//       Access: Published
+//  Description: Returns the union of the Geom::GeomRendering bits
+//               that will be required once this RenderState is
+//               applied to a geom which includes the indicated
+//               geom_rendering bits.
+////////////////////////////////////////////////////////////////////
+int RenderState::
+get_geom_rendering(int geom_rendering) const {
+  if (get_render_mode() != (const RenderModeAttrib *)NULL) {
+    geom_rendering = _render_mode->get_geom_rendering(geom_rendering);
+  }
+  if (get_tex_gen() != (const TexGenAttrib *)NULL) {
+    geom_rendering = _tex_gen->get_geom_rendering(geom_rendering);
+  }
+  if (get_tex_matrix() != (const TexMatrixAttrib *)NULL) {
+    geom_rendering = _tex_matrix->get_geom_rendering(geom_rendering);
+  }
+
+  return geom_rendering;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: RenderState::issue_delta_modify
 //       Access: Public

+ 2 - 2
panda/src/pgraph/renderState.h

@@ -28,7 +28,6 @@
 #include "updateSeq.h"
 #include "pStatCollector.h"
 #include "renderModeAttrib.h"
-#include "texGenAttrib.h"
 #include "texMatrixAttrib.h"
 #include "geomMunger.h"
 #include "weakPointerTo.h"
@@ -40,6 +39,7 @@ class TransparencyAttrib;
 class ColorAttrib;
 class ColorScaleAttrib;
 class TextureAttrib;
+class TexGenAttrib;
 class FactoryParams;
 
 ////////////////////////////////////////////////////////////////////
@@ -134,7 +134,7 @@ PUBLISHED:
   INLINE const TexMatrixAttrib *get_tex_matrix() const;
   INLINE const RenderModeAttrib *get_render_mode() const;
 
-  INLINE int get_geom_rendering(int geom_rendering) const;
+  int get_geom_rendering(int geom_rendering) const;
 
 public:
   CPT(RenderState) issue_delta_modify(const RenderState *other, 

+ 23 - 0
panda/src/pgraph/spotlight.cxx

@@ -141,6 +141,29 @@ write(ostream &out, int indent_level) const {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Spotlight::get_vector_to_light
+//       Access: Public, Virtual
+//  Description: Computes the vector from a particular vertex to this
+//               light.  The exact vector depends on the type of light
+//               (e.g. point lights return a different result than
+//               directional lights).
+//
+//               The input parameters are the vertex position in
+//               question, expressed in object space, and the matrix
+//               which converts from light space to object space.  The
+//               result is expressed in object space.
+//
+//               The return value is true if the result is successful,
+//               or false if it cannot be computed (e.g. for an
+//               ambient light).
+////////////////////////////////////////////////////////////////////
+bool Spotlight::
+get_vector_to_light(LVector3f &result, const LPoint3f &from_object_point, 
+                    const LMatrix4f &to_object_space) {
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Spotlight::make_spot
 //       Access: Published, Static

+ 4 - 0
panda/src/pgraph/spotlight.h

@@ -51,6 +51,10 @@ public:
   virtual void xform(const LMatrix4f &mat);
   virtual void write(ostream &out, int indent_level) const;
 
+  virtual bool get_vector_to_light(LVector3f &result,
+                                   const LPoint3f &from_object_point, 
+                                   const LMatrix4f &to_object_space);
+
 PUBLISHED:
   INLINE float get_exponent() const;
   INLINE void set_exponent(float exponent);

+ 47 - 6
panda/src/pgraph/texGenAttrib.I

@@ -25,7 +25,10 @@
 ////////////////////////////////////////////////////////////////////
 INLINE TexGenAttrib::
 TexGenAttrib() :
-  _num_point_sprites(0)
+  _num_point_sprites(0),
+  _num_light_vectors(0),
+  _point_geom_rendering(0),
+  _geom_rendering(0)
 {
 }
 
@@ -39,7 +42,10 @@ INLINE TexGenAttrib::
 TexGenAttrib(const TexGenAttrib &copy) :
   _stages(copy._stages),
   _no_texcoords(copy._no_texcoords),
-  _num_point_sprites(copy._num_point_sprites)
+  _num_point_sprites(copy._num_point_sprites),
+  _num_light_vectors(copy._num_light_vectors),
+  _point_geom_rendering(copy._point_geom_rendering),
+  _geom_rendering(copy._geom_rendering)
 {
 }
 
@@ -54,12 +60,10 @@ TexGenAttrib(const TexGenAttrib &copy) :
 INLINE int TexGenAttrib::
 get_geom_rendering(int geom_rendering) const {
   if ((geom_rendering & Geom::GR_point) != 0) {
-    if (_num_point_sprites > 0) {
-      geom_rendering |= Geom::GR_point_sprite;
-    }
+    geom_rendering |= _point_geom_rendering;
   }
 
-  return geom_rendering;
+  return geom_rendering | _geom_rendering;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -75,3 +79,40 @@ INLINE const Geom::NoTexCoordStages &TexGenAttrib::
 get_no_texcoords() const {
   return _no_texcoords;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexGenAttrib::get_light_vectors
+//       Access: Public
+//  Description: Returns the set of TextureStages that have
+//               M_light_vector in effect, as well as the associated
+//               Lights.
+////////////////////////////////////////////////////////////////////
+INLINE const TexGenAttrib::LightVectors &TexGenAttrib::
+get_light_vectors() const {
+  return _light_vectors;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexGenAttrib::ModeDef::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE TexGenAttrib::ModeDef::
+ModeDef() :
+  _mode(M_off)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexGenAttrib::ModeDef::compare_to
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE int TexGenAttrib::ModeDef::
+compare_to(const TexGenAttrib::ModeDef &other) const {
+  if (_mode != other._mode) {
+    return (int)_mode < (int)other._mode ? -1 : 1;
+  }
+  return _light.compare_to(other._light);
+}
+

+ 125 - 22
panda/src/pgraph/texGenAttrib.cxx

@@ -61,8 +61,8 @@ make() {
 //               indicated stage.
 ////////////////////////////////////////////////////////////////////
 CPT(RenderAttrib) TexGenAttrib::
-make(TextureStage *stage, TexGenAttrib::Mode mode) {
-  return DCAST(TexGenAttrib, make())->add_stage(stage, mode);
+make(TextureStage *stage, TexGenAttrib::Mode mode, const NodePath &light) {
+  return DCAST(TexGenAttrib, make())->add_stage(stage, mode, light);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -74,14 +74,44 @@ make(TextureStage *stage, TexGenAttrib::Mode mode) {
 //               replaced.
 ////////////////////////////////////////////////////////////////////
 CPT(RenderAttrib) TexGenAttrib::
-add_stage(TextureStage *stage, TexGenAttrib::Mode mode) const {
-  TexGenAttrib *attrib = new TexGenAttrib(*this);
-  attrib->_stages[stage] = mode;
-  if (mode != M_off) {
+add_stage(TextureStage *stage, TexGenAttrib::Mode mode, const NodePath &light) const {
+  CPT(RenderAttrib) removed = remove_stage(stage);
+  TexGenAttrib *attrib = new TexGenAttrib(*DCAST(TexGenAttrib, removed));
+
+  ModeDef &mode_def = attrib->_stages[stage];
+  mode_def._mode = mode;
+  mode_def._light = light;
+  switch (mode) {
+  case M_point_sprite:
     attrib->_no_texcoords.insert(stage);
-    if (mode == M_point_sprite) {
-      attrib->_num_point_sprites++;
+    attrib->_point_geom_rendering |= Geom::GR_point_sprite;
+    attrib->_num_point_sprites++;
+    break;
+
+  case M_light_vector:
+    {
+      Light *light_obj = NULL;
+      if (!light.is_empty()) {
+        light_obj = light.node()->as_light();
+      }
+      if (light_obj == (Light *)NULL) {
+        ostringstream strm;
+        strm << "Not a light: " << light;
+        nassert_raise(strm.str());
+
+      } else {
+        attrib->_light_vectors[stage] = light;
+        attrib->_geom_rendering |= Geom::GR_texcoord_light_vector;
+        attrib->_num_light_vectors++;
+      }
     }
+    break;
+
+  case M_off:
+    break;
+
+  default:
+    attrib->_no_texcoords.insert(stage);
   }
   return return_new(attrib);
 }
@@ -100,12 +130,21 @@ remove_stage(TextureStage *stage) const {
     return this;
   }
 
-  Mode mode = (*si).second;
+  Mode mode = (*si).second._mode;
   TexGenAttrib *attrib = new TexGenAttrib(*this);
   attrib->_stages.erase(stage);
   attrib->_no_texcoords.erase(stage);
   if (mode == M_point_sprite) {
     attrib->_num_point_sprites--;
+    if (attrib->_num_point_sprites == 0) {
+      attrib->_point_geom_rendering &= ~Geom::GR_point_sprite;
+    }
+  } else if (mode == M_light_vector) {
+    attrib->_light_vectors.erase(stage);
+    attrib->_num_light_vectors--;
+    if (attrib->_num_light_vectors == 0) {
+      attrib->_geom_rendering &= ~Geom::GR_texcoord_light_vector;
+    }
   }
   return return_new(attrib);
 }
@@ -145,11 +184,28 @@ TexGenAttrib::Mode TexGenAttrib::
 get_mode(TextureStage *stage) const {
   Stages::const_iterator mi = _stages.find(stage);
   if (mi != _stages.end()) {
-    return (*mi).second;
+    return (*mi).second._mode;
   }
   return M_off;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TexGenAttrib::get_light
+//       Access: Published
+//  Description: Returns the Light associated with the named texture
+//               stage, or the empty NodePath if no light is
+//               associated with the indicated stage.  This is only
+//               meaningful if the mode is M_light_vector.
+////////////////////////////////////////////////////////////////////
+NodePath TexGenAttrib::
+get_light(TextureStage *stage) const {
+  Stages::const_iterator mi = _stages.find(stage);
+  if (mi != _stages.end()) {
+    return (*mi).second._light;
+  }
+  return NodePath();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TexGenAttrib::issue
 //       Access: Public, Virtual
@@ -176,9 +232,9 @@ output(ostream &out) const {
   Stages::const_iterator mi;
   for (mi = _stages.begin(); mi != _stages.end(); ++mi) {
     TextureStage *stage = (*mi).first;
-    Mode mode = (*mi).second;
+    const ModeDef &mode_def = (*mi).second;
     out << " " << stage->get_name() << "(";
-    switch (mode) {
+    switch (mode_def._mode) {
     case M_off:
       out << "off";
       break;
@@ -214,6 +270,10 @@ output(ostream &out) const {
     case M_point_sprite:
       out << "point_sprite";
       break;
+
+    case M_light_vector:
+      out << "light_vector: " << mode_def._light;
+      break;
     }
     out << ")";
   }
@@ -253,8 +313,9 @@ compare_to_impl(const RenderAttrib *other) const {
 
     } else {
       // This stage is in both; compare the stages.
-      if ((*ai).second != (*bi).second) {
-        return (int)(*ai).second < (int)(*bi).second ? -1 : 1;
+      int compare = (*ai).second.compare_to((*bi).second);
+      if (compare != 0) {
+        return compare;
       }
       ++ai;
       ++bi;
@@ -338,8 +399,29 @@ compose_impl(const RenderAttrib *other) const {
   // Now copy from _stages to _no_texcoords.
   Stages::const_iterator ri;
   for (ri = attrib->_stages.begin(); ri != attrib->_stages.end(); ++ri) {
-    if ((*ri).second != M_off) {
-      attrib->_no_texcoords.insert((*ri).first);
+    TextureStage *stage = (*ri).first;
+    const ModeDef &mode_def = (*ri).second;
+    Mode mode = mode_def._mode;
+    const NodePath &light = mode_def._light;
+
+    switch (mode) {
+    case M_point_sprite:
+      attrib->_no_texcoords.insert(stage);
+      attrib->_point_geom_rendering |= Geom::GR_point_sprite;
+      attrib->_num_point_sprites++;
+      break;
+      
+    case M_light_vector:
+      attrib->_light_vectors[stage] = light;
+      attrib->_geom_rendering |= Geom::GR_texcoord_light_vector;
+      attrib->_num_light_vectors++;
+      break;
+      
+    case M_off:
+      break;
+      
+    default:
+      attrib->_no_texcoords.insert(stage);
     }
   }
 
@@ -371,7 +453,7 @@ invert_compose_impl(const RenderAttrib *other) const {
   while (ai != _stages.end() && bi != ta->_stages.end()) {
     if ((*ai).first < (*bi).first) {
       // This stage is in a but not in b.  Turn a off.
-      attrib->_stages.insert(attrib->_stages.end(), Stages::value_type((*ai).first, M_off));
+      attrib->_stages.insert(attrib->_stages.end(), Stages::value_type((*ai).first, ModeDef()));
       ++ai;
 
     } else if ((*bi).first < (*ai).first) {
@@ -389,7 +471,7 @@ invert_compose_impl(const RenderAttrib *other) const {
 
   while (ai != _stages.end()) {
     // This stage is in a but not in b.
-    attrib->_stages.insert(attrib->_stages.end(), Stages::value_type((*ai).first, M_off));
+    attrib->_stages.insert(attrib->_stages.end(), Stages::value_type((*ai).first, ModeDef()));
     ++ai;
   }
 
@@ -402,8 +484,29 @@ invert_compose_impl(const RenderAttrib *other) const {
   // Now copy from _stages to _no_texcoords.
   Stages::const_iterator ri;
   for (ri = attrib->_stages.begin(); ri != attrib->_stages.end(); ++ri) {
-    if ((*ri).second != M_off) {
-      attrib->_no_texcoords.insert((*ri).first);
+    TextureStage *stage = (*ri).first;
+    const ModeDef &mode_def = (*ri).second;
+    Mode mode = mode_def._mode;
+    const NodePath &light = mode_def._light;
+
+    switch (mode) {
+    case M_point_sprite:
+      attrib->_no_texcoords.insert(stage);
+      attrib->_point_geom_rendering |= Geom::GR_point_sprite;
+      attrib->_num_point_sprites++;
+      break;
+      
+    case M_light_vector:
+      attrib->_light_vectors[stage] = light;
+      attrib->_geom_rendering |= Geom::GR_texcoord_light_vector;
+      attrib->_num_light_vectors++;
+      break;
+      
+    case M_off:
+      break;
+      
+    default:
+      attrib->_no_texcoords.insert(stage);
     }
   }
 
@@ -452,7 +555,7 @@ write_datagram(BamWriter *manager, Datagram &dg) {
   Stages::const_iterator si;
   for (si = _stages.begin(); si != _stages.end(); ++si) {
     TextureStage *stage = (*si).first;
-    Mode mode = (*si).second;
+    Mode mode = (*si).second._mode;
 
     manager->write_pointer(dg, stage);
     dg.add_uint8((unsigned int)mode);
@@ -475,7 +578,7 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
     Mode mode = (*mi);
 
     TextureStage *stage = DCAST(TextureStage, p_list[pi++]);
-    _stages[stage] = mode;
+    _stages[stage]._mode = mode;
 
     if (mode != M_off) {
       _no_texcoords.insert(stage);

+ 34 - 58
panda/src/pgraph/texGenAttrib.h

@@ -26,6 +26,7 @@
 #include "textureStage.h"
 #include "texture.h"
 #include "pointerTo.h"
+#include "nodePath.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : TexGenAttrib
@@ -38,59 +39,11 @@
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA TexGenAttrib : public RenderAttrib {
 PUBLISHED:
-  enum Mode {
-    M_off,
-
-    // In the types below, "eye" means the coordinate space of the
-    // observing camera, "object" means the local coordinate space of
-    // the object, and "world" means world coordinates, e.g. the
-    // coordinate space of the root of the graph.
-
-    // Sphere maps are classic static reflection maps.  They are
-    // supported on just about any hardware, and require a precomputed
-    // 180-degree fisheye image.  Sphere maps only make sense in eye
-    // coordinate space.
-    M_eye_sphere_map,
-
-    // Cube maps are a modern improvement on the sphere map; they
-    // don't suffer from any polar singularities, but they require six
-    // texture images.  They can also be generated dynamically for
-    // real-time reflections (see GraphicsOutput::make_cube_map()).
-    // Typically, a statically-generated cube map will be in eye
-    // space, while a dynamically-generated map will be in world space
-    // or object space (depending on where the camera rig that
-    // generates the map is parented).
-
-    // Cube mapping is not supported on all hardware.
-    M_world_cube_map,
-    M_eye_cube_map,
-
-    // Normal maps are most useful for applying diffuse lighting
-    // effects via a pregenerated cube map.
-    M_world_normal,
-    M_eye_normal,
-
-    // Position maps convert XYZ coordinates directly to texture
-    // coordinates.  This is particularly useful for implementing
-    // projective texturing (see NodePath::project_texture()).
-    M_world_position,
-    M_object_position,
-    M_eye_position,
-
-    // With M_point_sprite, texture coordinates will be generated for
-    // large points in the range (0,0) - (1,1) from upper-left to
-    // lower-right across the point's face.  Without this, each point
-    // will have just a single uniform texture coordinate value across
-    // its face.
-
-    // Unfortunately, the generated texture coordinates are inverted
-    // (upside-down) from Panda's usual convention, but this is what
-    // the graphics card manufacturers decided to use.  You could use
-    // a texture matrix to re-invert the texture, but that will
-    // probably force software rendering.  You'll have to paint your
-    // textures upside-down if you want true hardware sprites.
-    M_point_sprite,
-  };
+  // We inherit the definition of our Mode enumerated type from
+  // RenderAttrib.  Normally, Mode would be defined here, but we
+  // define it in the base class instead as a hack to avoid a problem
+  // with circular includes.
+  typedef RenderAttrib::TexGenMode Mode;
 
 protected:
   INLINE TexGenAttrib();
@@ -101,20 +54,24 @@ public:
 
 PUBLISHED:
   static CPT(RenderAttrib) make();
-  static CPT(RenderAttrib) make(TextureStage *stage, Mode mode);
+  static CPT(RenderAttrib) make(TextureStage *stage, Mode mode, const NodePath &light = NodePath());
 
-  CPT(RenderAttrib) add_stage(TextureStage *stage, Mode mode) const;
+  CPT(RenderAttrib) add_stage(TextureStage *stage, Mode mode, const NodePath &light = NodePath()) const;
   CPT(RenderAttrib) remove_stage(TextureStage *stage) const;
 
   bool is_empty() const;
   bool has_stage(TextureStage *stage) const;
   Mode get_mode(TextureStage *stage) const;
+  NodePath get_light(TextureStage *stage) const;
 
   INLINE int get_geom_rendering(int geom_rendering) const;
 
 public:
   INLINE const Geom::NoTexCoordStages &get_no_texcoords() const;
 
+  typedef pmap<TextureStage *, NodePath> LightVectors;
+  INLINE const LightVectors &get_light_vectors() const;
+
   virtual void issue(GraphicsStateGuardianBase *gsg) const;
   virtual void output(ostream &out) const;
 
@@ -125,21 +82,40 @@ protected:
   virtual RenderAttrib *make_default_impl() const;
 
 private:
-  typedef pmap<PT(TextureStage), Mode> Stages;
+  class ModeDef {
+  public:
+    INLINE ModeDef();
+    INLINE int compare_to(const ModeDef &other) const;
+    Mode _mode;
+    NodePath _light;
+  };
+  typedef pmap<PT(TextureStage), ModeDef> Stages;
   Stages _stages;
 
   // This is a set of TextureStage pointers for which texture
   // coordinates will not be needed from the Geom.  It's redundant;
   // it's almost the same set that is listed in _stages, above.  It's
-  // just here as an optimization to pass to
-  // Geom::setup_multitexcoord_iterator() during rendering.
+  // just here as an optimization during rendering.
   Geom::NoTexCoordStages _no_texcoords;
 
+  // This is another optimization during rendering; it lists the
+  // texture stages (if any) that use M_light_vector, and their
+  // associated lights.
+  LightVectors _light_vectors;
+
   // This element is only used during reading from a bam file.  It has
   // no meaningful value any other time.
   pvector<Mode> _read_modes;
 
   int _num_point_sprites;
+  int _num_light_vectors;
+
+  // _point_geom_rendering is the GeomRendering bits that are added by
+  // the TexGenAttrib if there are any points in the Geom.
+  // _geom_rendering is the GeomRendering bits that are added
+  // regardless of the kind of Geom it is.
+  int _point_geom_rendering;
+  int _geom_rendering;
   
   static CPT(RenderAttrib) _empty_attrib;