David Rose 21 years ago
parent
commit
bacd4ad811
3 changed files with 590 additions and 194 deletions
  1. 97 17
      panda/src/parametrics/ropeNode.I
  2. 432 173
      panda/src/parametrics/ropeNode.cxx
  3. 61 4
      panda/src/parametrics/ropeNode.h

+ 97 - 17
panda/src/parametrics/ropeNode.I

@@ -29,8 +29,11 @@ CData() {
   _uv_mode = RopeNode::UV_none;
   _u_dominant = true;
   _uv_scale = 1.0f;
+  _normal_mode = RopeNode::NM_none;
+  _tube_up = LVector3f::up();
   _use_vertex_color = false;
   _num_subdiv = 10;
+  _num_slices = 5;
   _thickness = 1.0f;
 }
 
@@ -46,8 +49,11 @@ CData(const RopeNode::CData &copy) :
   _uv_mode(copy._uv_mode),
   _u_dominant(copy._u_dominant),
   _uv_scale(copy._uv_scale),
+  _normal_mode(copy._normal_mode),
+  _tube_up(copy._tube_up),
   _use_vertex_color(copy._use_vertex_color),
   _num_subdiv(copy._num_subdiv),
+  _num_slices(copy._num_slices),
   _thickness(copy._thickness)
 {
 }
