Browse Source

nonindexed primitives

David Rose 20 years ago
parent
commit
e0dca3b43b

+ 266 - 127
panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx

@@ -2739,29 +2739,51 @@ void DXGraphicsStateGuardian8::
 draw_triangles(const qpGeomTriangles *primitive) {
   _vertices_tri_pcollector.add_level(primitive->get_num_vertices());
   _primitive_batches_tri_pcollector.add_level(1);
-  if (_vbuffer_active) {
-    IndexBufferContext *ibc = ((qpGeomPrimitive *)primitive)->prepare_now(get_prepared_objects(), this);
-    nassertv(ibc != (IndexBufferContext *)NULL);
-    apply_index_buffer(ibc);
-
-    _pD3DDevice->DrawIndexedPrimitive
-      (D3DPT_TRIANGLELIST,
-       primitive->get_min_vertex(),
-       primitive->get_max_vertex() - primitive->get_min_vertex() + 1,
-       0, primitive->get_num_primitives());
-
+  if (primitive->is_indexed()) {
+    if (_vbuffer_active) {
+      // Indexed, vbuffers.
+      IndexBufferContext *ibc = ((qpGeomPrimitive *)primitive)->prepare_now(get_prepared_objects(), this);
+      nassertv(ibc != (IndexBufferContext *)NULL);
+      apply_index_buffer(ibc);
+      
+      _pD3DDevice->DrawIndexedPrimitive
+        (D3DPT_TRIANGLELIST,
+         primitive->get_min_vertex(),
+         primitive->get_max_vertex() - primitive->get_min_vertex() + 1,
+         0, primitive->get_num_primitives());
+      
+    } else {
+      // Indexed, client arrays.
+      D3DFORMAT index_type = get_index_type(primitive->get_index_type());
+      
+      _pD3DDevice->DrawIndexedPrimitiveUP
+        (D3DPT_TRIANGLELIST, 
+         primitive->get_min_vertex(),
+         primitive->get_max_vertex() - primitive->get_min_vertex() + 1,
+         primitive->get_num_primitives(), 
+         primitive->get_data(),
+         index_type,
+         _vertex_data->get_array(0)->get_data(),
+         _vertex_data->get_format()->get_array(0)->get_stride());
+    }
   } else {
-    D3DFORMAT index_type = get_index_type(primitive->get_index_type());
-
-    _pD3DDevice->DrawIndexedPrimitiveUP
-      (D3DPT_TRIANGLELIST, 
-       primitive->get_min_vertex(),
-       primitive->get_max_vertex() - primitive->get_min_vertex() + 1,
-       primitive->get_num_primitives(), 
-       primitive->get_data(),
-       index_type,
-       _vertex_data->get_array(0)->get_data(),
-       _vertex_data->get_format()->get_array(0)->get_stride());
+    if (_vbuffer_active) {
+      // Nonindexed, vbuffers.
+      _pD3DDevice->DrawPrimitive
+        (D3DPT_TRIANGLELIST,
+         primitive->get_first_vertex(),
+         primitive->get_num_primitives());
+      
+    } else {
+      // Nonindexed, client arrays.
+      int stride = _vertex_data->get_format()->get_array(0)->get_stride();
+      unsigned int first_vertex = primitive->get_first_vertex();
+      _pD3DDevice->DrawPrimitiveUP
+        (D3DPT_TRIANGLELIST, 
+         primitive->get_num_primitives(),
+         _vertex_data->get_array(0)->get_data() + stride * first_vertex,
+         stride);
+    }
   }
 }
 
@@ -2781,137 +2803,232 @@ draw_tristrips(const qpGeomTristrips *primitive) {
     // that have already been set up within the primitive.
     _vertices_tristrip_pcollector.add_level(primitive->get_num_vertices());
     _primitive_batches_tristrip_pcollector.add_level(1);
-    if (_vbuffer_active) {
-      IndexBufferContext *ibc = ((qpGeomPrimitive *)primitive)->prepare_now(get_prepared_objects(), this);
-      nassertv(ibc != (IndexBufferContext *)NULL);
-      apply_index_buffer(ibc);
-
-      _pD3DDevice->DrawIndexedPrimitive
-        (D3DPT_TRIANGLESTRIP,
-         min_vertex, max_vertex - min_vertex + 1,
-         0, primitive->get_num_vertices() - 2);
-      
+    if (primitive->is_indexed()) {
+      if (_vbuffer_active) {
+        // Indexed, vbuffers, one line triangle strip.
+        IndexBufferContext *ibc = ((qpGeomPrimitive *)primitive)->prepare_now(get_prepared_objects(), this);
+        nassertv(ibc != (IndexBufferContext *)NULL);
+        apply_index_buffer(ibc);
+        
+        _pD3DDevice->DrawIndexedPrimitive
+          (D3DPT_TRIANGLESTRIP,
+           min_vertex, max_vertex - min_vertex + 1,
+           0, primitive->get_num_vertices() - 2);
+        
+      } else {
+        // Indexed, client arrays, one long triangle strip.
+        _pD3DDevice->DrawIndexedPrimitiveUP
+          (D3DPT_TRIANGLESTRIP, 
+           min_vertex, max_vertex - min_vertex + 1,
+           primitive->get_num_vertices() - 2, 
+           primitive->get_data(), index_type,
+           _vertex_data->get_array(0)->get_data(),
+           _vertex_data->get_format()->get_array(0)->get_stride());
+      }
     } else {
-      _pD3DDevice->DrawIndexedPrimitiveUP
-        (D3DPT_TRIANGLESTRIP, 
-         min_vertex, max_vertex - min_vertex + 1,
-         primitive->get_num_vertices() - 2, 
-         primitive->get_data(), index_type,
-         _vertex_data->get_array(0)->get_data(),
-         _vertex_data->get_format()->get_array(0)->get_stride());
+      if (_vbuffer_active) {
+        // Nonindexed, vbuffers, one long triangle strip.
+        _pD3DDevice->DrawPrimitive
+          (D3DPT_TRIANGLESTRIP,
+           primitive->get_first_vertex(),
+           primitive->get_num_vertices() - 2);
+        
+      } else {
+        // Indexed, client arrays, one long triangle strip.
+        int stride = _vertex_data->get_format()->get_array(0)->get_stride();
+        unsigned int first_vertex = primitive->get_first_vertex();
+        _pD3DDevice->DrawPrimitiveUP
+          (D3DPT_TRIANGLESTRIP, 
+           primitive->get_num_vertices() - 2, 
+           _vertex_data->get_array(0)->get_data() + stride * first_vertex,
+           stride);
+      }
     }
 
   } else {
     // Send the individual triangle strips, stepping over the
     // degenerate vertices.
     CPTA_int ends = primitive->get_ends();
-    int index_stride = primitive->get_index_stride();
     _primitive_batches_tristrip_pcollector.add_level(ends.size());
 
+    if (primitive->is_indexed()) {
+      CPTA_int ends = primitive->get_ends();
+      int index_stride = primitive->get_index_stride();
+      _primitive_batches_tristrip_pcollector.add_level(ends.size());
+      
+      qpGeomVertexReader mins(primitive->get_mins(), 0);
+      qpGeomVertexReader maxs(primitive->get_maxs(), 0);
+      nassertv(primitive->get_mins()->get_num_rows() == (int)ends.size() && 
+               primitive->get_maxs()->get_num_rows() == (int)ends.size());
+      
+      if (_vbuffer_active) {
+        // Indexed, client arrays, individual triangle strips.
+        IndexBufferContext *ibc = ((qpGeomPrimitive *)primitive)->prepare_now(get_prepared_objects(), this);
+        nassertv(ibc != (IndexBufferContext *)NULL);
+        apply_index_buffer(ibc);
+        
+        unsigned int start = 0;
+        for (size_t i = 0; i < ends.size(); i++) {
+          _vertices_tristrip_pcollector.add_level(ends[i] - start);
+          unsigned int min = mins.get_data1i();
+          unsigned int max = maxs.get_data1i();
+          _pD3DDevice->DrawIndexedPrimitive
+            (D3DPT_TRIANGLESTRIP,
+             min, max - min + 1, 
+             start, ends[i] - start - 2);
+          
+          start = ends[i] + 2;
+        }
+        
+      } else {
+        // Indexed, client arrays, individual triangle strips.
+        CPTA_uchar array_data = _vertex_data->get_array(0)->get_data();
+        int stride = _vertex_data->get_format()->get_array(0)->get_stride();
+        CPTA_uchar vertices = primitive->get_data();
+        
+        unsigned int start = 0;
+        for (size_t i = 0; i < ends.size(); i++) {
+          _vertices_tristrip_pcollector.add_level(ends[i] - start);
+          unsigned int min = mins.get_data1i();
+          unsigned int max = maxs.get_data1i();
+          _pD3DDevice->DrawIndexedPrimitiveUP
+            (D3DPT_TRIANGLESTRIP, 
+             min, max - min + 1, 
+             ends[i] - start - 2,
+             vertices + start * index_stride, index_type,
+             array_data, stride);
+          
+          start = ends[i] + 2;
+        }
+      }
+    } else {
+      if (_vbuffer_active) {
+        // Nonindexed, client arrays, individual triangle strips.
+        unsigned int first_vertex = primitive->get_first_vertex();
+        unsigned int start = 0;
+        for (size_t i = 0; i < ends.size(); i++) {
+          _vertices_tristrip_pcollector.add_level(ends[i] - start);
+          _pD3DDevice->DrawPrimitive
+            (D3DPT_TRIANGLESTRIP,
+             first_vertex + start, ends[i] - start - 2);
+          
+          start = ends[i] + 2;
+        }
+        
+      } else {
+        // Nonindexed, client arrays, individual triangle strips.
+        CPTA_uchar array_data = _vertex_data->get_array(0)->get_data();
+        int stride = _vertex_data->get_format()->get_array(0)->get_stride();
+        unsigned int first_vertex = primitive->get_first_vertex();
+        
+        unsigned int start = 0;
+        for (size_t i = 0; i < ends.size(); i++) {
+          _vertices_tristrip_pcollector.add_level(ends[i] - start);
+          _pD3DDevice->DrawPrimitiveUP
+            (D3DPT_TRIANGLESTRIP, 
+             ends[i] - start - 2,
+             array_data + (first_vertex + start) * stride, stride);
+          
+          start = ends[i] + 2;
+        }
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DXGraphicsStateGuardian8::draw_trifans
+//       Access: Public, Virtual
+//  Description: Draws a series of triangle fans.
+////////////////////////////////////////////////////////////////////
+void DXGraphicsStateGuardian8::
+draw_trifans(const qpGeomTrifans *primitive) {
+  CPTA_int ends = primitive->get_ends();
+  _primitive_batches_trifan_pcollector.add_level(ends.size());
+
+  if (primitive->is_indexed()) {
+    int min_vertex = primitive->get_min_vertex();
+    int max_vertex = primitive->get_max_vertex();
+    D3DFORMAT index_type = get_index_type(primitive->get_index_type());
+    
+    // Send the individual triangle fans.  There's no connecting fans
+    // with degenerate vertices, so no worries about that.
+    int index_stride = primitive->get_index_stride();
+    
     qpGeomVertexReader mins(primitive->get_mins(), 0);
     qpGeomVertexReader maxs(primitive->get_maxs(), 0);
     nassertv(primitive->get_mins()->get_num_rows() == (int)ends.size() && 
              primitive->get_maxs()->get_num_rows() == (int)ends.size());
     
     if (_vbuffer_active) {
+      // Indexed, vbuffers.
       IndexBufferContext *ibc = ((qpGeomPrimitive *)primitive)->prepare_now(get_prepared_objects(), this);
       nassertv(ibc != (IndexBufferContext *)NULL);
       apply_index_buffer(ibc);
       
       unsigned int start = 0;
       for (size_t i = 0; i < ends.size(); i++) {
-        _vertices_tristrip_pcollector.add_level(ends[i] - start);
+        _vertices_trifan_pcollector.add_level(ends[i] - start);
         unsigned int min = mins.get_data1i();
         unsigned int max = maxs.get_data1i();
         _pD3DDevice->DrawIndexedPrimitive
-          (D3DPT_TRIANGLESTRIP,
-           min, max - min + 1, 
+          (D3DPT_TRIANGLEFAN,
+           min, max - min + 1,
            start, ends[i] - start - 2);
         
-        start = ends[i] + 2;
+        start = ends[i];
       }
       
     } else {
+      // Indexed, client arrays.
       CPTA_uchar array_data = _vertex_data->get_array(0)->get_data();
       int stride = _vertex_data->get_format()->get_array(0)->get_stride();
       CPTA_uchar vertices = primitive->get_data();
       
       unsigned int start = 0;
       for (size_t i = 0; i < ends.size(); i++) {
-        _vertices_tristrip_pcollector.add_level(ends[i] - start);
+        _vertices_trifan_pcollector.add_level(ends[i] - start);
         unsigned int min = mins.get_data1i();
         unsigned int max = maxs.get_data1i();
         _pD3DDevice->DrawIndexedPrimitiveUP
-          (D3DPT_TRIANGLESTRIP, 
-           min, max - min + 1, 
+          (D3DPT_TRIANGLEFAN, 
+           min, max - min + 1,
            ends[i] - start - 2,
            vertices + start * index_stride, index_type,
            array_data, stride);
         
-        start = ends[i] + 2;
+        start = ends[i];
       }
     }
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: DXGraphicsStateGuardian8::draw_trifans
-//       Access: Public, Virtual
-//  Description: Draws a series of triangle fans.
-////////////////////////////////////////////////////////////////////
-void DXGraphicsStateGuardian8::
-draw_trifans(const qpGeomTrifans *primitive) {
-  int min_vertex = primitive->get_min_vertex();
-  int max_vertex = primitive->get_max_vertex();
-  D3DFORMAT index_type = get_index_type(primitive->get_index_type());
-
-  // Send the individual triangle fans.  There's no connecting fans
-  // with degenerate vertices, so no worries about that.
-  CPTA_int ends = primitive->get_ends();
-  int index_stride = primitive->get_index_stride();
-  _primitive_batches_trifan_pcollector.add_level(ends.size());
-
-  qpGeomVertexReader mins(primitive->get_mins(), 0);
-  qpGeomVertexReader maxs(primitive->get_maxs(), 0);
-  nassertv(primitive->get_mins()->get_num_rows() == (int)ends.size() && 
-           primitive->get_maxs()->get_num_rows() == (int)ends.size());
-  
-  if (_vbuffer_active) {
-    IndexBufferContext *ibc = ((qpGeomPrimitive *)primitive)->prepare_now(get_prepared_objects(), this);
-    nassertv(ibc != (IndexBufferContext *)NULL);
-    apply_index_buffer(ibc);
-    
-    unsigned int start = 0;
-    for (size_t i = 0; i < ends.size(); i++) {
-      _vertices_trifan_pcollector.add_level(ends[i] - start);
-      unsigned int min = mins.get_data1i();
-      unsigned int max = maxs.get_data1i();
-      _pD3DDevice->DrawIndexedPrimitive
-        (D3DPT_TRIANGLEFAN,
-         min, max - min + 1,
-         start, ends[i] - start - 2);
-      
-      start = ends[i];
-    }
-    
   } else {
-    CPTA_uchar array_data = _vertex_data->get_array(0)->get_data();
-    int stride = _vertex_data->get_format()->get_array(0)->get_stride();
-    CPTA_uchar vertices = primitive->get_data();
-
-    unsigned int start = 0;
-    for (size_t i = 0; i < ends.size(); i++) {
-      _vertices_trifan_pcollector.add_level(ends[i] - start);
-      unsigned int min = mins.get_data1i();
-      unsigned int max = maxs.get_data1i();
-      _pD3DDevice->DrawIndexedPrimitiveUP
-        (D3DPT_TRIANGLEFAN, 
-         min, max - min + 1,
-         ends[i] - start - 2,
-         vertices + start * index_stride, index_type,
-         array_data, stride);
+    if (_vbuffer_active) {
+      // Nonindexed, vbuffers.
+      unsigned int start = 0;
+      for (size_t i = 0; i < ends.size(); i++) {
+        _vertices_trifan_pcollector.add_level(ends[i] - start);
+        _pD3DDevice->DrawPrimitive
+          (D3DPT_TRIANGLEFAN,
+           start, ends[i] - start - 2);
+        
+        start = ends[i];
+      }
+      
+    } else {
+      // Nonindexed, client arrays.
+      CPTA_uchar array_data = _vertex_data->get_array(0)->get_data();
+      int stride = _vertex_data->get_format()->get_array(0)->get_stride();
+      unsigned int first_vertex = primitive->get_first_vertex();
       
-      start = ends[i];
+      unsigned int start = 0;
+      for (size_t i = 0; i < ends.size(); i++) {
+        _vertices_trifan_pcollector.add_level(ends[i] - start);
+        _pD3DDevice->DrawPrimitiveUP
+          (D3DPT_TRIANGLEFAN, 
+           ends[i] - start - 2,
+           array_data + (first_vertex + start) * stride, stride);
+        
+        start = ends[i];
+      }
     }
   }
 }
@@ -2925,29 +3042,51 @@ void DXGraphicsStateGuardian8::
 draw_lines(const qpGeomLines *primitive) {
   _vertices_other_pcollector.add_level(primitive->get_num_vertices());
   _primitive_batches_other_pcollector.add_level(1);
-  if (_vbuffer_active) {
-    IndexBufferContext *ibc = ((qpGeomPrimitive *)primitive)->prepare_now(get_prepared_objects(), this);
-    nassertv(ibc != (IndexBufferContext *)NULL);
-    apply_index_buffer(ibc);
 
-    _pD3DDevice->DrawIndexedPrimitive
-      (D3DPT_LINELIST,
-       primitive->get_min_vertex(),
-       primitive->get_max_vertex() - primitive->get_min_vertex() + 1,
-       0, primitive->get_num_primitives());
+  if (primitive->is_indexed()) {
+    if (_vbuffer_active) {
+      // Indexed, vbuffers.
+      IndexBufferContext *ibc = ((qpGeomPrimitive *)primitive)->prepare_now(get_prepared_objects(), this);
+      nassertv(ibc != (IndexBufferContext *)NULL);
+      apply_index_buffer(ibc);
+      
+      _pD3DDevice->DrawIndexedPrimitive
+        (D3DPT_LINELIST,
+         primitive->get_min_vertex(),
+         primitive->get_max_vertex() - primitive->get_min_vertex() + 1,
+         0, primitive->get_num_primitives());
+      
+    } else {
+      // Indexed, client arrays.
+      D3DFORMAT index_type = get_index_type(primitive->get_index_type());
 
+      _pD3DDevice->DrawIndexedPrimitiveUP
+        (D3DPT_LINELIST, 
+         primitive->get_min_vertex(),
+         primitive->get_max_vertex() - primitive->get_min_vertex() + 1,
+         primitive->get_num_primitives(), 
+         primitive->get_data(),
+         index_type,
+         _vertex_data->get_array(0)->get_data(),
+         _vertex_data->get_format()->get_array(0)->get_stride());
+    }
   } else {
-    D3DFORMAT index_type = get_index_type(primitive->get_index_type());
-
-    _pD3DDevice->DrawIndexedPrimitiveUP
-      (D3DPT_LINELIST, 
-       primitive->get_min_vertex(),
-       primitive->get_max_vertex() - primitive->get_min_vertex() + 1,
-       primitive->get_num_primitives(), 
-       primitive->get_data(),
-       index_type,
-       _vertex_data->get_array(0)->get_data(),
-       _vertex_data->get_format()->get_array(0)->get_stride());
+    if (_vbuffer_active) {
+      // Nonindexed, vbuffers.
+      _pD3DDevice->DrawPrimitive
+        (D3DPT_LINELIST, primitive->get_first_vertex(), 
+         primitive->get_num_primitives());
+      
+    } else {
+      // Nonindexed, client arrays.
+      int stride = _vertex_data->get_format()->get_array(0)->get_stride();
+      unsigned int first_vertex = primitive->get_first_vertex();
+      _pD3DDevice->DrawPrimitiveUP
+        (D3DPT_LINELIST, 
+         primitive->get_num_primitives(), 
+         _vertex_data->get_array(0)->get_data() + stride * first_vertex, 
+         stride);
+    }
   }
 }
 

+ 107 - 61
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -2489,14 +2489,21 @@ void CLP(GraphicsStateGuardian)::
 draw_triangles(const qpGeomTriangles *primitive) {
   _vertices_tri_pcollector.add_level(primitive->get_num_vertices());
   _primitive_batches_tri_pcollector.add_level(1);
-  const unsigned char *client_pointer = setup_primitive(primitive);
 
-  _glDrawRangeElements(GL_TRIANGLES, 
-                       primitive->get_min_vertex(),
-                       primitive->get_max_vertex(),
-                       primitive->get_num_vertices(),
-                       get_numeric_type(primitive->get_index_type()), 
-                       client_pointer);
+  if (primitive->is_indexed()) {
+    const unsigned char *client_pointer = setup_primitive(primitive);
+    
+    _glDrawRangeElements(GL_TRIANGLES, 
+                         primitive->get_min_vertex(),
+                         primitive->get_max_vertex(),
+                         primitive->get_num_vertices(),
+                         get_numeric_type(primitive->get_index_type()), 
+                         client_pointer);
+  } else {
+    GLP(DrawArrays)(GL_TRIANGLES,
+                    primitive->get_first_vertex(),
+                    primitive->get_num_vertices());
+  }
 
   report_my_gl_errors();
 }
@@ -2508,41 +2515,58 @@ draw_triangles(const qpGeomTriangles *primitive) {
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
 draw_tristrips(const qpGeomTristrips *primitive) {
-  const unsigned char *client_pointer = setup_primitive(primitive);
-
   if (connect_triangle_strips && _render_mode != RenderModeAttrib::M_wireframe) {
     // One long triangle strip, connected by the degenerate vertices
     // that have already been set up within the primitive.
     _vertices_tristrip_pcollector.add_level(primitive->get_num_vertices());
     _primitive_batches_tristrip_pcollector.add_level(1);
-    _glDrawRangeElements(GL_TRIANGLE_STRIP, 
-                         primitive->get_min_vertex(),
-                         primitive->get_max_vertex(),
-                         primitive->get_num_vertices(),
-                         get_numeric_type(primitive->get_index_type()), 
-                         client_pointer);
+    if (primitive->is_indexed()) {
+      const unsigned char *client_pointer = setup_primitive(primitive);
+      _glDrawRangeElements(GL_TRIANGLE_STRIP, 
+                           primitive->get_min_vertex(),
+                           primitive->get_max_vertex(),
+                           primitive->get_num_vertices(),
+                           get_numeric_type(primitive->get_index_type()), 
+                           client_pointer);
+    } else {
+      GLP(DrawArrays)(GL_TRIANGLE_STRIP,
+                      primitive->get_first_vertex(),
+                      primitive->get_num_vertices());
+    }
 
   } else {
     // Send the individual triangle strips, stepping over the
     // degenerate vertices.
     CPTA_int ends = primitive->get_ends();
-    int index_stride = primitive->get_index_stride();
-
-    qpGeomVertexReader mins(primitive->get_mins(), 0);
-    qpGeomVertexReader maxs(primitive->get_maxs(), 0);
-    nassertv(primitive->get_mins()->get_num_rows() == (int)ends.size() && 
-             primitive->get_maxs()->get_num_rows() == (int)ends.size());
     
     _primitive_batches_tristrip_pcollector.add_level(ends.size());
-    unsigned int start = 0;
-    for (size_t i = 0; i < ends.size(); i++) {
-      _vertices_tristrip_pcollector.add_level(ends[i] - start);
-      _glDrawRangeElements(GL_TRIANGLE_STRIP, 
-                           mins.get_data1i(), maxs.get_data1i(), 
-                           ends[i] - start,
-                           get_numeric_type(primitive->get_index_type()), 
-                           client_pointer + start * index_stride);
-      start = ends[i] + 2;
+    if (primitive->is_indexed()) {
+      const unsigned char *client_pointer = setup_primitive(primitive);
+      int index_stride = primitive->get_index_stride();
+      qpGeomVertexReader mins(primitive->get_mins(), 0);
+      qpGeomVertexReader maxs(primitive->get_maxs(), 0);
+      nassertv(primitive->get_mins()->get_num_rows() == (int)ends.size() && 
+               primitive->get_maxs()->get_num_rows() == (int)ends.size());
+
+      unsigned int start = 0;
+      for (size_t i = 0; i < ends.size(); i++) {
+        _vertices_tristrip_pcollector.add_level(ends[i] - start);
+        _glDrawRangeElements(GL_TRIANGLE_STRIP, 
+                             mins.get_data1i(), maxs.get_data1i(), 
+                             ends[i] - start,
+                             get_numeric_type(primitive->get_index_type()), 
+                             client_pointer + start * index_stride);
+        start = ends[i] + 2;
+      }
+    } else {
+      unsigned int start = 0;
+      int first_vertex = primitive->get_first_vertex();
+      for (size_t i = 0; i < ends.size(); i++) {
+        _vertices_tristrip_pcollector.add_level(ends[i] - start);
+        GLP(DrawArrays)(GL_TRIANGLE_STRIP, first_vertex + start, 
+                        ends[i] - start);
+        start = ends[i] + 2;
+      }
     }
   }
     
@@ -2556,27 +2580,37 @@ draw_tristrips(const qpGeomTristrips *primitive) {
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
 draw_trifans(const qpGeomTrifans *primitive) {
-  const unsigned char *client_pointer = setup_primitive(primitive);
-
   // Send the individual triangle fans.  There's no connecting fans
   // with degenerate vertices, so no worries about that.
   CPTA_int ends = primitive->get_ends();
-  int index_stride = primitive->get_index_stride();
-
-  qpGeomVertexReader mins(primitive->get_mins(), 0);
-  qpGeomVertexReader maxs(primitive->get_maxs(), 0);
-  nassertv(primitive->get_mins()->get_num_rows() == (int)ends.size() && 
-           primitive->get_maxs()->get_num_rows() == (int)ends.size());
 
   _primitive_batches_trifan_pcollector.add_level(ends.size());
-  unsigned int start = 0;
-  for (size_t i = 0; i < ends.size(); i++) {
-    _vertices_trifan_pcollector.add_level(ends[i] - start);
-    _glDrawRangeElements(GL_TRIANGLE_FAN, 
-                         mins.get_data1i(), maxs.get_data1i(), ends[i] - start,
-                         get_numeric_type(primitive->get_index_type()), 
-                         client_pointer + start * index_stride);
-    start = ends[i];
+  if (primitive->is_indexed()) {
+    const unsigned char *client_pointer = setup_primitive(primitive);
+    int index_stride = primitive->get_index_stride();
+    qpGeomVertexReader mins(primitive->get_mins(), 0);
+    qpGeomVertexReader maxs(primitive->get_maxs(), 0);
+    nassertv(primitive->get_mins()->get_num_rows() == (int)ends.size() && 
+             primitive->get_maxs()->get_num_rows() == (int)ends.size());
+
+    unsigned int start = 0;
+    for (size_t i = 0; i < ends.size(); i++) {
+      _vertices_trifan_pcollector.add_level(ends[i] - start);
+      _glDrawRangeElements(GL_TRIANGLE_FAN, 
+                           mins.get_data1i(), maxs.get_data1i(), ends[i] - start,
+                           get_numeric_type(primitive->get_index_type()), 
+                           client_pointer + start * index_stride);
+      start = ends[i];
+    }
+  } else {
+    unsigned int start = 0;
+    int first_vertex = primitive->get_first_vertex();
+    for (size_t i = 0; i < ends.size(); i++) {
+      _vertices_trifan_pcollector.add_level(ends[i] - start);
+      GLP(DrawArrays)(GL_TRIANGLE_FAN, first_vertex + start,
+                      ends[i] - start);
+      start = ends[i];
+    }
   }
     
   report_my_gl_errors();
@@ -2591,14 +2625,20 @@ void CLP(GraphicsStateGuardian)::
 draw_lines(const qpGeomLines *primitive) {
   _vertices_other_pcollector.add_level(primitive->get_num_vertices());
   _primitive_batches_other_pcollector.add_level(1);
-  const unsigned char *client_pointer = setup_primitive(primitive);
 
-  _glDrawRangeElements(GL_LINES, 
-                       primitive->get_min_vertex(),
-                       primitive->get_max_vertex(),
-                       primitive->get_num_vertices(),
-                       get_numeric_type(primitive->get_index_type()), 
-                       client_pointer);
+  if (primitive->is_indexed()) {
+    const unsigned char *client_pointer = setup_primitive(primitive);
+    _glDrawRangeElements(GL_LINES, 
+                         primitive->get_min_vertex(),
+                         primitive->get_max_vertex(),
+                         primitive->get_num_vertices(),
+                         get_numeric_type(primitive->get_index_type()), 
+                         client_pointer);
+  } else {
+    GLP(DrawArrays)(GL_LINES,
+                    primitive->get_first_vertex(),
+                    primitive->get_num_vertices());
+  }
 
   report_my_gl_errors();
 }
@@ -2621,14 +2661,20 @@ void CLP(GraphicsStateGuardian)::
 draw_points(const qpGeomPoints *primitive) {
   _vertices_other_pcollector.add_level(primitive->get_num_vertices());
   _primitive_batches_other_pcollector.add_level(1);
-  const unsigned char *client_pointer = setup_primitive(primitive);
-
-  _glDrawRangeElements(GL_POINTS, 
-                       primitive->get_min_vertex(),
-                       primitive->get_max_vertex(),
-                       primitive->get_num_vertices(),
-                       get_numeric_type(primitive->get_index_type()), 
-                       client_pointer);
+
+  if (primitive->is_indexed()) {
+    const unsigned char *client_pointer = setup_primitive(primitive);
+    _glDrawRangeElements(GL_POINTS, 
+                         primitive->get_min_vertex(),
+                         primitive->get_max_vertex(),
+                         primitive->get_num_vertices(),
+                         get_numeric_type(primitive->get_index_type()), 
+                         client_pointer);
+  } else {
+    GLP(DrawArrays)(GL_POINTS,
+                    primitive->get_first_vertex(),
+                    primitive->get_num_vertices());
+  }
 
   report_my_gl_errors();
 }

+ 1 - 0
panda/src/gobj/qpgeom.I

@@ -70,6 +70,7 @@ get_usage_hint() const {
   if (!cdata->_got_usage_hint) {
     CDWriter cdataw(((qpGeom *)this)->_cycler, cdata);
     ((qpGeom *)this)->reset_usage_hint(cdataw);
+    return cdataw->_usage_hint;
   }
   return cdata->_usage_hint;
 }

+ 60 - 3
panda/src/gobj/qpgeom.cxx

@@ -200,12 +200,69 @@ offset_vertices(const qpGeomVertexData *data, int offset) {
   }
 
   cdata->_modified = qpGeom::get_next_modified();
-  mark_bound_stale();
-  reset_geom_rendering(cdata);
-
   nassertv(all_is_valid);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeom::make_nonindexed
+//       Access: Published
+//  Description: Converts the geom from indexed to nonindexed by
+//               duplicating vertices as necessary.  If composite_only
+//               is true, then only composite primitives such as
+//               trifans and tristrips are converted.  Returns the
+//               number of GeomPrimitive objects converted.
+////////////////////////////////////////////////////////////////////
+int qpGeom::
+make_nonindexed(bool composite_only) {
+  int num_changed = 0;
+
+  clear_cache();
+  CDWriter cdata(_cycler);
+  CPT(qpGeomVertexData) orig_data = cdata->_data;
+  PT(qpGeomVertexData) new_data = new qpGeomVertexData(*cdata->_data);
+  new_data->clear_rows();
+
+#ifndef NDEBUG
+  bool all_is_valid = true;
+#endif
+  Primitives::iterator pi;
+  Primitives new_prims;
+  new_prims.reserve(cdata->_primitives.size());
+  for (pi = cdata->_primitives.begin(); pi != cdata->_primitives.end(); ++pi) {
+    PT(qpGeomPrimitive) primitive = (*pi)->make_copy();
+    new_prims.push_back(primitive);
+
+    if (primitive->is_indexed() && 
+        (primitive->is_composite() || !composite_only)) {
+      primitive->make_nonindexed(new_data, orig_data);
+      ++num_changed;
+    } else {
+      // If it's a simple primitive, pack it anyway, so it can share
+      // the same GeomVertexData.
+      primitive->pack_vertices(new_data, orig_data);
+    }
+
+#ifndef NDEBUG
+    if (!primitive->check_valid(new_data)) {
+      all_is_valid = false;
+    }
+#endif
+  }
+
+  nassertr(all_is_valid, 0);
+
+  if (num_changed != 0) {
+    // If any at all were changed, then keep the result (otherwise,
+    // discard it, since we might have de-optimized the indexed
+    // geometry a bit).
+    cdata->_data = new_data;
+    cdata->_primitives.swap(new_prims);
+    cdata->_modified = qpGeom::get_next_modified();
+  }
+
+  return num_changed;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeom::set_primitive
 //       Access: Published

+ 1 - 0
panda/src/gobj/qpgeom.h

@@ -78,6 +78,7 @@ PUBLISHED:
   PT(qpGeomVertexData) modify_vertex_data();
   void set_vertex_data(const qpGeomVertexData *data);
   void offset_vertices(const qpGeomVertexData *data, int offset);
+  int make_nonindexed(bool composite_only);
 
   INLINE int get_num_primitives() const;
   INLINE const qpGeomPrimitive *get_primitive(int i) const;

+ 29 - 14
panda/src/gobj/qpgeomLines.cxx

@@ -124,23 +124,38 @@ draw(GraphicsStateGuardianBase *gsg) const {
 CPT(qpGeomVertexArrayData) qpGeomLines::
 rotate_impl() const {
   // To rotate lines, we just move reverse the pairs of vertices.
-  CPT(qpGeomVertexArrayData) vertices = get_vertices();
-
-  PT(qpGeomVertexArrayData) new_vertices = 
-    new qpGeomVertexArrayData(*vertices);
-  qpGeomVertexReader from(vertices, 0);
-  qpGeomVertexWriter to(new_vertices, 0);
+  int num_vertices = get_num_vertices();
+    
+  PT(qpGeomVertexArrayData) new_vertices = make_index_data();
+  new_vertices->set_num_rows(num_vertices);
+
+  if (is_indexed()) {
+    CPT(qpGeomVertexArrayData) vertices = get_vertices();
+    qpGeomVertexReader from(vertices, 0);
+    qpGeomVertexWriter to(new_vertices, 0);
+
+    for (int begin = 0; begin < num_vertices; begin += 2) {
+      from.set_row(begin + 1);
+      to.set_data1i(from.get_data1i());
+      from.set_row(begin);
+      to.set_data1i(from.get_data1i());
+    }
+  
+    nassertr(to.is_at_end(), NULL);
 
-  int num_vertices = vertices->get_num_rows();
+  } else {
+    // Nonindexed case.
+    int first_vertex = get_first_vertex();
+    qpGeomVertexWriter to(new_vertices, 0);
 
-  for (int begin = 0; begin < num_vertices; begin += 2) {
-    from.set_row(begin + 1);
-    to.set_data1i(from.get_data1i());
-    from.set_row(begin);
-    to.set_data1i(from.get_data1i());
-  }
+    for (int begin = 0; begin < num_vertices; begin += 2) {
+      to.set_data1i(begin + 1 + first_vertex);
+      to.set_data1i(begin + first_vertex);
+    }
   
-  nassertr(to.is_at_end(), NULL);
+    nassertr(to.is_at_end(), NULL);
+  }
+    
   return new_vertices;
 }
 

+ 36 - 16
panda/src/gobj/qpgeomLinestrips.cxx

@@ -166,25 +166,45 @@ decompose_impl() const {
 CPT(qpGeomVertexArrayData) qpGeomLinestrips::
 rotate_impl() const {
   // To rotate a line strip, we just reverse the vertices.
-  CPT(qpGeomVertexArrayData) vertices = get_vertices();
   CPTA_int ends = get_ends();
-  PT(qpGeomVertexArrayData) new_vertices = 
-    new qpGeomVertexArrayData(*vertices);
-  qpGeomVertexReader from(vertices, 0);
-  qpGeomVertexWriter to(new_vertices, 0);
-
-  int begin = 0;
-  CPTA_int::const_iterator ei;
-  for (ei = ends.begin(); ei != ends.end(); ++ei) {
-    int end = (*ei);
-    for (int vi = end - 1; vi >= begin; --vi) {
-      from.set_row(vi);
-      to.set_data1i(from.get_data1i());
+  PT(qpGeomVertexArrayData) new_vertices = make_index_data();
+  new_vertices->set_num_rows(get_num_vertices());
+
+  if (is_indexed()) {
+    CPT(qpGeomVertexArrayData) vertices = get_vertices();
+    qpGeomVertexReader from(vertices, 0);
+    qpGeomVertexWriter to(new_vertices, 0);
+    
+    int begin = 0;
+    CPTA_int::const_iterator ei;
+    for (ei = ends.begin(); ei != ends.end(); ++ei) {
+      int end = (*ei);
+      for (int vi = end - 1; vi >= begin; --vi) {
+        from.set_row(vi);
+        to.set_data1i(from.get_data1i());
+      }
+      begin = end;
+    }
+    
+    nassertr(to.is_at_end(), NULL);
+
+  } else {
+    // Nonindexed case.
+    int first_vertex = get_first_vertex();
+    qpGeomVertexWriter to(new_vertices, 0);
+    
+    int begin = 0;
+    CPTA_int::const_iterator ei;
+    for (ei = ends.begin(); ei != ends.end(); ++ei) {
+      int end = (*ei);
+      for (int vi = end - 1; vi >= begin; --vi) {
+        to.set_data1i(vi + first_vertex);
+      }
+      begin = end;
     }
-    begin = end;
+    
+    nassertr(to.is_at_end(), NULL);
   }
-
-  nassertr(to.is_at_end(), NULL);
   return new_vertices;
 }
 

+ 73 - 14
panda/src/gobj/qpgeomPrimitive.I

@@ -65,18 +65,8 @@ set_shade_model(qpGeomPrimitive::ShadeModel shade_model) {
 ////////////////////////////////////////////////////////////////////
 INLINE qpGeomPrimitive::UsageHint qpGeomPrimitive::
 get_usage_hint() const {
-  return get_vertices()->get_usage_hint();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: qpGeomPrimitive::set_usage_hint
-//       Access: Published
-//  Description: Changes the UsageHint hint for this primitive.  See
-//               get_usage_hint().
-////////////////////////////////////////////////////////////////////
-INLINE void qpGeomPrimitive::
-set_usage_hint(qpGeomPrimitive::UsageHint usage_hint) {
-  modify_vertices()->set_usage_hint(usage_hint);
+  CDReader cdata(_cycler);
+  return cdata->_usage_hint;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -91,6 +81,36 @@ get_index_type() const {
   return cdata->_index_type;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::is_composite
+//       Access: Published
+//  Description: Returns true if the primitive is a composite
+//               primitive such as a tristrip or trifan, or false if
+//               it is a fundamental primitive such as a collection of
+//               triangles.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpGeomPrimitive::
+is_composite() const {
+  return (get_num_vertices_per_primitive() == 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::is_indexed
+//       Access: Published
+//  Description: Returns true if the primitive is indexed, false
+//               otherwise.  An indexed primitive stores a table of
+//               index numbers into its GeomVertexData, so that it can
+//               reference the vertices in any order.  A nonindexed
+//               primitive, on the other hand, stores only the first
+//               vertex number and number of vertices used, so that it
+//               can only reference the vertices consecutively.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpGeomPrimitive::
+is_indexed() const {
+  CDReader cdata(_cycler);
+  return (cdata->_vertices != (qpGeomVertexArrayData *)NULL);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::get_num_vertices
 //       Access: Published
@@ -100,7 +120,11 @@ get_index_type() const {
 INLINE int qpGeomPrimitive::
 get_num_vertices() const {
   CDReader cdata(_cycler);
-  return cdata->_vertices->get_num_rows();
+  if (cdata->_vertices == (qpGeomVertexArrayData *)NULL) {
+    return cdata->_num_vertices;
+  } else {
+    return cdata->_vertices->get_num_rows();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -157,6 +181,7 @@ get_min_vertex() const {
     ((qpGeomPrimitive *)this)->recompute_minmax(cdataw);
     return cdataw->_min_vertex;
   }
+  
   return cdata->_min_vertex;
 }
 
@@ -206,7 +231,8 @@ get_modified() const {
 //     Function: qpGeomPrimitive::get_vertices
 //       Access: Public
 //  Description: Returns a const pointer to the vertex index array so
-//               application code can read it directly.  Do not
+//               application code can read it directly.  This might
+//               return NULL if the primitive is nonindexed.  Do not
 //               attempt to modify the returned array; use
 //               modify_vertices() or set_vertices() for this.
 ////////////////////////////////////////////////////////////////////
@@ -225,6 +251,7 @@ get_vertices() const {
 ////////////////////////////////////////////////////////////////////
 INLINE int qpGeomPrimitive::
 get_index_stride() const {
+  nassertr(is_indexed(), 0);
   return get_vertices()->get_array_format()->get_stride();
 }
 
@@ -236,6 +263,7 @@ get_index_stride() const {
 ////////////////////////////////////////////////////////////////////
 INLINE CPTA_uchar qpGeomPrimitive::
 get_data() const {
+  nassertr(is_indexed(), CPTA_uchar());
   return get_vertices()->get_data();
 }
 
@@ -270,6 +298,7 @@ get_ends() const {
 ////////////////////////////////////////////////////////////////////
 INLINE const qpGeomVertexArrayData *qpGeomPrimitive::
 get_mins() const {
+  nassertr(is_indexed(), NULL);
   CDReader cdata(_cycler);
   if (!cdata->_got_minmax) {
     CDWriter cdataw(((qpGeomPrimitive *)this)->_cycler, cdata);
@@ -292,6 +321,7 @@ get_mins() const {
 ////////////////////////////////////////////////////////////////////
 INLINE const qpGeomVertexArrayData *qpGeomPrimitive::
 get_maxs() const {
+  nassertr(is_indexed(), NULL);
   CDReader cdata(_cycler);
   if (!cdata->_got_minmax) {
     CDWriter cdataw(((qpGeomPrimitive *)this)->_cycler, cdata);
@@ -301,6 +331,29 @@ get_maxs() const {
   return cdata->_maxs;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::get_index_format
+//       Access: Protected
+//  Description: Returns a registered format appropriate for using to
+//               store the index table.
+////////////////////////////////////////////////////////////////////
+INLINE CPT(qpGeomVertexArrayFormat) qpGeomPrimitive::
+get_index_format() const {
+  return qpGeomVertexArrayFormat::register_format
+    (new qpGeomVertexArrayFormat(InternalName::get_index(), 1, 
+                                 get_index_type(), C_index));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::make_index_data
+//       Access: Protected
+//  Description: Creates and returns a new, empty index table.
+////////////////////////////////////////////////////////////////////
+INLINE PT(qpGeomVertexArrayData) qpGeomPrimitive::
+make_index_data() const {
+  return new qpGeomVertexArrayData(get_index_format(), get_usage_hint());
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::CData::Constructor
 //       Access: Public
@@ -309,7 +362,10 @@ get_maxs() const {
 INLINE qpGeomPrimitive::CData::
 CData() :
   _shade_model(SM_smooth),
+  _first_vertex(0),
+  _num_vertices(0),
   _index_type(NT_uint16),
+  _usage_hint(UH_unspecified),
   _got_minmax(true),
   _min_vertex(0),
   _max_vertex(0)
@@ -324,7 +380,10 @@ CData() :
 INLINE qpGeomPrimitive::CData::
 CData(const qpGeomPrimitive::CData &copy) :
   _shade_model(copy._shade_model),
+  _first_vertex(copy._first_vertex),
+  _num_vertices(copy._num_vertices),
   _index_type(copy._index_type),
+  _usage_hint(copy._usage_hint),
   _vertices(copy._vertices),
   _ends(copy._ends),
   _mins(copy._mins),

+ 420 - 90
panda/src/gobj/qpgeomPrimitive.cxx

@@ -53,13 +53,7 @@ qpGeomPrimitive() {
 qpGeomPrimitive::
 qpGeomPrimitive(qpGeomPrimitive::UsageHint usage_hint) {
   CDWriter cdata(_cycler);
-
-  CPT(qpGeomVertexArrayFormat) new_format =
-    qpGeomVertexArrayFormat::register_format
-    (new qpGeomVertexArrayFormat(InternalName::get_index(), 1, 
-                                 cdata->_index_type, C_index));
-
-  cdata->_vertices = new qpGeomVertexArrayData(new_format, usage_hint);
+  cdata->_usage_hint = usage_hint;
 }
  
 ////////////////////////////////////////////////////////////////////
@@ -97,6 +91,27 @@ get_geom_rendering() const {
   return 0;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::set_usage_hint
+//       Access: Published
+//  Description: Changes the UsageHint hint for this primitive.  See
+//               get_usage_hint().
+////////////////////////////////////////////////////////////////////
+void qpGeomPrimitive::
+set_usage_hint(qpGeomPrimitive::UsageHint usage_hint) {
+  CDWriter cdata(_cycler);
+  cdata->_usage_hint = usage_hint;
+
+  if (cdata->_vertices != (qpGeomVertexArrayData *)NULL) {
+    if (cdata->_vertices->get_ref_count() > 1) {
+      cdata->_vertices = new qpGeomVertexArrayData(*cdata->_vertices);
+    }
+    
+    cdata->_modified = qpGeom::get_next_modified();
+    cdata->_usage_hint = usage_hint;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::set_index_type
 //       Access: Published
@@ -108,23 +123,45 @@ void qpGeomPrimitive::
 set_index_type(qpGeomPrimitive::NumericType index_type) {
   CDWriter cdata(_cycler);
   cdata->_index_type = index_type;
-  
-  CPT(qpGeomVertexArrayFormat) new_format =
-    qpGeomVertexArrayFormat::register_format
-    (new qpGeomVertexArrayFormat(InternalName::get_index(), 1, index_type, 
-                                 C_index));
-
-  if (cdata->_vertices->get_array_format() != new_format) {
-    PT(qpGeomVertexArrayData) new_vertices = 
-      new qpGeomVertexArrayData(new_format, cdata->_vertices->get_usage_hint());
-    qpGeomVertexReader from(cdata->_vertices, 0);
-    qpGeomVertexWriter to(new_vertices, 0);
-
-    while (!from.is_at_end()) {
-      to.add_data1i(from.get_data1i());
+
+  if (cdata->_vertices != (qpGeomVertexArrayData *)NULL) {
+    CPT(qpGeomVertexArrayFormat) new_format = get_index_format();
+    
+    if (cdata->_vertices->get_array_format() != new_format) {
+      PT(qpGeomVertexArrayData) new_vertices = make_index_data();
+      new_vertices->set_num_rows(cdata->_vertices->get_num_rows());
+
+      qpGeomVertexReader from(cdata->_vertices, 0);
+      qpGeomVertexWriter to(new_vertices, 0);
+      
+      while (!from.is_at_end()) {
+        to.set_data1i(from.get_data1i());
+      }
+      cdata->_vertices = new_vertices;
+      cdata->_got_minmax = false;
     }
-    cdata->_vertices = new_vertices;
-    cdata->_got_minmax = false;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::get_first_vertex
+//       Access: Published
+//  Description: Returns the first vertex number referenced by the
+//               primitive.  This is particularly important in the
+//               case of a nonindexed primitive, in which case
+//               get_first_vertex() and get_num_vertices() completely
+//               define the extent of the vertex range.
+////////////////////////////////////////////////////////////////////
+int qpGeomPrimitive::
+get_first_vertex() const {
+  CDReader cdata(_cycler);
+  if (cdata->_vertices == (qpGeomVertexArrayData *)NULL) {
+    return cdata->_first_vertex;
+  } else if (cdata->_vertices->get_num_rows() == 0) {
+    return 0;
+  } else {
+    qpGeomVertexReader index(cdata->_vertices, 0);
+    return index.get_data1i();
   }
 }
 
@@ -136,11 +173,19 @@ set_index_type(qpGeomPrimitive::NumericType index_type) {
 int qpGeomPrimitive::
 get_vertex(int i) const {
   CDReader cdata(_cycler);
-  nassertr(i >= 0 && i < (int)cdata->_vertices->get_num_rows(), -1);
 
-  qpGeomVertexReader index(cdata->_vertices, 0);
-  index.set_row(i);
-  return index.get_data1i();
+  if (cdata->_vertices != (qpGeomVertexArrayData *)NULL) {
+    // The indexed case.
+    nassertr(i >= 0 && i < (int)cdata->_vertices->get_num_rows(), -1);
+
+    qpGeomVertexReader index(cdata->_vertices, 0);
+    index.set_row(i);
+    return index.get_data1i();
+
+  } else {
+    // The nonindexed case.
+    return cdata->_first_vertex + i;
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -159,12 +204,33 @@ add_vertex(int vertex) {
 
   int num_primitives = get_num_primitives();
   if (num_primitives > 0 &&
-      (int)cdata->_vertices->get_num_rows() == get_primitive_end(num_primitives - 1)) {
+      requires_unused_vertices() && 
+      get_num_vertices() == get_primitive_end(num_primitives - 1)) {
     // If we are beginning a new primitive, give the derived class a
     // chance to insert some degenerate vertices.
+    if (cdata->_vertices == (qpGeomVertexArrayData *)NULL) {
+      do_make_indexed(cdata);
+    }
     append_unused_vertices(cdata->_vertices, vertex);
   }
 
+  if (cdata->_vertices == (qpGeomVertexArrayData *)NULL) {
+    // The nonindexed case.  We can keep the primitive nonindexed only
+    // if the vertex number happens to be the next available vertex.
+    if (cdata->_num_vertices == 0) {
+      cdata->_first_vertex = vertex;
+      cdata->_num_vertices = 1;
+      return;
+
+    } else if (vertex == cdata->_first_vertex + cdata->_num_vertices) {
+      ++cdata->_num_vertices;
+      return;
+    }
+    
+    // Otherwise, we need to suddenly become an indexed primitive.
+    do_make_indexed(cdata);
+  }
+
   qpGeomVertexWriter index(cdata->_vertices, 0);
   index.set_row(cdata->_vertices->get_num_rows());
 
@@ -191,12 +257,32 @@ add_consecutive_vertices(int start, int num_vertices) {
 
   int num_primitives = get_num_primitives();
   if (num_primitives > 0 &&
-      (int)cdata->_vertices->get_num_rows() == get_primitive_end(num_primitives - 1)) {
+      get_num_vertices() == get_primitive_end(num_primitives - 1)) {
     // If we are beginning a new primitive, give the derived class a
     // chance to insert some degenerate vertices.
+    if (cdata->_vertices == (qpGeomVertexArrayData *)NULL) {
+      do_make_indexed(cdata);
+    }
     append_unused_vertices(cdata->_vertices, start);
   }
 
+  if (cdata->_vertices == (qpGeomVertexArrayData *)NULL) {
+    // The nonindexed case.  We can keep the primitive nonindexed only
+    // if the vertex number happens to be the next available vertex.
+    if (cdata->_num_vertices == 0) {
+      cdata->_first_vertex = start;
+      cdata->_num_vertices = num_vertices;
+      return;
+
+    } else if (start == cdata->_first_vertex + cdata->_num_vertices) {
+      cdata->_num_vertices += num_vertices;
+      return;
+    }
+    
+    // Otherwise, we need to suddenly become an indexed primitive.
+    do_make_indexed(cdata);
+  }
+
   qpGeomVertexWriter index(cdata->_vertices, 0);
   index.set_row(cdata->_vertices->get_num_rows());
 
@@ -247,14 +333,14 @@ close_primitive() {
 #ifndef NDEBUG
     int num_added;
     if (cdata->_ends.empty()) {
-      num_added = (int)cdata->_vertices->get_num_rows();
+      num_added = get_num_vertices();
     } else {
-      num_added = (int)cdata->_vertices->get_num_rows() - cdata->_ends.back();
+      num_added = get_num_vertices() - cdata->_ends.back();
       num_added -= get_num_unused_vertices_per_primitive();
     }
     nassertr(num_added >= get_min_num_vertices_per_primitive(), false);
 #endif
-    cdata->_ends.push_back((int)cdata->_vertices->get_num_rows());
+    cdata->_ends.push_back(get_num_vertices());
 
   } else {
 #ifndef NDEBUG
@@ -264,7 +350,7 @@ close_primitive() {
     int num_vertices_per_primitive = get_num_vertices_per_primitive();
     int num_unused_vertices_per_primitive = get_num_unused_vertices_per_primitive();
 
-    int num_vertices = cdata->_vertices->get_num_rows();
+    int num_vertices = get_num_vertices();
     nassertr((num_vertices + num_unused_vertices_per_primitive) % (num_vertices_per_primitive + num_unused_vertices_per_primitive) == 0, false)
 #endif
   }
@@ -283,8 +369,9 @@ close_primitive() {
 void qpGeomPrimitive::
 clear_vertices() {
   CDWriter cdata(_cycler);
-  cdata->_vertices = new qpGeomVertexArrayData
-    (cdata->_vertices->get_array_format(), cdata->_vertices->get_usage_hint());
+  cdata->_first_vertex = 0;
+  cdata->_num_vertices = 0;
+  cdata->_vertices.clear();
   cdata->_ends.clear();
   cdata->_mins.clear();
   cdata->_maxs.clear();
@@ -300,12 +387,103 @@ clear_vertices() {
 ////////////////////////////////////////////////////////////////////
 void qpGeomPrimitive::
 offset_vertices(int offset) {
-  qpGeomVertexRewriter index(modify_vertices(), 0);
-  while (!index.is_at_end()) {
-    index.set_data1i(index.get_data1i() + offset);
+  if (is_indexed()) {
+    qpGeomVertexRewriter index(modify_vertices(), 0);
+    while (!index.is_at_end()) {
+      index.set_data1i(index.get_data1i() + offset);
+    }
+
+  } else {
+    CDWriter cdata(_cycler);
+    cdata->_first_vertex += offset;
+    cdata->_modified = qpGeom::get_next_modified();
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::make_nonindexed
+//       Access: Published
+//  Description: Converts the primitive from indexed to nonindexed by
+//               duplicating vertices as necessary into the indicated
+//               dest GeomVertexData.
+////////////////////////////////////////////////////////////////////
+void qpGeomPrimitive::
+make_nonindexed(qpGeomVertexData *dest, const qpGeomVertexData *source) {
+  int num_vertices = get_num_vertices();
+  int dest_start = dest->get_num_rows();
+
+  dest->set_num_rows(dest_start + num_vertices);
+  for (int i = 0; i < num_vertices; ++i) {
+    int v = get_vertex(i);
+    dest->copy_row_from(dest_start + i, source, v);
+  }
+
+  set_nonindexed_vertices(dest_start, num_vertices);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::pack_vertices
+//       Access: Published
+//  Description: Packs the vertices used by the primitive from the
+//               indicated source array onto the end of the indicated
+//               destination array.
+////////////////////////////////////////////////////////////////////
+void qpGeomPrimitive::
+pack_vertices(qpGeomVertexData *dest, const qpGeomVertexData *source) {
+  if (!is_indexed()) {
+    // If the primitive is nonindexed, packing is the same as
+    // converting (again) to nonindexed.
+    make_nonindexed(dest, source);
+
+  } else {
+    // The indexed case: build up a new index as we go.
+    CPT(qpGeomVertexArrayData) orig_vertices = get_vertices();
+    PT(qpGeomVertexArrayData) new_vertices = make_index_data();
+    qpGeomVertexWriter index(new_vertices, 0);
+    typedef pmap<int, int> CopiedIndices;
+    CopiedIndices copied_indices;
+
+    int num_vertices = get_num_vertices();
+    int dest_start = dest->get_num_rows();
+
+    for (int i = 0; i < num_vertices; ++i) {
+      int v = get_vertex(i);
+
+      // Try to add the relation { v : size() }.  If that succeeds,
+      // great; if it doesn't, look up whatever we previously added
+      // for v.
+      pair<CopiedIndices::iterator, bool> result = 
+        copied_indices.insert(CopiedIndices::value_type(v, (int)copied_indices.size()));
+      int v2 = (*result.first).second + dest_start;
+      index.add_data1i(v2);
+
+      if (result.second) {
+        // This is the first time we've seen vertex v.
+        dest->copy_row_from(v2, source, v);
+      }
+    }
+    
+    set_vertices(new_vertices);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::make_indexed
+//       Access: Published
+//  Description: Converts the primitive from nonindexed form to
+//               indexed form.  This will simply create an index table
+//               that is numbered consecutively from
+//               get_first_vertex(); it does not automatically
+//               collapse together identical vertices that may have
+//               been split apart by a previous call to
+//               make_nonindexed().
+////////////////////////////////////////////////////////////////////
+void qpGeomPrimitive::
+make_indexed() {
+  CDWriter cdata(_cycler);
+  do_make_indexed(cdata);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::get_num_primitives
 //       Access: Published
@@ -326,7 +504,7 @@ get_num_primitives() const {
   } else {
     // This is a simple primitive type like a triangle: each primitive
     // uses the same number of vertices.
-    return ((int)cdata->_vertices->get_num_rows() / num_vertices_per_primitive);
+    return (get_num_vertices() / num_vertices_per_primitive);
   }
 }
 
@@ -431,12 +609,16 @@ get_primitive_num_vertices(int n) const {
 ////////////////////////////////////////////////////////////////////
 int qpGeomPrimitive::
 get_primitive_min_vertex(int n) const {
-  CDReader cdata(_cycler);
-  nassertr(n >= 0 && n < (int)cdata->_mins->get_num_rows(), -1);
+  if (is_indexed()) {
+    CPT(qpGeomVertexArrayData) mins = get_mins();
+    nassertr(n >= 0 && n < mins->get_num_rows(), -1);
 
-  qpGeomVertexReader index(cdata->_mins, 0);
-  index.set_row(n);
-  return index.get_data1i();
+    qpGeomVertexReader index(mins, 0);
+    index.set_row(n);
+    return index.get_data1i();
+  } else {
+    return get_primitive_start(n);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -447,12 +629,16 @@ get_primitive_min_vertex(int n) const {
 ////////////////////////////////////////////////////////////////////
 int qpGeomPrimitive::
 get_primitive_max_vertex(int n) const {
-  CDReader cdata(_cycler);
-  nassertr(n >= 0 && n < (int)cdata->_maxs->get_num_rows(), -1);
+  if (is_indexed()) {
+    CPT(qpGeomVertexArrayData) maxs = get_maxs();
+    nassertr(n >= 0 && n < maxs->get_num_rows(), -1);
 
-  qpGeomVertexReader index(cdata->_maxs, 0);
-  index.set_row(n);
-  return index.get_data1i();
+    qpGeomVertexReader index(maxs, 0);
+    index.set_row(n);
+    return index.get_data1i();
+  } else {
+    return get_primitive_end(n) - 1;
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -520,8 +706,12 @@ rotate() const {
 int qpGeomPrimitive::
 get_num_bytes() const {
   CDReader cdata(_cycler);
-  return (cdata->_vertices->get_data_size_bytes() +
-    cdata->_ends.size() * sizeof(int) + sizeof(qpGeomPrimitive));
+  int num_bytes = cdata->_ends.size() * sizeof(int) + sizeof(qpGeomPrimitive);
+  if (cdata->_vertices != (qpGeomVertexArrayData *)NULL) {
+    num_bytes += cdata->_vertices->get_data_size_bytes();
+  }
+
+  return num_bytes;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -557,7 +747,13 @@ output(ostream &out) const {
 void qpGeomPrimitive::
 write(ostream &out, int indent_level) const {
   indent(out, indent_level)
-    << get_type() << ":\n";
+    << get_type();
+  if (is_indexed()) {
+    out << " (indexed)";
+  } else {
+    out << " (nonindexed)";
+  }
+  out << ":\n";
   int num_primitives = get_num_primitives();
   int num_vertices = get_num_vertices();
   int num_unused_vertices_per_primitive = get_num_unused_vertices_per_primitive();
@@ -590,10 +786,18 @@ write(ostream &out, int indent_level) const {
 //               list, so application code can directly fiddle with
 //               this data.  Use with caution, since there are no
 //               checks that the data will be left in a stable state.
+//
+//               If this is called on a nonindexed primitive, it will
+//               implicitly be converted to an indexed primitive.
 ////////////////////////////////////////////////////////////////////
 qpGeomVertexArrayData *qpGeomPrimitive::
 modify_vertices() {
   CDWriter cdata(_cycler);
+
+  if (cdata->_vertices == (qpGeomVertexArrayData *)NULL) {
+    do_make_indexed(cdata);
+  }
+
   if (cdata->_vertices->get_ref_count() > 1) {
     cdata->_vertices = new qpGeomVertexArrayData(*cdata->_vertices);
   }
@@ -619,6 +823,24 @@ set_vertices(const qpGeomVertexArrayData *vertices) {
   cdata->_got_minmax = false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::set_nonindexed_vertices
+//       Access: Public
+//  Description: Sets the primitive up as a nonindexed primitive,
+//               using the indicated vertex range.
+////////////////////////////////////////////////////////////////////
+void qpGeomPrimitive::
+set_nonindexed_vertices(int first_vertex, int num_vertices) {
+  CDWriter cdata(_cycler);
+  cdata->_vertices = (qpGeomVertexArrayData *)NULL;
+  cdata->_first_vertex = first_vertex;
+  cdata->_num_vertices = num_vertices;
+
+  cdata->_modified = qpGeom::get_next_modified();
+  cdata->_got_minmax = false;
+  recompute_minmax(cdata);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::modify_ends
 //       Access: Public
@@ -847,42 +1069,85 @@ calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point,
 
   CDReader cdata(_cycler);
 
-  qpGeomVertexReader index(cdata->_vertices, 0);
-
-  if (got_mat) {
-    while (!index.is_at_end()) {
-      reader.set_row(index.get_data1i());
-      const LVecBase3f &vertex = reader.get_data3f();
-      
-      if (found_any) {
-        min_point.set(min(min_point[0], vertex[0]),
-                      min(min_point[1], vertex[1]),
-                      min(min_point[2], vertex[2]));
-        max_point.set(max(max_point[0], vertex[0]),
-                      max(max_point[1], vertex[1]),
-                      max(max_point[2], vertex[2]));
-      } else {
-        min_point = vertex;
-        max_point = vertex;
-        found_any = true;
+  if (cdata->_vertices == (qpGeomVertexArrayData *)NULL) {
+    // Nonindexed case.
+    if (got_mat) {
+      for (int i = 0; i < cdata->_num_vertices; i++) {
+        reader.set_row(cdata->_first_vertex + i);
+        const LVecBase3f &vertex = reader.get_data3f();
+        
+        if (found_any) {
+          min_point.set(min(min_point[0], vertex[0]),
+                        min(min_point[1], vertex[1]),
+                        min(min_point[2], vertex[2]));
+          max_point.set(max(max_point[0], vertex[0]),
+                        max(max_point[1], vertex[1]),
+                        max(max_point[2], vertex[2]));
+        } else {
+          min_point = vertex;
+          max_point = vertex;
+          found_any = true;
+        }
+      }
+    } else {
+      for (int i = 0; i < cdata->_num_vertices; i++) {
+        reader.set_row(cdata->_first_vertex + i);
+        LPoint3f vertex = mat.xform_point(reader.get_data3f());
+        
+        if (found_any) {
+          min_point.set(min(min_point[0], vertex[0]),
+                        min(min_point[1], vertex[1]),
+                        min(min_point[2], vertex[2]));
+          max_point.set(max(max_point[0], vertex[0]),
+                        max(max_point[1], vertex[1]),
+                        max(max_point[2], vertex[2]));
+        } else {
+          min_point = vertex;
+          max_point = vertex;
+          found_any = true;
+        }
       }
     }
+
   } else {
-    while (!index.is_at_end()) {
-      reader.set_row(index.get_data1i());
-      LPoint3f vertex = mat.xform_point(reader.get_data3f());
-      
-      if (found_any) {
-        min_point.set(min(min_point[0], vertex[0]),
-                      min(min_point[1], vertex[1]),
-                      min(min_point[2], vertex[2]));
-        max_point.set(max(max_point[0], vertex[0]),
-                      max(max_point[1], vertex[1]),
-                      max(max_point[2], vertex[2]));
-      } else {
-        min_point = vertex;
-        max_point = vertex;
-        found_any = true;
+    // Indexed case.
+    qpGeomVertexReader index(cdata->_vertices, 0);
+
+    if (got_mat) {
+      while (!index.is_at_end()) {
+        reader.set_row(index.get_data1i());
+        const LVecBase3f &vertex = reader.get_data3f();
+        
+        if (found_any) {
+          min_point.set(min(min_point[0], vertex[0]),
+                        min(min_point[1], vertex[1]),
+                        min(min_point[2], vertex[2]));
+          max_point.set(max(max_point[0], vertex[0]),
+                        max(max_point[1], vertex[1]),
+                        max(max_point[2], vertex[2]));
+        } else {
+          min_point = vertex;
+          max_point = vertex;
+          found_any = true;
+        }
+      }
+    } else {
+      while (!index.is_at_end()) {
+        reader.set_row(index.get_data1i());
+        LPoint3f vertex = mat.xform_point(reader.get_data3f());
+        
+        if (found_any) {
+          min_point.set(min(min_point[0], vertex[0]),
+                        min(min_point[1], vertex[1]),
+                        min(min_point[2], vertex[2]));
+          max_point.set(max(max_point[0], vertex[0]),
+                        max(max_point[1], vertex[1]),
+                        max(max_point[2], vertex[2]));
+        } else {
+          min_point = vertex;
+          max_point = vertex;
+          found_any = true;
+        }
       }
     }
   }
@@ -919,6 +1184,17 @@ rotate_impl() const {
   return NULL;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::requires_unused_vertices
+//       Access: Protected, Virtual
+//  Description: Should be redefined to return true in any primitive
+//               that implements append_unused_vertices().
+////////////////////////////////////////////////////////////////////
+bool qpGeomPrimitive::
+requires_unused_vertices() const {
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::append_unused_vertices
 //       Access: Protected, Virtual
@@ -927,6 +1203,9 @@ rotate_impl() const {
 //               vertices between primitives, if the primitive type
 //               requires that.  The second parameter is the first
 //               vertex that begins the new primitive.
+//
+//               This method is only called if
+//               requires_unused_vertices(), above, returns true.
 ////////////////////////////////////////////////////////////////////
 void qpGeomPrimitive::
 append_unused_vertices(qpGeomVertexArrayData *, int) {
@@ -940,7 +1219,16 @@ append_unused_vertices(qpGeomVertexArrayData *, int) {
 ////////////////////////////////////////////////////////////////////
 void qpGeomPrimitive::
 recompute_minmax(qpGeomPrimitive::CDWriter &cdata) {
-  if (cdata->_vertices->get_num_rows() == 0) {
+  if (cdata->_vertices == (qpGeomVertexArrayData *)NULL) {
+    // In the nonindexed case, we don't need to do much (the
+    // minmax is trivial).
+    cdata->_min_vertex = cdata->_first_vertex;
+    cdata->_max_vertex = cdata->_first_vertex + cdata->_num_vertices - 1;
+    cdata->_mins.clear();
+    cdata->_maxs.clear();
+
+  } else if (get_num_vertices() == 0) {
+    // Or if we don't have any vertices, the minmax is also trivial.
     cdata->_min_vertex = 0;
     cdata->_max_vertex = 0;
     cdata->_mins.clear();
@@ -951,10 +1239,8 @@ recompute_minmax(qpGeomPrimitive::CDWriter &cdata) {
     // the minmax of each primitive (as well as the overall minmax).
     qpGeomVertexReader index(cdata->_vertices, 0);
 
-    cdata->_mins = new qpGeomVertexArrayData
-      (cdata->_vertices->get_array_format(), UH_unspecified);
-    cdata->_maxs = new qpGeomVertexArrayData
-      (cdata->_vertices->get_array_format(), UH_unspecified);
+    cdata->_mins = make_index_data();
+    cdata->_maxs = make_index_data();
 
     qpGeomVertexWriter mins(cdata->_mins, 0);
     qpGeomVertexWriter maxs(cdata->_maxs, 0);
@@ -1014,6 +1300,22 @@ recompute_minmax(qpGeomPrimitive::CDWriter &cdata) {
   cdata->_got_minmax = true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::do_make_indexed
+//       Access: Private
+//  Description: The private implementation of make_indexed().
+////////////////////////////////////////////////////////////////////
+void qpGeomPrimitive::
+do_make_indexed(qpGeomPrimitive::CDWriter &cdata) {
+  if (cdata->_vertices == (qpGeomVertexArrayData *)NULL) {
+    cdata->_vertices = make_index_data();
+    qpGeomVertexWriter index(cdata->_vertices, 0);
+    for (int i = 0; i < cdata->_num_vertices; ++i) {
+      index.add_data1i(i + cdata->_first_vertex);
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::write_datagram
 //       Access: Public, Virtual
@@ -1027,6 +1329,23 @@ write_datagram(BamWriter *manager, Datagram &dg) {
   manager->write_cdata(dg, _cycler);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::finalize
+//       Access: Public, Virtual
+//  Description: Called by the BamReader to perform any final actions
+//               needed for setting up the object after all objects
+//               have been read and all pointers have been completed.
+////////////////////////////////////////////////////////////////////
+void qpGeomPrimitive::
+finalize(BamReader *manager) {
+  if (manager->get_file_minor_ver() < 19) {
+    const qpGeomVertexArrayData *vertices = get_vertices();
+    if (vertices != (qpGeomVertexArrayData *)NULL) {
+      set_usage_hint(vertices->get_usage_hint());
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::fillin
 //       Access: Protected
@@ -1039,6 +1358,7 @@ fillin(DatagramIterator &scan, BamReader *manager) {
   TypedWritable::fillin(scan, manager);
 
   manager->read_cdata(scan, _cycler);
+  manager->register_finalize(this);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1060,7 +1380,10 @@ make_copy() const {
 void qpGeomPrimitive::CData::
 write_datagram(BamWriter *manager, Datagram &dg) const {
   dg.add_uint8(_shade_model);
+  dg.add_uint32(_first_vertex);
+  dg.add_uint32(_num_vertices);
   dg.add_uint8(_index_type);
+  dg.add_uint8(_usage_hint);
 
   manager->write_pointer(dg, _vertices);
   WRITE_PTA(manager, dg, IPD_int::write_datagram, _ends);
@@ -1092,7 +1415,14 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
 void qpGeomPrimitive::CData::
 fillin(DatagramIterator &scan, BamReader *manager) {
   _shade_model = (ShadeModel)scan.get_uint8();
+  if (manager->get_file_minor_ver() >= 19) {
+    _first_vertex = scan.get_uint32();
+    _num_vertices = scan.get_uint32();
+  }
   _index_type = (NumericType)scan.get_uint8();
+  if (manager->get_file_minor_ver() >= 19) {
+    _usage_hint = (UsageHint)scan.get_uint8();
+  }
 
   manager->read_pointer(scan);
   READ_PTA(manager, scan, IPD_int::read_datagram, _ends);

+ 19 - 1
panda/src/gobj/qpgeomPrimitive.h

@@ -80,7 +80,7 @@ PUBLISHED:
   INLINE void set_shade_model(ShadeModel shade_model);
 
   INLINE UsageHint get_usage_hint() const;
-  INLINE void set_usage_hint(UsageHint usage_hint);
+  void set_usage_hint(UsageHint usage_hint);
 
   INLINE NumericType get_index_type() const;
   void set_index_type(NumericType index_type);
@@ -92,6 +92,9 @@ PUBLISHED:
   // primitives' lengths are encoded.  You can also safely build up a
   // composite primitive using these methods.
 
+  INLINE bool is_composite() const;
+  INLINE bool is_indexed() const;
+  int get_first_vertex() const;
   INLINE int get_num_vertices() const;
   int get_vertex(int i) const;
   void add_vertex(int vertex);
@@ -100,6 +103,9 @@ PUBLISHED:
   bool close_primitive();
   void clear_vertices();
   void offset_vertices(int offset);
+  void make_nonindexed(qpGeomVertexData *dest, const qpGeomVertexData *source);
+  void pack_vertices(qpGeomVertexData *dest, const qpGeomVertexData *source);
+  void make_indexed();
 
   int get_num_primitives() const;
   int get_primitive_start(int n) const;
@@ -140,6 +146,7 @@ public:
   INLINE const qpGeomVertexArrayData *get_vertices() const;
   qpGeomVertexArrayData *modify_vertices();
   void set_vertices(const qpGeomVertexArrayData *vertices);
+  void set_nonindexed_vertices(int first_vertex, int num_vertices);
 
   INLINE int get_index_stride() const;
   INLINE CPTA_uchar get_data() const;
@@ -163,6 +170,10 @@ public:
   bool release(PreparedGraphicsObjects *prepared_objects);
   int release_all();
 
+protected:
+  INLINE CPT(qpGeomVertexArrayFormat) get_index_format() const;
+  INLINE PT(qpGeomVertexArrayData) make_index_data() const;
+
 private:
   void clear_prepared(PreparedGraphicsObjects *prepared_objects);
 
@@ -177,6 +188,7 @@ public:
 protected:
   virtual CPT(qpGeomPrimitive) decompose_impl() const;
   virtual CPT(qpGeomVertexArrayData) rotate_impl() const;
+  virtual bool requires_unused_vertices() const;
   virtual void append_unused_vertices(qpGeomVertexArrayData *vertices, 
                                       int vertex);
 
@@ -200,7 +212,10 @@ private:
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
 
     ShadeModel _shade_model;
+    int _first_vertex;
+    int _num_vertices;
     NumericType _index_type;
+    UsageHint _usage_hint;
     PT(qpGeomVertexArrayData) _vertices;
     PTA_int _ends;
     PT(qpGeomVertexArrayData) _mins;
@@ -217,6 +232,7 @@ private:
   typedef CycleDataWriter<CData> CDWriter;
 
   void recompute_minmax(CDWriter &cdata);
+  void do_make_indexed(CDWriter &cdata);
 
   static PStatCollector _decompose_pcollector;
   static PStatCollector _rotate_pcollector;
@@ -224,6 +240,8 @@ private:
 public:
   virtual void write_datagram(BamWriter *manager, Datagram &dg);
 
+  virtual void finalize(BamReader *manager);
+
 protected:
   void fillin(DatagramIterator &scan, BamReader *manager);
 

+ 73 - 35
panda/src/gobj/qpgeomTriangles.cxx

@@ -116,49 +116,87 @@ rotate_impl() const {
   // To rotate triangles, we just move one vertex from the front to
   // the back, or vice-versa; but we have to know what direction we're
   // going.
-  CPT(qpGeomVertexArrayData) vertices = get_vertices();
   ShadeModel shade_model = get_shade_model();
+  int num_vertices = get_num_vertices();
+    
+  PT(qpGeomVertexArrayData) new_vertices = make_index_data();
+  new_vertices->set_num_rows(num_vertices);
 
-  PT(qpGeomVertexArrayData) new_vertices = 
-    new qpGeomVertexArrayData(*vertices);
-  qpGeomVertexReader from(vertices, 0);
-  qpGeomVertexWriter to(new_vertices, 0);
-
-  int num_vertices = vertices->get_num_rows();
-  
-  switch (shade_model) {
-  case SM_flat_first_vertex:
-    // Move the first vertex to the end.
-    {
-      for (int begin = 0; begin < num_vertices; begin += 3) {
-        from.set_row(begin + 1);
-        to.set_data1i(from.get_data1i());
-        to.set_data1i(from.get_data1i());
-        from.set_row(begin);
-        to.set_data1i(from.get_data1i());
+  if (is_indexed()) {
+    CPT(qpGeomVertexArrayData) vertices = get_vertices();
+    qpGeomVertexReader from(vertices, 0);
+    qpGeomVertexWriter to(new_vertices, 0);
+    
+    switch (shade_model) {
+    case SM_flat_first_vertex:
+      // Move the first vertex to the end.
+      {
+        for (int begin = 0; begin < num_vertices; begin += 3) {
+          from.set_row(begin + 1);
+          to.set_data1i(from.get_data1i());
+          to.set_data1i(from.get_data1i());
+          from.set_row(begin);
+          to.set_data1i(from.get_data1i());
+        }
+      }
+      break;
+      
+    case SM_flat_last_vertex:
+      // Move the last vertex to the front.
+      {
+        for (int begin = 0; begin < num_vertices; begin += 3) {
+          from.set_row(begin + 2);
+          to.set_data1i(from.get_data1i());
+          from.set_row(begin);
+          to.set_data1i(from.get_data1i());
+          to.set_data1i(from.get_data1i());
+        }
       }
+      break;
+      
+    default:
+      // This shouldn't get called with any other shade model.
+      nassertr(false, vertices);
     }
-    break;
     
-  case SM_flat_last_vertex:
-    // Move the last vertex to the front.
-    {
-      for (int begin = 0; begin < num_vertices; begin += 3) {
-        from.set_row(begin + 2);
-        to.set_data1i(from.get_data1i());
-        from.set_row(begin);
-        to.set_data1i(from.get_data1i());
-        to.set_data1i(from.get_data1i());
+    nassertr(to.is_at_end(), NULL);
+
+  } else {
+    // Nonindexed case.
+    int first_vertex = get_first_vertex();
+    qpGeomVertexWriter to(new_vertices, 0);
+    
+    switch (shade_model) {
+    case SM_flat_first_vertex:
+      // Move the first vertex to the end.
+      {
+        for (int begin = 0; begin < num_vertices; begin += 3) {
+          to.set_data1i(begin + 1 + first_vertex);
+          to.set_data1i(begin + 2 + first_vertex);
+          to.set_data1i(begin + first_vertex);
+        }
       }
-    }
-    break;
+      break;
+      
+    case SM_flat_last_vertex:
+      // Move the last vertex to the front.
+      {
+        for (int begin = 0; begin < num_vertices; begin += 3) {
+          to.set_data1i(begin + 2 + first_vertex);
+          to.set_data1i(begin + first_vertex);
+          to.set_data1i(begin + 1 + first_vertex);
+        }
+      }
+      break;
       
-  default:
-    // This shouldn't get called with any other shade model.
-    nassertr(false, vertices);
+    default:
+      // This shouldn't get called with any other shade model.
+      nassertr(false, NULL);
+    }
+    
+    nassertr(to.is_at_end(), NULL);
   }
-  
-  nassertr(to.is_at_end(), NULL);
+
   return new_vertices;
 }
 

+ 74 - 27
panda/src/gobj/qpgeomTristrips.cxx

@@ -235,44 +235,91 @@ rotate_impl() const {
   // vertices, that doesn't work--in fact, nothing works (without also
   // changing the winding order), so we don't allow an odd number of
   // vertices in a flat-shaded tristrip.
-  CPT(qpGeomVertexArrayData) vertices = get_vertices();
   CPTA_int ends = get_ends();
-  PT(qpGeomVertexArrayData) new_vertices = 
-    new qpGeomVertexArrayData(*vertices);
-  qpGeomVertexReader from(vertices, 0);
-  qpGeomVertexWriter to(new_vertices, 0);
 
-  int begin = 0;
-  int last_added = 0;
-  CPTA_int::const_iterator ei;
-  for (ei = ends.begin(); ei != ends.end(); ++ei) {
-    int end = (*ei);
-    int num_vertices = end - begin;
+  PT(qpGeomVertexArrayData) new_vertices = make_index_data();
+  new_vertices->set_num_rows(get_num_vertices());
 
-    if (begin != 0) {
-      // Copy in the unused vertices between tristrips.
-      to.set_data1i(last_added);
-      from.set_row(end - 1);
-      to.set_data1i(from.get_data1i());
-      begin += 2;
+  if (is_indexed()) {
+    CPT(qpGeomVertexArrayData) vertices = get_vertices();
+    qpGeomVertexReader from(vertices, 0);
+    qpGeomVertexWriter to(new_vertices, 0);
+    
+    int begin = 0;
+    int last_added = 0;
+    CPTA_int::const_iterator ei;
+    for (ei = ends.begin(); ei != ends.end(); ++ei) {
+      int end = (*ei);
+      int num_vertices = end - begin;
+      
+      if (begin != 0) {
+        // Copy in the unused vertices between tristrips.
+        to.set_data1i(last_added);
+        from.set_row(end - 1);
+        to.set_data1i(from.get_data1i());
+        begin += 2;
+      }
+      
+      // If this assertion is triggered, there was a triangle strip with
+      // an odd number of vertices, which is not allowed.
+      nassertr((num_vertices & 1) == 0, NULL);
+      for (int vi = end - 1; vi >= begin; --vi) {
+        from.set_row(vi);
+        last_added = from.get_data1i();
+        to.set_data1i(last_added);
+      }
+      
+      begin = end;
     }
 
-    // If this assertion is triggered, there was a triangle strip with
-    // an odd number of vertices, which is not allowed.
-    nassertr((num_vertices & 1) == 0, NULL);
-    for (int vi = end - 1; vi >= begin; --vi) {
-      from.set_row(vi);
-      last_added = from.get_data1i();
-      to.set_data1i(last_added);
+    nassertr(to.is_at_end(), NULL);
+
+  } else {
+    // Nonindexed case.
+    int first_vertex = get_first_vertex();
+    qpGeomVertexWriter to(new_vertices, 0);
+    
+    int begin = 0;
+    int last_added = 0;
+    CPTA_int::const_iterator ei;
+    for (ei = ends.begin(); ei != ends.end(); ++ei) {
+      int end = (*ei);
+      int num_vertices = end - begin;
+      
+      if (begin != 0) {
+        // Copy in the unused vertices between tristrips.
+        to.set_data1i(last_added);
+        to.set_data1i(end - 1 + first_vertex);
+        begin += 2;
+      }
+      
+      // If this assertion is triggered, there was a triangle strip with
+      // an odd number of vertices, which is not allowed.
+      nassertr((num_vertices & 1) == 0, NULL);
+      for (int vi = end - 1; vi >= begin; --vi) {
+        last_added = vi + first_vertex;
+        to.set_data1i(last_added);
+      }
+      
+      begin = end;
     }
 
-    begin = end;
+    nassertr(to.is_at_end(), NULL);
   }
-
-  nassertr(to.is_at_end(), NULL);
   return new_vertices;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTristrips::requires_unused_vertices
+//       Access: Protected, Virtual
+//  Description: Should be redefined to return true in any primitive
+//               that implements append_unused_vertices().
+////////////////////////////////////////////////////////////////////
+bool qpGeomTristrips::
+requires_unused_vertices() const {
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomTristrips::append_unused_vertices
 //       Access: Protected, Virtual

+ 1 - 0
panda/src/gobj/qpgeomTristrips.h

@@ -46,6 +46,7 @@ public:
 protected:
   virtual CPT(qpGeomPrimitive) decompose_impl() const;
   virtual CPT(qpGeomVertexArrayData) rotate_impl() const;
+  virtual bool requires_unused_vertices() const;
   virtual void append_unused_vertices(qpGeomVertexArrayData *vertices, 
                                       int vertex);
 

+ 27 - 1
panda/src/gobj/qpgeomVertexColumn.cxx

@@ -46,7 +46,33 @@ operator = (const qpGeomVertexColumn &copy) {
 ////////////////////////////////////////////////////////////////////
 void qpGeomVertexColumn::
 output(ostream &out) const {
-  out << *get_name() << "(" << get_num_components() << ")";
+  out << *get_name() << "(" << get_num_components();
+  switch (get_numeric_type()) {
+  case NT_uint8:
+    out << "b";
+    break;
+
+  case NT_uint16:
+    out << "s";
+    break;
+
+  case NT_uint32:
+    out << "l";
+    break;
+
+  case NT_packed_dcba:
+    out << "p-";
+    break;
+
+  case NT_packed_dabc:
+    out << "p";
+    break;
+
+  case NT_float32:
+    out << "f";
+  }
+
+  out << ")";
 }
 
 ////////////////////////////////////////////////////////////////////

+ 48 - 13
panda/src/gobj/qpgeomVertexData.cxx

@@ -379,11 +379,11 @@ get_num_bytes() const {
 //               pointerwise from the source.
 ////////////////////////////////////////////////////////////////////
 void qpGeomVertexData::
-copy_from(const qpGeomVertexData &source, bool keep_data_objects) {
-  const qpGeomVertexFormat *source_format = source.get_format();
+copy_from(const qpGeomVertexData *source, bool keep_data_objects) {
+  const qpGeomVertexFormat *source_format = source->get_format();
   const qpGeomVertexFormat *dest_format = get_format();
 
-  int num_rows = source.get_num_rows();
+  int num_rows = source->get_num_rows();
   int num_arrays = source_format->get_num_arrays();
   int source_i;
 
@@ -410,14 +410,14 @@ copy_from(const qpGeomVertexData &source, bool keep_data_objects) {
 
           // Maybe it even has the same data pointer already.  If so,
           // avoid flipping the modified flag.
-          CPTA_uchar source_data = source.get_array(source_i)->get_data();
+          CPTA_uchar source_data = source->get_array(source_i)->get_data();
           if (get_array(dest_i)->get_data() != source_data) {
             modify_array(dest_i)->set_data(source_data);
           }
         } else {
           // Copy the GeomVertexArrayData object.
-          if (get_array(dest_i) != source.get_array(source_i)) {
-            set_array(dest_i, source.get_array(source_i));
+          if (get_array(dest_i) != source->get_array(source_i)) {
+            set_array(dest_i, source->get_array(source_i));
           }
         }
 
@@ -432,7 +432,7 @@ copy_from(const qpGeomVertexData &source, bool keep_data_objects) {
 
   // Now go back through and copy any data that's left over.
   for (source_i = 0; source_i < num_arrays; ++source_i) {
-    CPTA_uchar array_data = source.get_array(source_i)->get_data();
+    CPTA_uchar array_data = source->get_array(source_i)->get_data();
     const qpGeomVertexArrayFormat *source_array_format = source_format->get_array(source_i);
     int num_columns = source_array_format->get_num_columns();
     for (int di = 0; di < num_columns; ++di) {
@@ -487,7 +487,7 @@ copy_from(const qpGeomVertexData &source, bool keep_data_objects) {
           }
           qpGeomVertexWriter to(this);
           to.set_column(dest_i, dest_column);
-          qpGeomVertexReader from(&source);
+          qpGeomVertexReader from(source);
           from.set_column(source_i, source_column);
 
           while (!from.is_at_end()) {
@@ -505,7 +505,7 @@ copy_from(const qpGeomVertexData &source, bool keep_data_objects) {
     if (dest_animation.get_animation_type() == AT_hardware) {
       // Convert Panda-style animation tables to hardware-style
       // animation tables.
-      CPT(TransformBlendPalette) blend_palette = source.get_transform_blend_palette();
+      CPT(TransformBlendPalette) blend_palette = source->get_transform_blend_palette();
       if (blend_palette != (TransformBlendPalette *)NULL) {
         PT(TransformPalette) transform_palette = new TransformPalette;
         TransformMap already_added;
@@ -515,7 +515,7 @@ copy_from(const qpGeomVertexData &source, bool keep_data_objects) {
           // means we can put the blends in any order.
           qpGeomVertexWriter weight(this, InternalName::get_transform_weight());
           qpGeomVertexWriter index(this, InternalName::get_transform_index());
-          qpGeomVertexReader from(&source, InternalName::get_transform_blend());
+          qpGeomVertexReader from(source, InternalName::get_transform_blend());
         
           while (!from.is_at_end()) {
             const TransformBlend &blend = blend_palette->get_blend(from.get_data1i());
@@ -537,7 +537,7 @@ copy_from(const qpGeomVertexData &source, bool keep_data_objects) {
           // Build a nonindexed transform array.  This means we have to
           // use the same n transforms, in the same order, for each vertex.
           qpGeomVertexWriter weight(this, InternalName::get_transform_weight());
-          qpGeomVertexReader from(&source, InternalName::get_transform_blend());
+          qpGeomVertexReader from(source, InternalName::get_transform_blend());
         
           while (!from.is_at_end()) {
             const TransformBlend &blend = blend_palette->get_blend(from.get_data1i());
@@ -562,6 +562,41 @@ copy_from(const qpGeomVertexData &source, bool keep_data_objects) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexData::copy_row_from
+//       Access: Published
+//  Description: Copies a single row of the data from the other array
+//               into the indicated row of this array.  In this case,
+//               the source format must exactly match the destination
+//               format.
+////////////////////////////////////////////////////////////////////
+void qpGeomVertexData::
+copy_row_from(int dest_row, const qpGeomVertexData *source, 
+              int source_row) {
+  const qpGeomVertexFormat *source_format = source->get_format();
+  const qpGeomVertexFormat *dest_format = get_format();
+  nassertv(source_format == dest_format);
+  nassertv(source_row >= 0 && source_row < source->get_num_rows());
+
+  if (dest_row >= get_num_rows()) {
+    // Implicitly add enough rows to get to the indicated row.
+    set_num_rows(dest_row + 1);
+  }
+
+  int num_arrays = source_format->get_num_arrays();
+
+  for (int i = 0; i < num_arrays; ++i) {
+    PTA_uchar dest_array_data = modify_array(i)->modify_data();
+    CPTA_uchar source_array_data = source->get_array(i)->get_data();
+    const qpGeomVertexArrayFormat *array_format = source_format->get_array(i);
+    int stride = array_format->get_stride();
+
+    memcpy(dest_array_data + stride * dest_row,
+           source_array_data + stride * source_row,
+           stride);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomVertexData::convert_to
 //       Access: Published
@@ -605,7 +640,7 @@ convert_to(const qpGeomVertexFormat *new_format) const {
   new_data->set_transform_blend_palette(get_transform_blend_palette());
   new_data->set_slider_table(get_slider_table());
 
-  new_data->copy_from(*this, false);
+  new_data->copy_from(this, false);
 
   {
     // Record the new result in the cache.
@@ -1269,7 +1304,7 @@ update_animated_vertices(qpGeomVertexData::CDWriter &cdata, bool from_app) {
   // of the data that might have changed since last frame, but that's
   // too much trouble (and isn't obviously faster than just copying
   // the whole thing).
-  new_data->copy_from(*this, true);
+  new_data->copy_from(this, true);
 
   // First, apply all of the morphs.
   CPT(SliderTable) table = cdata->_slider_table;

+ 3 - 1
panda/src/gobj/qpgeomVertexData.h

@@ -118,7 +118,9 @@ PUBLISHED:
   int get_num_bytes() const;
   INLINE UpdateSeq get_modified() const;
 
-  void copy_from(const qpGeomVertexData &source, bool keep_data_objects);
+  void copy_from(const qpGeomVertexData *source, bool keep_data_objects);
+  void copy_row_from(int dest_row, const qpGeomVertexData *source, 
+                     int source_row);
   CPT(qpGeomVertexData) convert_to(const qpGeomVertexFormat *new_format) const;
   CPT(qpGeomVertexData) 
     scale_color(const LVecBase4f &color_scale) const;

+ 2 - 1
panda/src/gobj/qpgeomVertexFormat.cxx

@@ -417,7 +417,8 @@ write_with_data(ostream &out, int indent_level,
   for (size_t i = 0; i < _arrays.size(); i++) {
     CPTA_uchar array_data = data->get_array(i)->get_data();
     indent(out, indent_level)
-      << "Array " << i << " (" << (void *)array_data.p() << "):\n";
+      << "Array " << i << " (" << (void *)array_data.p() << ", "
+      << *_arrays[i] << "):\n";
     _arrays[i]->write_with_data(out, indent_level + 2, data->get_array(i));
   }
 }

+ 12 - 1
panda/src/pgraph/geomNode.I

@@ -67,6 +67,17 @@ get_geom(int n) const {
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomNode::get_unique_geom
 //       Access: Public
+//  Description: This method is deprecated; you should call
+//               modify_geom() instead.
+////////////////////////////////////////////////////////////////////
+INLINE Geom *GeomNode::
+get_unique_geom(int n) {
+  return modify_geom(n);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeomNode::modify_geom
+//       Access: Public
 //  Description: Returns the nth geom of the node, suitable for
 //               modifying it.  If the nth Geom has multiple reference
 //               counts to it, reassigns it to an identical copy
@@ -76,7 +87,7 @@ get_geom(int n) const {
 //               with any other GeomNodes.
 ////////////////////////////////////////////////////////////////////
 INLINE Geom *GeomNode::
-get_unique_geom(int n) {
+modify_geom(int n) {
   CDWriter cdata(_cycler);
   nassertr(n >= 0 && n < (int)cdata->_geoms.size(), NULL);
   Geom *geom = cdata->_geoms[n]._geom;

+ 1 - 0
panda/src/pgraph/geomNode.h

@@ -59,6 +59,7 @@ PUBLISHED:
   INLINE int get_num_geoms() const;
   INLINE const Geom *get_geom(int n) const;
   INLINE Geom *get_unique_geom(int n);
+  INLINE Geom *modify_geom(int n);
   INLINE const RenderState *get_geom_state(int n) const;
   INLINE void set_geom_state(int n, const RenderState *state);
 

+ 15 - 0
panda/src/pgraph/sceneGraphReducer.I

@@ -118,3 +118,18 @@ INLINE int SceneGraphReducer::
 collect_vertex_data(PandaNode *root, int collect_bits) {
   return r_collect_vertex_data(root, collect_bits, _transformer);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: SceneGraphReducer::make_nonindexed
+//       Access: Published
+//  Description: Converts indexed geometry to nonindexed geometry at
+//               the indicated node and below, by duplicating vertices
+//               where necessary.  The parameter nonindexed_bits is a
+//               union of bits defined in
+//               SceneGraphReducer::MakeNonindexed, which specifes
+//               which types of geometry to avoid making nonindexed.
+////////////////////////////////////////////////////////////////////
+INLINE int SceneGraphReducer::
+make_nonindexed(PandaNode *root, int nonindexed_bits) {
+  return r_make_nonindexed(root, nonindexed_bits);
+}

+ 53 - 0
panda/src/pgraph/sceneGraphReducer.cxx

@@ -583,6 +583,9 @@ r_collect_vertex_data(PandaNode *node, int collect_bits,
   if (!node->get_transform()->is_identity()) {
     this_node_bits |= CVD_transform;
   }
+  if (node->is_geom_node()) {
+    this_node_bits |= CVD_one_node_only;
+  }
 
   if ((collect_bits & this_node_bits) != 0) {
     // We need to start a unique collection here.
@@ -621,3 +624,53 @@ r_collect_vertex_data(PandaNode *node, int collect_bits,
     
   return num_created;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: SceneGraphReducer::r_make_nonindexed
+//       Access: Private
+//  Description: The recursive implementation of
+//               make_nonindexed().
+////////////////////////////////////////////////////////////////////
+int SceneGraphReducer::
+r_make_nonindexed(PandaNode *node, int nonindexed_bits) {
+  int num_changed = 0;
+
+  if (node->is_geom_node()) {
+    GeomNode *geom_node = DCAST(GeomNode, node);
+    int num_geoms = geom_node->get_num_geoms();
+    for (int i = 0; i < num_geoms; ++i) {
+      if (geom_node->get_geom(i)->is_of_type(qpGeom::get_class_type())) {
+        const qpGeom *geom = DCAST(qpGeom, geom_node->get_geom(i));
+
+        // Check whether the geom is animated or dynamic, and skip it
+        // if the user specified so.
+        const qpGeomVertexData *data = geom->get_vertex_data();
+        int this_geom_bits = 0;
+        if (data->get_format()->get_animation().get_animation_type() !=
+            qpGeom::AT_none) {
+          this_geom_bits |= MN_avoid_animated;
+        }
+        if (data->get_usage_hint() != qpGeom::UH_static ||
+            geom->get_usage_hint() != qpGeom::UH_static) {
+          this_geom_bits |= MN_avoid_dynamic;
+        }
+
+        if ((nonindexed_bits & this_geom_bits) == 0) {
+          // The geom meets the user's qualifications for making
+          // nonindexed, so do it.
+          qpGeom *mgeom = DCAST(qpGeom, geom_node->modify_geom(i));
+          num_changed += mgeom->make_nonindexed((nonindexed_bits & MN_composite_only) != 0);
+        }
+      }
+    }
+  }
+
+  PandaNode::Children children = node->get_children();
+  int num_children = children.get_num_children();
+  for (int i = 0; i < num_children; ++i) {
+    num_changed += 
+      r_make_nonindexed(children.get_child(i), nonindexed_bits);
+  }
+    
+  return num_changed;
+}

+ 24 - 2
panda/src/pgraph/sceneGraphReducer.h

@@ -78,9 +78,29 @@ PUBLISHED:
 
     // If set, GeomVertexDatas with any usage_hint other than
     // UH_static will not be collected with any other Geoms in a
-    // different GeomNode.  However, two different Geoms within the
-    // same node might still be collected together.
+    // different GeomNode.  However, two different dynamic Geoms
+    // within the same node might still be collected together.
     CVD_avoid_dynamic  = 0x008,
+
+    // If set, only those GeomVertexDatas within the same node might
+    // be collected together.
+    CVD_one_node_only  = 0x010,
+  };
+
+  enum MakeNonindexed {
+    // If set, only composite primitives such as tristrips and trifans
+    // will be made nonindexed; simple primitives such as triangles
+    // will be left indexed.
+    MN_composite_only  = 0x001,
+
+    // If set any GeomVertexData with any animation indication will
+    // not be adjusted, whether the animation is to be performed on
+    // the CPU or on the graphics pipe.
+    MN_avoid_animated  = 0x002,
+
+    // If set, any GeomVertexData or Geom with a usage_hint other than
+    // UH_static will not be made nonindexed.
+    MN_avoid_dynamic   = 0x004,
   };
 
   INLINE void set_usage_hint(qpGeom::UsageHint usage_hint);
@@ -93,6 +113,7 @@ PUBLISHED:
   int flatten(PandaNode *root, int combine_siblings_bits);
 
   INLINE int collect_vertex_data(PandaNode *root, int collect_bits = ~0);
+  INLINE int make_nonindexed(PandaNode *root, int nonindexed_bits = ~0);
 
 protected:
   void r_apply_attribs(PandaNode *node, const AccumulatedAttribs &attribs,
@@ -121,6 +142,7 @@ protected:
 
   int r_collect_vertex_data(PandaNode *node, int collect_bits,
                             GeomTransformer &transformer);
+  int r_make_nonindexed(PandaNode *node, int collect_bits);
 
 private:
   GeomTransformer _transformer;

+ 2 - 1
panda/src/putil/bam.h

@@ -34,7 +34,7 @@ static const unsigned short _bam_major_ver = 4;
 // Bumped to major version 3 on 12/8/00 to change float64's to float32's.
 // Bumped to major version 4 on 4/10/02 to store new scene graph.
 
-static const unsigned short _bam_minor_ver = 18;
+static const unsigned short _bam_minor_ver = 19;
 // Bumped to minor version 1 on 4/10/03 to add CullFaceAttrib::reverse.
 // Bumped to minor version 2 on 4/12/03 to add num_components to texture.
 // Bumped to minor version 3 on 4/15/03 to add ImageBuffer::_alpha_file_channel
@@ -53,6 +53,7 @@ static const unsigned short _bam_minor_ver = 18;
 // Bumped to minor version 16 on 2/24/05 to add TextureStage::rgb_scale, etc.
 // Bumped to minor version 17 on 3/03/05 to add 3-d textures, etc.
 // Bumped to minor version 18 on 4/05/05 to add RenderModeAttrib::perspective.
+// Bumped to minor version 19 on 4/19/05 to add nonindexed qpgeom primitives.
 
 
 #endif

+ 9 - 1
panda/src/testbed/pview.cxx

@@ -18,9 +18,10 @@
 
 #include "pandaFramework.h"
 #include "textNode.h"
-#include "multitexReducer.h"
 #include "configVariableBool.h"
 #include "texturePool.h"
+#include "multitexReducer.h"
+#include "sceneGraphReducer.h"
 
 // By including checkPandaVersion.h, we guarantee that runtime
 // attempts to run pview will fail if it inadvertently links with the
@@ -130,6 +131,12 @@ event_2(CPT_Event event, void *) {
 void
 event_0(CPT_Event event, void *) {
   // 0: run hacky test.
+
+  SceneGraphReducer gr;
+  gr.make_nonindexed(framework.get_models().node());
+  gr.collect_vertex_data(framework.get_models().node());
+
+  /*
   static int count = 0;
 
   static PT(TextureStage) ts;
@@ -175,6 +182,7 @@ event_0(CPT_Event event, void *) {
     }
   }
   count++;
+  */
 }
 
 void