@@ -149,23 +155,6 @@ get_uv_direction() const {
   return cdata->_u_dominant;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: set_uv_scale
-//       Access: Published
-//  Description: Specifies an additional scaling factor to apply to
-//               generated UV's for the rope.  This is a deprecated
-//               interface; use set_uv_scale() that accepts a single
-//               float, instead.
-////////////////////////////////////////////////////////////////////
-INLINE void RopeNode::
-set_uv_scale(const LVecBase2f &uv_scale) {
-  if (get_uv_direction()) {
-    set_uv_scale(uv_scale[0]);
-  } else {
-    set_uv_scale(uv_scale[1]);
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: set_uv_scale
 //       Access: Published
@@ -192,6 +181,64 @@ get_uv_scale() const {
   return cdata->_uv_scale;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: set_normal_mode
+//       Access: Published
+//  Description: Specifies the kind of normals to generate for the
+//               rope.  This is only applicable when the RenderMode is
+//               set to RM_tube; in the other render modes, normals
+//               are never generated.
+////////////////////////////////////////////////////////////////////
+INLINE void RopeNode::
+set_normal_mode(RopeNode::NormalMode normal_mode) {
+  CDWriter cdata(_cycler);
+  cdata->_normal_mode = normal_mode;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: get_normal_mode
+//       Access: Published
+//  Description: Returns the kind of normals to generate for the rope.
+//               This is only applicable when the RenderMode is set to
+//               RM_tube.
+////////////////////////////////////////////////////////////////////
+INLINE RopeNode::NormalMode RopeNode::
+get_normal_mode() const {
+  CDReader cdata(_cycler);
+  return cdata->_normal_mode;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: set_tube_up
+//       Access: Published
+//  Description: Specifies a normal vector, generally perpendicular to
+//               the main axis of the starting point of the curve,
+//               that controls the "top" of the curve, when RenderMode
+//               is RM_tube.  This is used to orient the vertices that
+//               make up the tube.  If this vector is too nearly
+//               parallel with the starting direction of the curve,
+//               there may be a tendency for the whole tube to
+//               gimble-lock around its primary axis.
+////////////////////////////////////////////////////////////////////
+INLINE void RopeNode::
+set_tube_up(const LVector3f &tube_up) {
+  CDWriter cdata(_cycler);
+  cdata->_tube_up = tube_up;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: get_tube_up
+//       Access: Published
+//  Description: Returns the normal vector used to control the "top"
+//               of the curve, when RenderMode is RM_tube.  See
+//               set_tube_up().
+////////////////////////////////////////////////////////////////////
+INLINE const LVector3f &RopeNode::
+get_tube_up() const {
+  CDReader cdata(_cycler);
+  return cdata->_tube_up;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: set_use_vertex_color
 //       Access: Published
@@ -246,6 +293,39 @@ get_num_subdiv() const {
   return cdata->_num_subdiv;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: set_num_slices
+//       Access: Published
+//  Description: Specifies the number of radial subdivisions to make
+//               if RenderMode is RM_tube.  It is ignored in the other
+//               render modes.
+//
+//               Increasing this number increases the roundness of a
+//               cross-section of the tube.  The minimum value for a
+//               dimensional tube is 3; setting it to 2 will get you a
+//               thin piece of tape (which is similar to RM_billboard,
+//               except it won't rotate to face the camera).
+////////////////////////////////////////////////////////////////////
+INLINE void RopeNode::
+set_num_slices(int num_slices) {
+  nassertv(num_slices >= 0);
+  CDWriter cdata(_cycler);
+  cdata->_num_slices = num_slices;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: get_num_slices
+//       Access: Published
+//  Description: Returns the number of radial subdivisions to make if
+//               RenderMode is RM_tube.  It is ignored in the other
+//               render modes.  See set_num_slices().
+////////////////////////////////////////////////////////////////////
+INLINE int RopeNode::
+get_num_slices() const {
+  CDReader cdata(_cycler);
+  return cdata->_num_slices;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: set_thickness
 //       Access: Published

+ 432 - 173
panda/src/parametrics/ropeNode.cxx

@@ -165,6 +165,10 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
         case RM_billboard:
           render_billboard(trav, data, result);
           break;
+          
+        case RM_tube:
+          render_tube(trav, data, result);
+          break;
         }
       }
     }
@@ -265,89 +269,45 @@ do_recompute_bound(const NodePath &rel_to) {
 ////////////////////////////////////////////////////////////////////
 void RopeNode::
 render_thread(CullTraverser *trav, CullTraverserData &data, 
-              NurbsCurveResult *result) {
-  UVMode uv_mode = get_uv_mode();
-  float uv_scale = get_uv_scale();
-  bool u_dominant = get_uv_direction();
-  bool use_vertex_color = get_use_vertex_color();
+              NurbsCurveResult *result) const {
+  CurveSegments curve_segments;
+  get_connected_segments(curve_segments, result);
+
+  // Now we have stored one or more sequences of vertices down the
+  // center strips.  Go back through and calculate the vertices on
+  // either side.
 
   PTA_Vertexf verts;
   PTA_TexCoordf uvs;
   PTA_Colorf colors;
-  PTA_int lengths;
 
-  int num_verts = get_num_subdiv() + 1;
-  int num_segments = result->get_num_segments();
-  float dist = 0.0f;
-  for (int segment = 0; segment < num_segments; segment++) {
-    LPoint3f last_point;
-    for (int i = 0; i < num_verts; i++) {
-      float t = (float)i / (float)(num_verts - 1);
-      LPoint3f point;
-      result->eval_segment_point(segment, t, point);
-      verts.push_back(point);
+  compute_thread_vertices(verts, uvs, colors, curve_segments);
 
-      if (use_vertex_color) {
-        Colorf color;
-        result->eval_segment_extended_points(segment, t, 0, &color[0], 4),
-        colors.push_back(color);
-      }
+  // Finally, build the lengths array to make them into proper
+  // line strips.
 
-      t = result->get_segment_t(segment, t);
-      switch (uv_mode) {
-      case UV_none:
-        break;
-        
-      case UV_parametric:
-        if (u_dominant) {
-          uvs.push_back(TexCoordf(t * uv_scale, 0.0f));
-        } else {
-          uvs.push_back(TexCoordf(0.0f, t * uv_scale));
-        }
-        break;
-
-      case UV_distance:
-        if (i != 0) {
-          LVector3f vec = point - last_point;
-          dist += vec.length();
-        }
-        if (u_dominant) {
-          uvs.push_back(TexCoordf(dist * uv_scale, 0.0f));
-        } else {
-          uvs.push_back(TexCoordf(0.0f, dist * uv_scale));
-        }
-        break;
+  PTA_int lengths;
+  int num_prims = 0;
 
-      case UV_distance2:
-        if (i != 0) {
-          LVector3f vec = point - last_point;
-          dist += vec.length_squared();
-        }
-        if (u_dominant) {
-          uvs.push_back(TexCoordf(dist * uv_scale, 0.0f));
-        } else {
-          uvs.push_back(TexCoordf(0.0f, dist * uv_scale));
-        }
-        break;
-      }
+  CurveSegments::const_iterator si;
+  for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
+    const CurveSegment &segment = (*si);
 
-      last_point = point;
-    }
-    lengths.push_back(num_verts);
+    lengths.push_back(segment.size());
+    num_prims++;
   }
   
   PT(GeomLinestrip) geom = new GeomLinestrip;
   geom->set_width(get_thickness());
-  geom->set_num_prims(num_segments);
+  geom->set_num_prims(num_prims);
   geom->set_coords(verts);
-  if (uv_mode != UV_none) {
+  if (get_uv_mode() != UV_none) {
     geom->set_texcoords(uvs, G_PER_VERTEX);
   }
 
-  if (use_vertex_color) {
+  if (get_use_vertex_color()) {
     geom->set_colors(colors, G_PER_VERTEX);
   } else {
-    colors.push_back(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
     geom->set_colors(colors, G_OVERALL);
   }
   geom->set_lengths(lengths);
@@ -365,11 +325,11 @@ render_thread(CullTraverser *trav, CullTraverserData &data,
 //               perpendicular to the camera plane.
 //
 //               In this mode, thickness is in spatial units, and
-//               determines the with of the triangle strips.
+//               determines the width of the triangle strips.
 ////////////////////////////////////////////////////////////////////
 void RopeNode::
 render_billboard(CullTraverser *trav, CullTraverserData &data, 
-                 NurbsCurveResult *result) {
+                 NurbsCurveResult *result) const {
   const TransformState *net_transform = data._net_transform;
   const TransformState *camera_transform = trav->get_camera_transform();
 
@@ -377,149 +337,448 @@ render_billboard(CullTraverser *trav, CullTraverserData &data,
     net_transform->invert_compose(camera_transform);
   LVector3f camera_vec = LVector3f::forward() * rel_transform->get_mat();
 
-  float thickness = get_thickness();
-  float radius = thickness * 0.5f;
-  UVMode uv_mode = get_uv_mode();
-  bool u_dominant = get_uv_direction();
-  float uv_scale = get_uv_scale();
+  CurveSegments curve_segments;
+  get_connected_segments(curve_segments, result);
+
+  // Now we have stored one or more sequences of vertices down the
+  // center strips.  Go back through and calculate the vertices on
+  // either side.
+
+  PTA_Vertexf verts;
+  PTA_TexCoordf uvs;
+  PTA_Colorf colors;
+
+  compute_billboard_vertices(verts, uvs, colors, camera_vec, 
+                             curve_segments, result);
 
-  // We can't just build one tristrip per segment.  Instead, we should
-  // build one continuous tristrip for all connected segments, so we
-  // can stitch them together properly at the seams.
+  // Finally, build the lengths array to make them into proper
+  // triangle strips.  We don't need a vindex array here, since the
+  // vertices just happened to end up in tristrip order.
 
+  PTA_int lengths;
+  int num_prims = 0;
+
+  CurveSegments::const_iterator si;
+  for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
+    const CurveSegment &segment = (*si);
+
+    lengths.push_back(segment.size() * 2);
+    num_prims++;
+  }
+
+  // And create a Geom for the rendering.
+  
+  PT(Geom) geom = new GeomTristrip;
+  geom->set_num_prims(num_prims);
+  geom->set_coords(verts);
+  if (get_uv_mode() != UV_none) {
+    geom->set_texcoords(uvs, G_PER_VERTEX);
+  }
+  if (get_use_vertex_color()) {
+    geom->set_colors(colors, G_PER_VERTEX);
+  } else {
+    geom->set_colors(colors, G_OVERALL);
+  }
+  geom->set_lengths(lengths);
+  
+  CullableObject *object = new CullableObject(geom, data._state,
+                                              data._render_transform);
+  trav->get_cull_handler()->record_object(object);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RopeNode::render_tube
+//       Access: Private
+//  Description: Draws the rope in RM_tube mode.  This draws a hollow
+//               tube centered around the string.
+//
+//               In this mode, thickness is in spatial units, and
+//               determines the diameter of the tube.
+////////////////////////////////////////////////////////////////////
+void RopeNode::
+render_tube(CullTraverser *trav, CullTraverserData &data, 
+            NurbsCurveResult *result) const {
+  CurveSegments curve_segments;
+  get_connected_segments(curve_segments, result);
+
+  // Now, we build up a table of vertices, in a series of rings
+  // around the circumference of the tube.
+
+  int num_slices = get_num_slices();
+  int num_verts_per_slice;
+
+  PTA_Vertexf verts;
+  PTA_Normalf normals;
+  PTA_TexCoordf uvs;
+  PTA_Colorf colors;
+
+  compute_tube_vertices(verts, normals, uvs, colors,
+                        num_verts_per_slice, curve_segments, result);
+
+  // Finally, go back one more time and build up the vindex array, to
+  // tie all the triangle strips together.
+
+  PTA_ushort vindex;
+  PTA_int lengths;
+
+  int num_prims = 0;
+  int vi = 0;
+  CurveSegments::const_iterator si;
+  for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
+    const CurveSegment &segment = (*si);
+
+    for (int s = 0; s < num_slices; ++s) {
+      int s1 = (s + 1) % num_verts_per_slice;
+
+      for (size_t j = 0; j < segment.size(); ++j) {
+        vindex.push_back((vi + j) * num_verts_per_slice + s);
+        vindex.push_back((vi + j) * num_verts_per_slice + s1);
+      }
+
+      lengths.push_back(segment.size() * 2);
+      num_prims++;
+    }
+    vi += (int)segment.size();
+  }
+
+  // And create a Geom for the rendering.
+  
+  PT(Geom) geom = new GeomTristrip;
+  geom->set_num_prims(num_prims);
+  geom->set_coords(verts, vindex);
+  if (get_uv_mode() != UV_none) {
+    geom->set_texcoords(uvs, G_PER_VERTEX, vindex);
+  }
+
+  if (get_normal_mode() == NM_vertex) {
+    geom->set_normals(normals, G_PER_VERTEX, vindex);
+  }
+
+  if (get_use_vertex_color()) {
+    geom->set_colors(colors, G_PER_VERTEX, vindex);
+  } else {
+    geom->set_colors(colors, G_OVERALL);
+  }
+  geom->set_lengths(lengths);
+
+  CullableObject *object = new CullableObject(geom, data._state,
+                                              data._render_transform);
+  trav->get_cull_handler()->record_object(object);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RopeNode::get_connected_segments
+//       Access: Private
+//  Description: Evaluates the string of vertices along the curve, and
+//               also breaks them up into connected segments.
+//
+//               Since the NurbsCurveEvaluator describes the curve as
+//               a sequence of possibly-connected piecewise continuous
+//               segments, this means joining together some adjacent
+//               segments from the NurbsCurveEvaluator into a single
+//               CurveSegment, if they happen to be connected (as most
+//               will be).
+////////////////////////////////////////////////////////////////////
+void RopeNode::
+get_connected_segments(RopeNode::CurveSegments &curve_segments,
+                       const NurbsCurveResult *result) const {
   int num_verts = get_num_subdiv() + 1;
   int num_segments = result->get_num_segments();
+  bool use_vertex_color = get_use_vertex_color();
 
-  vector_Vertexf center_verts;
-  vector_int center_lengths;
-  vector_float center_t;
+  CurveSegment *curve_segment = NULL;
+  LPoint3f last_point;
 
-  LPoint3f point;
-  int cur_length = 0;
-  for (int segment = 0; segment < num_segments; segment++) {
-    LPoint3f first_point;
-    result->eval_segment_point(segment, 0.0f, first_point);
-    if (cur_length == 0 || point != first_point) {
+  for (int segment = 0; segment < num_segments; ++segment) {
+    LPoint3f point;
+    result->eval_segment_point(segment, 0.0f, point);
+
+    if (curve_segment == (CurveSegment *)NULL || 
+        !point.almost_equal(last_point)) {
       // If the first point of this segment is different from the last
-      // point of the previous segment, end the tristrip and store the
-      // point.
-      if (cur_length != 0) {
-        center_lengths.push_back(cur_length);
+      // point of the previous segment, end the previous segment and
+      // begin a new one.
+      curve_segments.push_back(CurveSegment());
+      curve_segment = &curve_segments.back();
+
+      CurveVertex vtx;
+      vtx._p = point;
+      vtx._t = result->get_segment_t(segment, 0.0f);
+      if (use_vertex_color) {
+        result->eval_segment_extended_points(segment, 0.0f, 0, &vtx._c[0], 4);
       }
-      center_verts.push_back(first_point);
-      center_t.push_back(result->get_segment_t(segment, 0.0f));
-      cur_length = 1;
+
+      curve_segment->push_back(vtx);
     }
 
     // Store all the remaining points in this segment.
-    for (int i = 1; i < num_verts; i++) {
+    for (int i = 1; i < num_verts; ++i) {
       float t = (float)i / (float)(num_verts - 1);
-      result->eval_segment_point(segment, t, point);
-      center_verts.push_back(point);
-      center_t.push_back(result->get_segment_t(segment, t));
-      cur_length++;
+
+      CurveVertex vtx;
+      result->eval_segment_point(segment, t, vtx._p);
+      vtx._t = result->get_segment_t(segment, t);
+      if (use_vertex_color) {
+        result->eval_segment_extended_points(segment, t, 0, &vtx._c[0], 4);
+      }
+
+      curve_segment->push_back(vtx);
+
+      last_point = vtx._p;
     }
   }
-  if (cur_length != 0) {
-    center_lengths.push_back(cur_length);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RopeNode::compute_thread_vertices
+//       Access: Private
+//  Description: Calculates the vertices for a RM_thread render.  This
+//               just copies the vertices more-or-less directly into
+//               the array.
+////////////////////////////////////////////////////////////////////
+void RopeNode::
+compute_thread_vertices(PTA_Vertexf &verts, PTA_TexCoordf &uvs, 
+                        PTA_Colorf &colors,
+                        const RopeNode::CurveSegments &curve_segments) const {
+  UVMode uv_mode = get_uv_mode();
+  float uv_scale = get_uv_scale();
+  bool u_dominant = get_uv_direction();
+  bool use_vertex_color = get_use_vertex_color();
+
+  float dist = 0.0f;
+  CurveSegments::const_iterator si;
+  for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
+    const CurveSegment &segment = (*si);
+    for (size_t j = 0; j < segment.size(); ++j) {
+      verts.push_back(segment[j]._p);
+
+      if (use_vertex_color) {
+        colors.push_back(segment[j]._c);
+      }
+
+      float uv_t = compute_uv_t(dist, uv_mode, uv_scale, segment, j);
+
+      if (uv_mode != UV_none) {
+        if (u_dominant) {
+          uvs.push_back(TexCoordf(uv_t, 0.0f));
+        } else {
+          uvs.push_back(TexCoordf(0.0f, uv_t));
+        }
+      }
+    }
   }
 
-  // Now we have stored one or more sequences of vertices down the
-  // center strips.  Go back and convert them into actual tristrips.
+  if (!use_vertex_color) {
+    colors.push_back(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
+  }
+}
 
-  PTA_Vertexf verts;
-  PTA_TexCoordf uvs;
-  PTA_Colorf colors;
-  PTA_int lengths;
+////////////////////////////////////////////////////////////////////
+//     Function: RopeNode::compute_billboard_vertices
+//       Access: Private
+//  Description: Calculates the vertices for a RM_billboard render.  This
+//               puts a pair of vertices on either side of each
+//               computed point in curve_segments.
+////////////////////////////////////////////////////////////////////
+void RopeNode::
+compute_billboard_vertices(PTA_Vertexf &verts, PTA_TexCoordf &uvs, 
+                           PTA_Colorf &colors, const LVector3f &camera_vec,
+                           const RopeNode::CurveSegments &curve_segments,
+                           NurbsCurveResult *result) const {
+  float thickness = get_thickness();
+  float radius = thickness * 0.5f;
+  UVMode uv_mode = get_uv_mode();
+  float uv_scale = get_uv_scale();
+  bool u_dominant = get_uv_direction();
+  bool use_vertex_color = get_use_vertex_color();
 
-  int vi = 0;
-  int num_prims = 0;
   float dist = 0.0f;
-  for (int i = 0; i < (int)center_lengths.size(); i++) {
-    int length = center_lengths[i];
-    for (int j = 0; j < length; j++) {
-      const Vertexf &point = center_verts[vi + j];
-      float t = center_t[vi + j];
+  CurveSegments::const_iterator si;
+  for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
+    const CurveSegment &segment = (*si);
+    for (size_t j = 0; j < segment.size(); ++j) {
       LVector3f tangent;
-      // Rather than evaluating the curve for the tangent, we derive
-      // it from the neighboring points.  This gives us better
-      // behavior at the endpoints, where the tangent might go to
-      // zero.
-      if (j == 0) {
-        tangent = center_verts[vi + j + 1] - point;
-      } else if (j == length - 1) {
-        tangent = point - center_verts[vi + j - 1];
-      } else {
-        tangent = center_verts[vi + j + 1] - center_verts[vi + j - 1];
+      compute_tangent(tangent, segment, j, result);
+
+      LVector3f normal = cross(tangent, camera_vec);
+      normal.normalize();
+
+      verts.push_back(segment[j]._p + normal * radius);
+      verts.push_back(segment[j]._p - normal * radius);
+
+      if (use_vertex_color) {
+        colors.push_back(segment[j]._c);
+        colors.push_back(segment[j]._c);
       }
-      LVector3f cross = normalize(tangent.cross(camera_vec));
-      cross *= radius;
-      verts.push_back(point + cross);
-      verts.push_back(point - cross);
-      switch (uv_mode) {
-      case UV_none:
-        break;
-
-      case UV_parametric:
+
+      float uv_t = compute_uv_t(dist, uv_mode, uv_scale, segment, j);
+
+      if (uv_mode != UV_none) {
         if (u_dominant) {
-          uvs.push_back(TexCoordf(t * uv_scale, 1.0f));
-          uvs.push_back(TexCoordf(t * uv_scale, 0.0f));
+          uvs.push_back(TexCoordf(uv_t, 1.0f));
+          uvs.push_back(TexCoordf(uv_t, 0.0f));
         } else {
-          uvs.push_back(TexCoordf(1.0f, t * uv_scale));
-          uvs.push_back(TexCoordf(0.0f, t * uv_scale));
+          uvs.push_back(TexCoordf(1.0f, uv_t));
+          uvs.push_back(TexCoordf(0.0f, uv_t));
         }
-        break;
+      }
+    }
+  }
 
-      case UV_distance:
-        if (j != 0) {
-          LVector3f vec = point - center_verts[vi + j - 1];
-          dist += vec.length();
-        }
-        if (u_dominant) {
-          uvs.push_back(TexCoordf(dist * uv_scale, 1.0f));
-          uvs.push_back(TexCoordf(dist * uv_scale, 0.0f));
-        } else {
-          uvs.push_back(TexCoordf(1.0f, dist * uv_scale));
-          uvs.push_back(TexCoordf(0.0f, dist * uv_scale));
+  if (!use_vertex_color) {
+    colors.push_back(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RopeNode::compute_tube_vertices
+//       Access: Private
+//  Description: Calculates the vertices for a RM_tube render.  This
+//               puts a ring of vertices around each computed point in
+//               curve_segments.
+////////////////////////////////////////////////////////////////////
+void RopeNode::
+compute_tube_vertices(PTA_Vertexf &verts, PTA_Normalf &normals,
+                      PTA_TexCoordf &uvs, PTA_Colorf &colors,
+                      int &num_verts_per_slice,
+                      const RopeNode::CurveSegments &curve_segments,
+                      NurbsCurveResult *result) const {
+  int num_slices = get_num_slices();
+  num_verts_per_slice = num_slices;
+
+  float thickness = get_thickness();
+  float radius = thickness * 0.5f;
+  UVMode uv_mode = get_uv_mode();
+  float uv_scale = get_uv_scale();
+  bool u_dominant = get_uv_direction();
+  NormalMode normal_mode = get_normal_mode();
+  bool use_vertex_color = get_use_vertex_color();
+
+  // If we are generating UV's, we will need to duplicate the vertices
+  // along the seam so that the UV's go through the whole range of
+  // 0..1 instead of reflecting in the last polygon before the seam.
+  if (uv_mode != UV_none) {
+    ++num_verts_per_slice;
+  }
+
+  LVector3f up = get_tube_up();
+
+  float dist = 0.0f;
+  CurveSegments::const_iterator si;
+  for (si = curve_segments.begin(); si != curve_segments.end(); ++si) {
+    const CurveSegment &segment = (*si);
+    for (size_t j = 0; j < segment.size(); ++j) {
+      LVector3f tangent;
+      compute_tangent(tangent, segment, j, result);
+
+      LVector3f normal = cross(tangent, up);
+      normal.normalize();
+      up = cross(normal, tangent);
+
+      LMatrix3f rotate = LMatrix3f::rotate_mat(360.0f / (float)num_slices,
+                                               tangent);
+
+      float uv_t = compute_uv_t(dist, uv_mode, uv_scale, segment, j);
+
+      for (int s = 0; s < num_verts_per_slice; ++s) {
+        verts.push_back(segment[j]._p + normal * radius);
+
+        if (normal_mode == NM_vertex) {
+          normals.push_back(normal);
         }
-        break;
 
-      case UV_distance2:
-        if (j != 0) {
-          LVector3f vec = point - center_verts[vi + j - 1];
-          dist += vec.length_squared();
+        if (use_vertex_color) {
+          colors.push_back(segment[j]._c);
         }
-        if (u_dominant) {
-          uvs.push_back(TexCoordf(dist * uv_scale, 1.0f));
-          uvs.push_back(TexCoordf(dist * uv_scale, 0.0f));
-        } else {
-          uvs.push_back(TexCoordf(1.0f, dist * uv_scale));
-          uvs.push_back(TexCoordf(0.0f, dist * uv_scale));
+
+        normal = normal * rotate;
+
+        if (uv_mode != UV_none) {
+          float uv_s = (float)s / (float)num_slices;
+          if (u_dominant) {
+            uvs.push_back(TexCoordf(uv_t, uv_s));
+          } else {
+            uvs.push_back(TexCoordf(uv_s, uv_t));
+          }
         }
-        break;
       }
     }
-    vi += length;
-    lengths.push_back(length * 2);
-    num_prims++;
   }
 
-  colors.push_back(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
-  
-  PT(Geom) geom = new GeomTristrip;
-  geom->set_num_prims(num_prims);
-  geom->set_coords(verts);
-  if (uv_mode != UV_none) {
-    geom->set_texcoords(uvs, G_PER_VERTEX);
+  if (!use_vertex_color) {
+    colors.push_back(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RopeNode::compute_tangent
+//       Access: Private, Static
+//  Description: Computes the tangent to the curve at the indicated
+//               point in the segment.
+////////////////////////////////////////////////////////////////////
+void RopeNode::
+compute_tangent(LVector3f &tangent, const RopeNode::CurveSegment &segment, 
+                size_t j, NurbsCurveResult *result) {
+  // First, try to evaluate the tangent at the curve.  This gives
+  // better results at the ends at the endpoints where the tangent
+  // does not go to zero.
+
+  /*
+    Actually, on second thought this looks terrible.
+
+  if (result->eval_tangent(segment[j]._t, tangent)) {
+    if (!tangent.almost_equal(LVector3f::zero())) {
+      return;
+    }
+  }
+  */
+
+  // If that failed (or produced a zero tangent), then derive the
+  // tangent from the neighboring points instead.
+  if (j == 0) {
+    tangent = segment[j + 1]._p - segment[j]._p;
+  } else if (j == segment.size() - 1) {
+    tangent = segment[j]._p - segment[j - 1]._p;
+  } else {
+    tangent = segment[j + 1]._p - segment[j - 1]._p;
   }
-  geom->set_colors(colors, G_OVERALL);
-  geom->set_lengths(lengths);
-  
-  CullableObject *object = new CullableObject(geom, data._state,
-                                              data._render_transform);
-  trav->get_cull_handler()->record_object(object);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: RopeNode::compute_uv_t
+//       Access: Private, Static
+//  Description: Computes the texture coordinate along the curve for
+//               the indicated point in the segment.
+////////////////////////////////////////////////////////////////////
+float RopeNode::
+compute_uv_t(float &dist, const RopeNode::UVMode &uv_mode,
+             float uv_scale, const RopeNode::CurveSegment &segment,
+             size_t j) {
+  switch (uv_mode) {
+  case UV_none:
+    return 0.0f;
+    
+  case UV_parametric:
+    return segment[j]._t * uv_scale;
+    
+  case UV_distance:
+    if (j != 0) {
+      LVector3f vec = segment[j]._p - segment[j - 1]._p;
+      dist += vec.length();
+    }
+    return dist * uv_scale;
+    
+  case UV_distance2:
+    if (j != 0) {
+      LVector3f vec = segment[j]._p - segment[j - 1]._p;
+      dist += vec.length_squared();
+    }
+    return dist * uv_scale;
+  }
+
+  return 0.0f;
+}
+  
 ////////////////////////////////////////////////////////////////////
 //     Function: RopeNode::register_with_read_factory
 //       Access: Public, Static

+ 61 - 4
panda/src/parametrics/ropeNode.h

@@ -59,7 +59,10 @@ PUBLISHED:
 
     // Render the rope as a continuous triangle strip oriented to be
     // perpendicular to the view vector.
-    RM_billboard
+    RM_billboard,
+
+    // Render the rope as a hollow tube extruded along its length.
+    RM_tube
   };
 
   enum UVMode {
@@ -83,6 +86,14 @@ PUBLISHED:
     UV_distance2,
   };
 
+  enum NormalMode {
+    // Don't generate normals.
+    NM_none,
+
+    // Generate vertex (smooth-shaded) normals.
+    NM_vertex
+  };
+
   INLINE void set_curve(NurbsCurveEvaluator *curve);
   INLINE NurbsCurveEvaluator *get_curve() const;
 
@@ -95,16 +106,24 @@ PUBLISHED:
   INLINE void set_uv_direction(bool u_dominant);
   INLINE bool get_uv_direction() const;
 
-  INLINE void set_uv_scale(const LVecBase2f &uv_scale);
   INLINE void set_uv_scale(float scale);
   INLINE float get_uv_scale() const;
 
+  INLINE void set_normal_mode(NormalMode normal_mode);
+  INLINE NormalMode get_normal_mode() const;
+
+  INLINE void set_tube_up(const LVector3f &tube_up);
+  INLINE const LVector3f &get_tube_up() const;
+
   INLINE void set_use_vertex_color(bool flag);
   INLINE bool get_use_vertex_color() const;
 
   INLINE void set_num_subdiv(int num_subdiv);
   INLINE int get_num_subdiv() const;
 
+  INLINE void set_num_slices(int num_slices);
+  INLINE int get_num_slices() const;
+
   INLINE void set_thickness(float thickness);
   INLINE float get_thickness() const;
 
@@ -116,9 +135,44 @@ protected:
 private:
   BoundingVolume *do_recompute_bound(const NodePath &rel_to);
   void render_thread(CullTraverser *trav, CullTraverserData &data, 
-                     NurbsCurveResult *result);
+                     NurbsCurveResult *result) const;
   void render_billboard(CullTraverser *trav, CullTraverserData &data, 
-                        NurbsCurveResult *result);
+                        NurbsCurveResult *result) const;
+  void render_tube(CullTraverser *trav, CullTraverserData &data, 
+                   NurbsCurveResult *result) const;
+
+  class CurveVertex {
+  public:
+    LPoint3f _p;
+    Colorf _c;
+    float _t;
+  };
+  typedef pvector<CurveVertex> CurveSegment;
+  typedef pvector<CurveSegment> CurveSegments;
+
+  void get_connected_segments(CurveSegments &curve_segments,
+                              const NurbsCurveResult *result) const;
+
+  void compute_thread_vertices(PTA_Vertexf &verts, PTA_TexCoordf &uvs, 
+                               PTA_Colorf &colors,
+                               const CurveSegments &curve_segments) const;
+  void compute_billboard_vertices(PTA_Vertexf &verts, PTA_TexCoordf &uvs, 
+                                  PTA_Colorf &colors,
+                                  const LVector3f &camera_vec,
+                                  const CurveSegments &curve_segments,
+                                  NurbsCurveResult *result) const;
+  void compute_tube_vertices(PTA_Vertexf &verts, PTA_Normalf &normals,
+                             PTA_TexCoordf &uvs, PTA_Colorf &colors,
+                             int &num_verts_per_slice,
+                             const CurveSegments &curve_segments,
+                             NurbsCurveResult *result) const;
+
+  static void compute_tangent(LVector3f &tangent, const CurveSegment &segment,
+                              size_t j, NurbsCurveResult *result);
+  static float compute_uv_t(float &dist, const UVMode &uv_mode,
+                            float uv_scale, const CurveSegment &segment,
+                            size_t j);
+
 
 private:
   // This is the data that must be cycled between pipeline stages.
@@ -135,8 +189,11 @@ private:
     UVMode _uv_mode;
     bool _u_dominant;
     float _uv_scale;
+    NormalMode _normal_mode;
+    LVector3f _tube_up;
     bool _use_vertex_color;
     int _num_subdiv;
+    int _num_slices;
     float _thickness;
   };