Просмотр исходного кода

load obj files directly into PandaNodes for performance

David Rose 13 лет назад
Родитель
Сommit
b3b2308c6c

+ 15 - 2
panda/src/gobj/geom.cxx

@@ -328,7 +328,7 @@ set_primitive(int i, const GeomPrimitive *primitive) {
   nassertv(cdata->_primitive_type == PT_none ||
            cdata->_primitive_type == primitive->get_primitive_type());
 
-  // They also should have the a compatible shade model.
+  // They also should have a compatible shade model.
   CPT(GeomPrimitive) compat = primitive->match_shade_model(cdata->_shade_model);
   nassertv_always(compat != (GeomPrimitive *)NULL);
 
@@ -374,7 +374,7 @@ add_primitive(const GeomPrimitive *primitive) {
   nassertv(cdata->_primitive_type == PT_none ||
            cdata->_primitive_type == primitive->get_primitive_type());
 
-  // They also should have the a compatible shade model.
+  // They also should have a compatible shade model.
   CPT(GeomPrimitive) compat = primitive->match_shade_model(cdata->_shade_model);
   nassertv_always(compat != (GeomPrimitive *)NULL);
 
@@ -591,6 +591,19 @@ rotate_in_place() {
 #endif
   }
 
+  switch (cdata->_shade_model) {
+  case SM_flat_first_vertex:
+    cdata->_shade_model = SM_flat_last_vertex;
+    break;
+
+  case SM_flat_last_vertex:
+    cdata->_shade_model = SM_flat_first_vertex;
+    break;
+
+  default:
+    break;
+  }
+
   cdata->_modified = Geom::get_next_modified();
   clear_cache_stage(current_thread);
 

+ 3 - 0
panda/src/mathutil/Sources.pp

@@ -40,6 +40,7 @@
     stackedPerlinNoise2.h stackedPerlinNoise2.I \
     stackedPerlinNoise3.h stackedPerlinNoise3.I \
     triangulator.h triangulator.I \
+    triangulator3.h triangulator3.I \
     unionBoundingVolume.h unionBoundingVolume.I
 
   #define INCLUDED_SOURCES \
@@ -66,6 +67,7 @@
     stackedPerlinNoise2.cxx \
     stackedPerlinNoise3.cxx \
     triangulator.cxx \
+    triangulator3.cxx \
     unionBoundingVolume.cxx
 
   #define INSTALL_HEADERS \
@@ -94,6 +96,7 @@
     stackedPerlinNoise2.h stackedPerlinNoise2.I \
     stackedPerlinNoise3.h stackedPerlinNoise3.I \
     triangulator.h triangulator.I \
+    triangulator3.h triangulator3.I \
     unionBoundingVolume.h unionBoundingVolume.I
 
 

+ 1 - 0
panda/src/mathutil/p3mathutil_composite2.cxx

@@ -16,3 +16,4 @@
 #include "stackedPerlinNoise2.cxx"
 #include "stackedPerlinNoise3.cxx"
 #include "triangulator.cxx"
+#include "triangulator3.cxx"

+ 54 - 2
panda/src/mathutil/triangulator.cxx

@@ -118,14 +118,27 @@ void Triangulator::
 triangulate() {
   _result.clear();
 
+  // Make sure our index numbers are reasonable.
+  cleanup_polygon_indices(_polygon);
+  Holes::iterator hi;
+  for (hi = _holes.begin(); hi != _holes.end(); ++hi) {
+    cleanup_polygon_indices(*hi);
+  }
+
+  if (_polygon.size() < 3) {
+    // Degenerate case.
+    return;
+  }
+
   // Set up the list of segments.
   seg.clear();
   seg.push_back(segment_t());  // we don't use the first entry.
   make_segment(_polygon, true);
 
-  Holes::const_iterator hi;
   for (hi = _holes.begin(); hi != _holes.end(); ++hi) {
-    make_segment(*hi, false);
+    if ((*hi).size() >= 3) {
+      make_segment(*hi, false);
+    }
   }
 
   // Shuffle the segment index.
@@ -151,6 +164,7 @@ triangulate() {
   */
   choose_idx = 0;
 
+  /*
   //cerr << "got " << num_segments << " segments\n";
   for (i = 1; i < (int)seg.size(); ++i) {
     segment_t &s = seg[i];
@@ -158,6 +172,7 @@ triangulate() {
     printf("    root0 = %d, root1 = %d\n", s.root0, s.root1);
     printf("    next = %d, prev = %d\n", s.next, s.prev);
   }
+  */
 
   while (construct_trapezoids(num_segments) != 0) {
     // If there's an error, re-shuffle the index and try again.
@@ -262,6 +277,43 @@ get_triangle_v2(int n) const {
   return _result[n]._v2;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Triangulator::cleanup_polygon_indices
+//       Access: Protected
+//  Description: Removes any invalid index numbers from the list.
+////////////////////////////////////////////////////////////////////
+void Triangulator::
+cleanup_polygon_indices(vector_int &polygon) {
+  // First, check for index bounds.
+  size_t pi = 0;
+  while (pi < polygon.size()) {
+    if (polygon[pi] >= 0 && polygon[pi] < _vertices.size()) {
+      // This vertex is OK.
+      ++pi;
+    } else {
+      // This index is out-of-bounds; remove it.
+      polygon.erase(_polygon.begin() + pi);
+    }
+  }
+
+  // Now, remove any consecutive repeated vertices.
+  pi = 1;
+  while (pi < polygon.size()) {
+    if (_vertices[polygon[pi]] != _vertices[polygon[pi - 1]]) {
+      // This vertex is OK.
+      ++pi;
+    } else {
+      // This vertex repeats the previous one; remove it.
+      polygon.erase(_polygon.begin() + pi);
+    }
+  }
+
+  if (polygon.size() > 1 && _vertices[polygon.back()] == _vertices[_polygon.front()]) {
+    // The last vertex repeats the first one; remove it.
+    polygon.pop_back();
+  }
+}
+
 
 // The remainder of the code in this file is adapted more or less from
 // the C code published with the referenced paper.

+ 5 - 4
panda/src/mathutil/triangulator.h

@@ -31,9 +31,8 @@
 //
 //               http://www.cs.unc.edu/~dm/CODE/GEM/chapter.html
 //
-//               It works strictly on 2-d points.  You'll have to
-//               convert your polygon into a plane if you have 3-d
-//               points.
+//               It works strictly on 2-d points.  See Triangulator3
+//               for 3-d points.
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA_MATHUTIL Triangulator {
 PUBLISHED:
@@ -61,7 +60,9 @@ PUBLISHED:
   int get_triangle_v1(int n) const;
   int get_triangle_v2(int n) const;
 
-private:
+protected:
+  void cleanup_polygon_indices(vector_int &polygon);
+
   typedef pvector<LPoint2d> Vertices;
   Vertices _vertices;
 

+ 61 - 0
panda/src/mathutil/triangulator3.I

@@ -0,0 +1,61 @@
+// Filename: triangulator3.I
+// Created by:  drose (03Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: Triangulator3::add_vertex
+//       Access: Published
+//  Description: Adds a new vertex to the vertex pool.  Returns the
+//               vertex index number.
+////////////////////////////////////////////////////////////////////
+INLINE int Triangulator3::
+add_vertex(double x, double y, double z) {
+  return add_vertex(LPoint3d(x, y, z));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Triangulator3::get_num_vertices
+//       Access: Published
+//  Description: Returns the number of vertices in the pool.  Note
+//               that the Triangulator might append new vertices, in
+//               addition to those added by the user, if any of the
+//               polygon is self-intersecting, or if any of the holes
+//               intersect some part of the polygon edges.
+////////////////////////////////////////////////////////////////////
+INLINE int Triangulator3::
+get_num_vertices() const {
+  return _vertices3.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Triangulator3::get_vertex
+//       Access: Published
+//  Description: Returns the nth vertex.
+////////////////////////////////////////////////////////////////////
+INLINE const LPoint3d &Triangulator3::
+get_vertex(int n) const {
+  nassertr(n >= 0 && n < (int)_vertices3.size(), LPoint3d::zero());
+  return _vertices3[n];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Triangulator3::get_plane
+//       Access: Published
+//  Description: Returns the plane of the polygon.  This is only
+//               available after calling triangulate().
+////////////////////////////////////////////////////////////////////
+INLINE const LPlaned &Triangulator3::
+get_plane() const {
+  return _plane;
+}

+ 112 - 0
panda/src/mathutil/triangulator3.cxx

@@ -0,0 +1,112 @@
+// Filename: triangulator3.cxx
+// Created by:  drose (03Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "triangulator3.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: Triangulator3::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+Triangulator3::
+Triangulator3() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Triangulator3::clear
+//       Access: Published
+//  Description: Removes all vertices and polygon specifications from
+//               the Triangulator, and prepares it to start over.
+////////////////////////////////////////////////////////////////////
+void Triangulator3::
+clear() {
+  _vertices3.clear();
+  _plane = LPlaned();
+  Triangulator::clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Triangulator3::add_vertex
+//       Access: Published
+//  Description: Adds a new vertex to the vertex pool.  Returns the
+//               vertex index number.
+////////////////////////////////////////////////////////////////////
+int Triangulator3::
+add_vertex(const LPoint3d &point) {
+  int index = (int)_vertices3.size();
+  _vertices3.push_back(point);
+  return index;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Triangulator3::triangulate
+//       Access: Published
+//  Description: Does the work of triangulating the specified polygon.
+//               After this call, you may retrieve the new triangles
+//               one at a time by iterating through
+//               get_triangle_v0/1/2().
+////////////////////////////////////////////////////////////////////
+void Triangulator3::
+triangulate() {
+  _result.clear();
+
+  if (_polygon.size() < 3) {
+    // Degenerate case.
+    return;
+  }
+
+  // First, determine the polygon normal.
+  LNormald normal = LNormald::zero();
+
+  // Project the polygon into each of the three major planes and
+  // calculate the area of each 2-d projection.  This becomes the
+  // polygon normal.  This works because the ratio between these
+  // different areas corresponds to the angle at which the polygon is
+  // tilted toward each plane.
+  size_t num_verts = _polygon.size();
+  for (size_t i = 0; i < num_verts; i++) {
+    int i0 = _polygon[i];
+    int i1 = _polygon[(i + 1) % num_verts];;;;
+    nassertv(i0 >= 0 && i0 < (int)_vertices3.size() &&
+             i1 >= 0 && i1 < (int)_vertices3.size());
+    const LPoint3d &p0 = _vertices3[i0];
+    const LPoint3d &p1 = _vertices3[i1];
+    normal[0] += p0[1] * p1[2] - p0[2] * p1[1];
+    normal[1] += p0[2] * p1[0] - p0[0] * p1[2];
+    normal[2] += p0[0] * p1[1] - p0[1] * p1[0];
+  }
+
+  if (!normal.normalize()) {
+    // The polygon is degenerate: it has zero area in each plane.  In
+    // this case, the triangulation result produces no triangles
+    // anyway.
+    return;
+  }
+
+  _plane = LPlaned(normal, _vertices3[0]);
+
+  // Now determine the matrix to project each of the vertices into
+  // this 2-d plane.
+  LMatrix4d mat;
+  heads_up(mat, _vertices3[1] - _vertices3[2], normal, CS_zup_right);
+  mat.set_row(3, _vertices3[0]);
+  mat.invert_in_place();
+
+  _vertices.clear();
+  for (size_t i = 0; i < _vertices3.size(); i++) {
+    LPoint3d p = _vertices3[i] * mat;
+    _vertices.push_back(LPoint2d(p[0], p[1]));
+  }
+  Triangulator::triangulate();
+}

+ 55 - 0
panda/src/mathutil/triangulator3.h

@@ -0,0 +1,55 @@
+// Filename: triangulator3.h
+// Created by:  drose (03Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef TRIANGULATOR3_H
+#define TRIANGULATOR3_H
+
+#include "pandabase.h"
+#include "triangulator.h"
+#include "plane.h"
+
+
+////////////////////////////////////////////////////////////////////
+//       Class : Triangulator3
+// Description : This is an extension of Triangulator to handle
+//               polygons with three-dimensional points.  It assumes
+//               all of the points lie in a single plane, and
+//               internally projects the supplied points into 2-D for
+//               passing to the underlying Triangulator object.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA_MATHUTIL Triangulator3 : public Triangulator {
+PUBLISHED:
+  Triangulator3();
+
+  void clear();
+  int add_vertex(const LPoint3d &point);
+  INLINE int add_vertex(double x, double y, double z);
+
+  INLINE int get_num_vertices() const;
+  INLINE const LPoint3d &get_vertex(int n) const;
+  MAKE_SEQ(get_vertices, get_num_vertices, get_vertex);
+
+  void triangulate();
+  INLINE const LPlaned &get_plane() const;
+
+private:
+  typedef pvector<LPoint3d> Vertices3;
+  Vertices3 _vertices3;
+
+  LPlaned _plane;
+};
+
+#include "triangulator3.I"
+
+#endif

+ 13 - 5
panda/src/putil/string_utils.cxx

@@ -402,13 +402,21 @@ string_to_double(const string &str, double &result) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: string_to_float
-//  Description: Another flavor of string_to_float(), this one
-//               returns true if the string is a perfectly valid
-//               number (and sets result to that value), or false
-//               otherwise.
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool
+string_to_float(const string &str, float &result) {
+  string tail;
+  result = (float)string_to_double(str, tail);
+  return tail.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: string_to_stdfloat
+//  Description: 
 ////////////////////////////////////////////////////////////////////
 bool
-string_to_float(const string &str, PN_stdfloat &result) {
+string_to_stdfloat(const string &str, PN_stdfloat &result) {
   string tail;
   result = (PN_stdfloat)string_to_double(str, tail);
   return tail.empty();

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

@@ -58,7 +58,8 @@ EXPCL_PANDA_PUTIL int string_to_int(const string &str, string &tail);
 EXPCL_PANDA_PUTIL bool string_to_int(const string &str, int &result);
 EXPCL_PANDA_PUTIL double string_to_double(const string &str, string &tail);
 EXPCL_PANDA_PUTIL bool string_to_double(const string &str, double &result);
-EXPCL_PANDA_PUTIL bool string_to_float(const string &str, PN_stdfloat &result);
+EXPCL_PANDA_PUTIL bool string_to_float(const string &str, float &result);
+EXPCL_PANDA_PUTIL bool string_to_stdfloat(const string &str, PN_stdfloat &result);
 
 // Convenience function to make a string from anything that has an
 // ostream operator.

+ 30 - 0
pandatool/src/converter/somethingToEggConverter.cxx

@@ -101,6 +101,22 @@ supports_compressed() const {
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: SomethingToEggConverter::supports_convert_to_node
+//       Access: Published, Virtual
+//  Description: Returns true if this converter can directly convert
+//               the model type to internal Panda memory structures,
+//               given the indicated options, or false otherwise.  If
+//               this returns true, then convert_to_node() may be
+//               called to perform the conversion, which may be faster
+//               than calling convert_file() if the ultimate goal is a
+//               PandaNode anyway.
+////////////////////////////////////////////////////////////////////
+bool SomethingToEggConverter::
+supports_convert_to_node(const LoaderOptions &options) const {
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: SomethingToEggConverter::get_input_units
 //       Access: Public, Virtual
@@ -115,6 +131,20 @@ get_input_units() {
   return DU_invalid;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: SomethingToEggConverter::convert_to_node
+//       Access: Public, Virtual
+//  Description: Reads the input file and directly produces a
+//               ready-to-render model file as a PandaNode.  Returns
+//               NULL on failure, or if it is not supported.  (This
+//               functionality is not supported by all converter
+//               types; see supports_convert_to_node()).
+////////////////////////////////////////////////////////////////////
+PT(PandaNode) SomethingToEggConverter::
+convert_to_node(const LoaderOptions &options, const Filename &filename) {
+  return NULL;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: SomethingToEggConverter::handle_external_reference
 //       Access: Public

+ 4 - 0
pandatool/src/converter/somethingToEggConverter.h

@@ -23,9 +23,11 @@
 #include "pathReplace.h"
 #include "pointerTo.h"
 #include "distanceUnit.h"
+#include "pandaNode.h"
 
 class EggData;
 class EggGroupNode;
+class LoaderOptions;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : SomethingToEggConverter
@@ -103,8 +105,10 @@ public:
   virtual string get_extension() const=0;
   virtual string get_additional_extensions() const;
   virtual bool supports_compressed() const;
+  virtual bool supports_convert_to_node(const LoaderOptions &options) const;
 
   virtual bool convert_file(const Filename &filename)=0;
+  virtual PT(PandaNode) convert_to_node(const LoaderOptions &options, const Filename &filename);
   virtual DistanceUnit get_input_units();
 
   bool handle_external_reference(EggGroupNode *egg_parent,

+ 2 - 2
pandatool/src/objegg/Sources.pp

@@ -15,11 +15,11 @@
 
   #define SOURCES \
     config_objegg.cxx config_objegg.h \
-    objToEggConverter.cxx objToEggConverter.h \
+    objToEggConverter.cxx objToEggConverter.h objToEggConverter.I \
     eggToObjConverter.cxx eggToObjConverter.h
 
   #define INSTALL_HEADERS \
-    objToEggConverter.h \
+    objToEggConverter.h objToEggConverter.I \
     eggToObjConverter.h
 
 #end ss_lib_target

+ 63 - 0
pandatool/src/objegg/objToEggConverter.I

@@ -0,0 +1,63 @@
+// Filename: objToEggConverter.I
+// Created by:  drose (03Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::VertexEntry::operator <
+//       Access: Public
+//  Description: Provides a unique but arbitrary ordering for
+//               VertexEntry objects in a map.
+////////////////////////////////////////////////////////////////////
+INLINE bool ObjToEggConverter::VertexEntry::
+operator < (const VertexEntry &other) const {
+  if (_vi != other._vi) {
+    return _vi < other._vi;
+  }
+  if (_vti != other._vti) {
+    return _vti < other._vti;
+  }
+
+  // It's important that these two tests are made last, so we can find
+  // the first vertex that has any normal but also matches the above
+  // properties.
+  if (_vni != other._vni) {
+    return _vni < other._vni;
+  }
+  if (_synth_vni != other._synth_vni) {
+    return _synth_vni < other._synth_vni;
+  }
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::VertexEntry::operator ==
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE bool ObjToEggConverter::VertexEntry::
+operator == (const VertexEntry &other) const {
+  return (_vi == other._vi && _vti == other._vti &&
+          _vni == other._vni && _synth_vni == other._synth_vni);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::VertexEntry::matches_except_normal
+//       Access: Public
+//  Description: Returns true if all the properties except _vni and _synth_vni
+//               are equivalent.
+////////////////////////////////////////////////////////////////////
+INLINE bool ObjToEggConverter::VertexEntry::
+matches_except_normal(const VertexEntry &other) const {
+  return (_vi == other._vi && _vti == other._vti);
+}

+ 725 - 117
pandatool/src/objegg/objToEggConverter.cxx

@@ -19,7 +19,14 @@
 #include "streamReader.h"
 #include "virtualFileSystem.h"
 #include "eggPolygon.h"
+#include "nodePath.h"
+#include "geomTriangles.h"
+#include "geomPoints.h"
+#include "colorAttrib.h"
+#include "shadeModelAttrib.h"
 #include "dcast.h"
+#include "triangulator3.h"
+#include "config_egg2pg.h"
 
 ////////////////////////////////////////////////////////////////////
 //     Function: ObjToEggConverter::Constructor
@@ -95,6 +102,22 @@ supports_compressed() const {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::supports_convert_to_node
+//       Access: Published, Virtual
+//  Description: Returns true if this converter can directly convert
+//               the model type to internal Panda memory structures,
+//               given the indicated options, or false otherwise.  If
+//               this returns true, then convert_to_node() may be
+//               called to perform the conversion, which may be faster
+//               than calling convert_file() if the ultimate goal is a
+//               PandaNode anyway.
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+supports_convert_to_node(const LoaderOptions &options) const {
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ObjToEggConverter::convert_file
 //       Access: Public, Virtual
@@ -116,10 +139,40 @@ convert_file(const Filename &filename) {
   return !had_error();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::convert_to_node
+//       Access: Public, Virtual
+//  Description: Reads the input file and directly produces a
+//               ready-to-render model file as a PandaNode.  Returns
+//               NULL on failure, or if it is not supported.  (This
+//               functionality is not supported by all converter
+//               types; see supports_convert_to_node()).
+////////////////////////////////////////////////////////////////////
+PT(PandaNode) ObjToEggConverter::
+convert_to_node(const LoaderOptions &options, const Filename &filename) {
+  clear_error();
+
+  _root_node = new PandaNode("");
+  _current_vertex_data = new VertexData(_root_node, "root");
+
+  if (!process_node(filename)) {
+    _error = true;
+  }
+
+  _current_vertex_data->close_geom(this);
+  delete _current_vertex_data;
+
+  if (had_error()) {
+    return NULL;
+  }
+
+  return _root_node;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ObjToEggConverter::process
 //       Access: Protected
-//  Description: 
+//  Description: Reads the file and converts it to egg structures.
 ////////////////////////////////////////////////////////////////////
 bool ObjToEggConverter::
 process(const Filename &filename) {
@@ -131,11 +184,14 @@ process(const Filename &filename) {
     return false;
   }
 
-  _vi = 1;
-  _vti = 1;
-  _xvti = 1;
-  _vni = 1;
+  _v_table.clear();
+  _vn_table.clear();
+  _rgb_table.clear();
+  _vt_table.clear();
   _ref_plane_res.set(1.0, 1.0);
+  _v4_given = false;
+  _vt3_given = false;
+  _f_given = false;
 
   _vpool = new EggVertexPool("vpool");
   _egg_data->add_child(_vpool);
@@ -192,6 +248,8 @@ process_line(const string &line) {
     return process_vt(words);
   } else if (tag == "xvt") {
     return process_xvt(words);
+  } else if (tag == "xvc") {
+    return process_xvc(words);
   } else if (tag == "vn") {
     return process_vn(words);
   } else if (tag == "f") {
@@ -231,9 +289,9 @@ process_ref_plane_res(const string &line) {
   }
 
   bool okflag = true;
-  LPoint3d pos;
-  okflag &= string_to_double(words[1], _ref_plane_res[0]);
-  okflag &= string_to_double(words[2], _ref_plane_res[1]);
+  LPoint3 pos;
+  okflag &= string_to_stdfloat(words[1], _ref_plane_res[0]);
+  okflag &= string_to_stdfloat(words[2], _ref_plane_res[1]);
 
   if (!okflag) {
     objegg_cat.error()
@@ -251,17 +309,24 @@ process_ref_plane_res(const string &line) {
 ////////////////////////////////////////////////////////////////////
 bool ObjToEggConverter::
 process_v(vector_string &words) {
-  if (words.size() != 4 && words.size() != 7) {
+  if (words.size() != 4 && words.size() != 5 && 
+      words.size() != 7 && words.size() != 8) {
     objegg_cat.error()
       << "Wrong number of tokens at line " << _line_number << "\n";
     return false;
   }
   
   bool okflag = true;
-  LPoint3d pos;
-  okflag &= string_to_double(words[1], pos[0]);
-  okflag &= string_to_double(words[2], pos[1]);
-  okflag &= string_to_double(words[3], pos[2]);
+  LPoint4 pos;
+  okflag &= string_to_stdfloat(words[1], pos[0]);
+  okflag &= string_to_stdfloat(words[2], pos[1]);
+  okflag &= string_to_stdfloat(words[3], pos[2]);
+  if (words.size() == 5 || words.size() == 8) {
+    okflag &= string_to_stdfloat(words[4], pos[3]);
+    _v4_given = true;
+  } else {
+    pos[3] = 1.0;
+  }
 
   if (!okflag) {
     objegg_cat.error()
@@ -269,27 +334,28 @@ process_v(vector_string &words) {
     return false;
   }
 
-  EggVertex *vertex = get_vertex(_vi);
-  vertex->set_pos(pos);
+  _v_table.push_back(pos);
 
   // Meshlab format might include an RGB color following the vertex
   // position.
-  if (words.size() == 7) {
-    double r, g, b;
-    okflag &= string_to_double(words[4], r);
-    okflag &= string_to_double(words[5], g);
-    okflag &= string_to_double(words[6], b);
+  if (words.size() == 7 && words.size() == 8) {
+    size_t si = words.size();
+    LVecBase3 rgb;
+    okflag &= string_to_stdfloat(words[si - 3], rgb[0]);
+    okflag &= string_to_stdfloat(words[si - 2], rgb[1]);
+    okflag &= string_to_stdfloat(words[si - 1], rgb[2]);
 
     if (!okflag) {
       objegg_cat.error()
         << "Invalid number at line " << _line_number << "\n";
       return false;
     }
-    vertex->set_color(LColor(r, g, b, 1.0));
+    while (_rgb_table.size() + 1 < _v_table.size()) {
+      _rgb_table.push_back(LVecBase3(1.0, 1.0, 1.0));
+    }
+    _rgb_table.push_back(rgb);
   }
 
-  ++_vi;
-
   return true;
 }
 
@@ -307,11 +373,14 @@ process_vt(vector_string &words) {
   }
   
   bool okflag = true;
-  LTexCoord3d uvw;
-  okflag &= string_to_double(words[1], uvw[0]);
-  okflag &= string_to_double(words[2], uvw[1]);
+  LTexCoord3 uvw;
+  okflag &= string_to_stdfloat(words[1], uvw[0]);
+  okflag &= string_to_stdfloat(words[2], uvw[1]);
   if (words.size() == 4) {
-    okflag &= string_to_double(words[3], uvw[2]);
+    okflag &= string_to_stdfloat(words[3], uvw[2]);
+    _vt3_given = true;
+  } else {
+    uvw[2] = 0.0;
   }
 
   if (!okflag) {
@@ -320,13 +389,7 @@ process_vt(vector_string &words) {
     return false;
   }
 
-  EggVertex *vertex = get_vertex(_vti);
-  if (words.size() == 4) {
-    vertex->set_uvw("", uvw);
-  } else {
-    vertex->set_uv("", LTexCoordd(uvw[0], uvw[1]));
-  }
-  ++_vti;
+  _vt_table.push_back(uvw);
 
   return true;
 }
@@ -348,9 +411,9 @@ process_xvt(vector_string &words) {
   }
   
   bool okflag = true;
-  LTexCoordd uv;
-  okflag &= string_to_double(words[1], uv[0]);
-  okflag &= string_to_double(words[2], uv[1]);
+  LTexCoord uv;
+  okflag &= string_to_stdfloat(words[1], uv[0]);
+  okflag &= string_to_stdfloat(words[2], uv[1]);
 
   if (!okflag) {
     objegg_cat.error()
@@ -361,13 +424,22 @@ process_xvt(vector_string &words) {
   uv[0] /= _ref_plane_res[0];
   uv[1] = 1.0 - uv[1] / _ref_plane_res[1];
 
-  EggVertex *vertex = get_vertex(_xvti);
-  vertex->set_uv("", uv);
-  ++_xvti;
+  _xvt_table.push_back(uv);
 
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::process_xvc
+//       Access: Protected
+//  Description: "xvc" is another extended column invented by DRZ.  We
+//               quietly ignore it.
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+process_xvc(vector_string &words) {
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ObjToEggConverter::process_vn
 //       Access: Protected
@@ -382,10 +454,10 @@ process_vn(vector_string &words) {
   }
   
   bool okflag = true;
-  LVector3d normal;
-  okflag &= string_to_double(words[1], normal[0]);
-  okflag &= string_to_double(words[2], normal[1]);
-  okflag &= string_to_double(words[3], normal[2]);
+  LVector3 normal;
+  okflag &= string_to_stdfloat(words[1], normal[0]);
+  okflag &= string_to_stdfloat(words[2], normal[1]);
+  okflag &= string_to_stdfloat(words[3], normal[2]);
 
   if (!okflag) {
     objegg_cat.error()
@@ -394,9 +466,7 @@ process_vn(vector_string &words) {
   }
   normal.normalize();
 
-  EggVertex *vertex = get_vertex(_vni);
-  vertex->set_normal(normal);
-  ++_vni;
+  _vn_table.push_back(normal);
 
   return true;
 }
@@ -408,6 +478,8 @@ process_vn(vector_string &words) {
 ////////////////////////////////////////////////////////////////////
 bool ObjToEggConverter::
 process_f(vector_string &words) {
+  _f_given = true;
+
   PT(EggPolygon) poly = new EggPolygon;
   for (size_t i = 1; i < words.size(); ++i) {
     EggVertex *vertex = get_face_vertex(words[i]);
@@ -451,107 +523,643 @@ process_g(vector_string &words) {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::get_vertex
+//     Function: ObjToEggConverter::get_face_vertex
 //       Access: Protected
-//  Description: Returns or creates a vertex in the vpool with the
-//               given index.
+//  Description: Returns or creates a vertex in the vpool according to
+//               the indicated face reference.
 ////////////////////////////////////////////////////////////////////
 EggVertex *ObjToEggConverter::
-get_vertex(int n) {
-  if (n < 0) {
-    // A negative index means to count backward from the end.
-    n = _vi + n;
+get_face_vertex(const string &reference) {
+  VertexEntry entry(this, reference);
+
+  // Synthesize a vertex.
+  EggVertex synth;
+
+  if (entry._vi != 0) {
+    if (_v4_given) {
+      synth.set_pos(LCAST(double, _v_table[entry._vi - 1]));
+    } else {
+      LPoint4 pos = _v_table[entry._vi - 1];
+      synth.set_pos(LPoint3d(pos[0], pos[1], pos[2]));
+    }
+
+    if (entry._vi - 1 < (int)_rgb_table.size()) {
+      // We have a per-vertex color too.
+      LRGBColor rgb = _rgb_table[entry._vi - 1];
+      LColorf rgba(rgb[0], rgb[1], rgb[2], 1.0);
+      synth.set_color(rgba);
+    }
   }
-  EggVertex *vertex = _vpool->get_vertex(n);
-  if (vertex == NULL) {
-    vertex = new EggVertex;
-    _vpool->add_vertex(vertex, n);
+
+  if (entry._vti != 0) {
+    // We have a texture coordinate; apply it.
+    if (_vt3_given) {
+      synth.set_uvw("", LCAST(double, _vt_table[entry._vti - 1]));
+    } else {
+      LTexCoord3 uvw = _vt_table[entry._vti - 1];
+      synth.set_uv("", LTexCoordd(uvw[0], uvw[1]));
+    }
+  } else if (entry._vi - 1 < (int)_xvt_table.size()) {
+    // We have an xvt texture coordinate.
+    synth.set_uv("", LCAST(double, _xvt_table[entry._vi - 1]));
+  }
+
+  if (entry._vni != 0) {
+    // We have a normal; apply it.
+    synth.set_normal(LCAST(double, _vn_table[entry._vni - 1]));
   }
 
-  return vertex;
+  return _vpool->create_unique_vertex(synth);
 }
 
+
 ////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::get_face_vertex
+//     Function: ObjToEggConverter::process_node
 //       Access: Protected
-//  Description: Returns or creates a vertex in the vpool according to
-//               the indicated face reference.
+//  Description: Reads the file and converts it to PandaNode structures.
 ////////////////////////////////////////////////////////////////////
-EggVertex *ObjToEggConverter::
-get_face_vertex(const string &reference) {
+bool ObjToEggConverter::
+process_node(const Filename &filename) {
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+  istream *strm = vfs->open_read_file(filename, true);
+  if (strm == NULL) {
+    objegg_cat.error() 
+      << "Couldn't read " << filename << "\n";
+    return false;
+  }
+
+  _v_table.clear();
+  _vn_table.clear();
+  _rgb_table.clear();
+  _vt_table.clear();
+  _ref_plane_res.set(1.0, 1.0);
+  _v4_given = false;
+  _vt3_given = false;
+  _f_given = false;
+
+  StreamReader sr(strm, true);
+  string line = sr.readline();
+  _line_number = 1;
+  while (!line.empty()) {
+    line = trim(line);
+    if (line.empty()) {
+      line = sr.readline();
+      continue;
+    }
+
+    if (line.substr(0, 15) == "#_ref_plane_res") {
+      process_ref_plane_res(line);
+      line = sr.readline();
+      continue;
+    }
+
+    if (line[0] == '#') {
+      line = sr.readline();
+      continue;
+    }
+
+    if (!process_line_node(line)) {
+      return false;
+    }
+    line = sr.readline();
+    ++_line_number;
+  }
+
+  if (!_f_given) {
+    generate_points();
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::process_line_node
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+process_line_node(const string &line) {
   vector_string words;
-  tokenize(reference, words, "/", false);
-  nassertr(!words.empty(), NULL);
+  tokenize(line, words, " \t", true);
+  nassertr(!words.empty(), false);
 
-  vector<EggVertex *> vertices;
-  vertices.reserve(words.size());
-  for (size_t i = 0; i < words.size(); ++i) {
-    if (trim(words[i]).empty()) {
-      if (i == 0) {
-        objegg_cat.error()                    
-          << "Invalid null vertex at line " << _line_number << "\n";
-        return NULL;
-      } else {
-        vertices.push_back(vertices[0]);
+  string tag = words[0];
+  if (tag == "v") {
+    return process_v(words);
+  } else if (tag == "vt") {
+    return process_vt(words);
+  } else if (tag == "xvt") {
+    return process_xvt(words);
+  } else if (tag == "xvc") {
+    return process_xvc(words);
+  } else if (tag == "vn") {
+    return process_vn(words);
+  } else if (tag == "f") {
+    return process_f_node(words);
+  } else if (tag == "g") {
+    return process_g_node(words);
+  } else {
+    bool inserted = _ignored_tags.insert(tag).second;
+    if (inserted) {
+      objegg_cat.info()
+        << "Ignoring tag " << tag << "\n";
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::process_f_node
+//       Access: Protected
+//  Description: Defines a face in the obj file.
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+process_f_node(vector_string &words) {
+  _f_given = true;
+
+  bool all_vn = true;
+  int non_vn_index = -1;
+
+  pvector<VertexEntry> verts;
+  verts.reserve(words.size() - 1);
+  for (size_t i = 1; i < words.size(); ++i) {
+    VertexEntry entry(this, words[i]);
+    verts.push_back(entry);
+    if (entry._vni == 0) {
+      all_vn = false;
+      non_vn_index = i;
+    }
+  }
+
+  if (verts.size() < 3) {
+    // Not enough vertices.
+    objegg_cat.error() 
+      << "Degenerate face at " << _line_number << "\n";
+    return false;
+  }
+
+  if (!all_vn) {
+    // Synthesize a normal if we need it.
+    LNormal normal = LNormal::zero();
+    for (size_t i = 0; i < verts.size(); ++i) {
+      int vi0 = verts[i]._vi;
+      int vi1 = verts[(i + 1) % verts.size()]._vi;
+      if (vi0 == 0 || vi1 == 0) {
         continue;
       }
+      const LVecBase4f &p0 = _v_table[vi0 - 1];
+      const LVecBase4f &p1 = _v_table[vi1 - 1];
+      
+      normal[0] += p0[1] * p1[2] - p0[2] * p1[1];
+      normal[1] += p0[2] * p1[0] - p0[0] * p1[2];
+      normal[2] += p0[0] * p1[1] - p0[1] * p1[0];
+    }
+    normal.normalize();
+    int synth_vni = add_synth_normal(normal);
+
+    // Roll the polygon around to put the "non-vn" vertex at the
+    // end.
+    while (non_vn_index + 1 < verts.size()) {
+      VertexEntry entry = verts.back();
+      verts.pop_back();
+      verts.insert(verts.begin(), entry);
+      ++non_vn_index;
+    }
+
+    verts.back()._synth_vni = synth_vni;
+  }
+
+  Triangulator3 tri;
+  int num_tris = 1;
+
+  if (verts.size() != 3) {
+    // We have to triangulate a higher-order polygon.
+    for (size_t i = 0; i < verts.size(); ++i) {
+      const LVecBase4 &p = _v_table[verts[i]._vi - 1];
+      tri.add_vertex(p[0], p[1], p[2]);
+      tri.add_polygon_vertex(i);
     }
 
+    tri.triangulate();
+    num_tris = tri.get_num_triangles();
+  }
+
+  if (_current_vertex_data->_prim->get_num_vertices() + 3 * num_tris > egg_max_indices ||
+      _current_vertex_data->_entries.size() + verts.size() > egg_max_vertices) {
+    // We'll exceed our specified limit with these triangles; start a new Geom.
+    _current_vertex_data->close_geom(this);
+  }
+    
+  pvector<int> indices;
+  indices.reserve(verts.size());
+  for (size_t i = 0; i < verts.size(); ++i) {
+    int index = _current_vertex_data->add_vertex(this, verts[i]);
+    indices.push_back(index);
+  }
+
+  if (indices.size() == 3) {
+    // It's already a triangle; add it directly.
+    _current_vertex_data->add_triangle(indices[0], indices[1], indices[2]);
+  } else {
+    // We have to triangulate a higher-order polygon.
+    for (size_t i = 0; i < verts.size(); ++i) {
+      const LVecBase4 &p = _v_table[verts[i]._vi - 1];
+      tri.add_vertex(p[0], p[1], p[2]);
+      tri.add_polygon_vertex(i);
+    }
+
+    tri.triangulate();
+    int num_tris = tri.get_num_triangles();
+    for (int ti = 0; ti < num_tris; ++ti) {
+      int i0 = tri.get_triangle_v0(ti);
+      int i1 = tri.get_triangle_v1(ti);
+      int i2 = tri.get_triangle_v2(ti);
+      _current_vertex_data->add_triangle(indices[i0], indices[i1], indices[i2]);
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::process_g_node
+//       Access: Protected
+//  Description: Defines a group in the obj file.
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+process_g_node(vector_string &words) {
+  _current_vertex_data->close_geom(this);
+  delete _current_vertex_data;
+  _current_vertex_data = NULL;
+
+  NodePath np(_root_node);
+
+  // We assume the group names define a hierarchy of more-specific to
+  // less-specific group names, so that the first group name is the
+  // bottommost node, and the last group name is the topmost node.
+
+  // Thus, iterate from the back to the front.
+  size_t i = words.size();
+  string name;
+  while (i > 2) {
+    --i;
+    name = words[i];
+    NodePath child = np.find(name);
+    if (!child) {
+      child = np.attach_new_node(name);
+    }
+    np = child;
+  }
+
+  if (i > 1) {
+    --i;
+    name = words[i];
+  }
+
+  _current_vertex_data = new VertexData(np.node(), name);
+  
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::generate_points
+//       Access: Protected
+//  Description: If an obj file defines no faces, create a bunch of
+//               GeomPoints to illustrate the vertex positions at
+//               least.
+////////////////////////////////////////////////////////////////////
+void ObjToEggConverter::
+generate_points() {
+  CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3();
+  PT(GeomVertexData) vdata = new GeomVertexData("points", format, GeomEnums::UH_static);
+  vdata->set_num_rows(_v_table.size());
+  GeomVertexWriter vertex_writer(vdata, InternalName::get_vertex());
+
+  for (size_t vi = 0; vi < _v_table.size(); ++vi) {
+    const LVecBase4 &p = _v_table[vi];
+    vertex_writer.add_data3(p[0], p[1], p[2]);
+  }
+
+  PT(GeomPrimitive) prim = new GeomPoints(GeomEnums::UH_static);
+  prim->add_next_vertices(_v_table.size());
+  prim->close_primitive();
+
+  PT(Geom) geom = new Geom(vdata);
+  geom->add_primitive(prim);
+
+  PT(GeomNode) geom_node = new GeomNode("points");
+  geom_node->add_geom(geom);
+  _root_node->add_child(geom_node);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::add_synth_normal
+//       Access: Private
+//  Description: Adds a new normal to the synth_vn table, or returns
+//               an existing normal.  In either case returns the
+//               1-based index number to the normal.
+////////////////////////////////////////////////////////////////////
+int ObjToEggConverter::
+add_synth_normal(const LVecBase3 &normal) {
+  pair<UniqueVec3Table::iterator, bool> result = _unique_synth_vn_table.insert(UniqueVec3Table::value_type(normal, _unique_synth_vn_table.size()));
+  UniqueVec3Table::iterator ni = result.first;
+  int index = (*ni).second;
+
+  if (result.second) {
+    // If the normal was added to the table, it's a unique normal, and
+    // now we have to add it to the table too.
+    _synth_vn_table.push_back(normal);
+  }
+
+  return index + 1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::VertexEntry::Constructor
+//       Access: Public
+//  Description: Creates a VertexEntry from the n/n/n string format in
+//               the obj file face reference.
+////////////////////////////////////////////////////////////////////
+ObjToEggConverter::VertexEntry::
+VertexEntry(const ObjToEggConverter *converter, const string &obj_vertex) {
+  _vi = 0;
+  _vti = 0;
+  _vni = 0;
+  _synth_vni = 0;
+
+  vector_string words;
+  tokenize(obj_vertex, words, "/", false);
+  nassertv(!words.empty());
+  
+  for (size_t i = 0; i < words.size(); ++i) {
     int index;
-    if (!string_to_int(words[i], index)) {
-      objegg_cat.error()
-        << "Invalid integer " << words[i] << " at line " << _line_number << "\n";
-      return NULL;
+    if (trim(words[i]).empty()) {
+      index = 0;
+    } else {
+      if (!string_to_int(words[i], index)) {
+        index = 0;
+      }
     }
-    EggVertex *vertex = get_vertex(index);
-    if (vertex == NULL){ 
-      objegg_cat.error()
-        << "Invalid vertex " << index << " at line " << _line_number << "\n";
-      return NULL;
+
+    switch (i) {
+    case 0:
+      _vi = index;
+      if (_vi < 0) {
+        _vi = (int)converter->_v_table.size() + _vi;
+      }
+      if (_vi < 0 || _vi - 1 >= (int)converter->_v_table.size()) {
+        _vi = 0;
+      }
+      break;
+
+    case 1:
+      _vti = index;
+      if (_vti < 0) {
+        _vti = (int)converter->_vt_table.size() + _vti;
+      }
+      if (_vti < 0 || _vti - 1 >= (int)converter->_vt_table.size()) {
+        _vti = 0;
+      }
+      break;
+
+    case 2:
+      _vni = index;
+      if (_vni < 0) {
+        _vni = (int)converter->_vn_table.size() + _vni;
+      }
+      if (_vni < 0 || _vni - 1 >= (int)converter->_vn_table.size()) {
+        _vni = 0;
+      }
+      break;
     }
-    vertices.push_back(vertex);
   }
-  nassertr(!vertices.empty(), NULL);
-  nassertr(vertices.size() == words.size(), NULL);
+}
 
-  if (vertices.size() == 1) {
-    // Just a pos reference.
-    return vertices[0];
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::VertexData::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+ObjToEggConverter::VertexData::
+VertexData(PandaNode *parent, const string &name) : 
+  _parent(parent), _name(name) 
+{
+  _geom_node = NULL;
+
+  _v4_given = false;
+  _vt3_given = false;
+  _vt_given = false;
+  _rgb_given = false;
+  _vn_given = false;
+
+  _prim = new GeomTriangles(GeomEnums::UH_static);
+}
 
-  } else if (vertices.size() == 2) {
-    // Pos + uv.
-    if (vertices[0] == vertices[1]) {
-      return vertices[0];
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::VertexData::add_vertex
+//       Access: Public
+//  Description: Adds a new entry to the vertex data for the indicated
+//               VertexEntry, or returns an equivalent vertex already
+//               present.
+////////////////////////////////////////////////////////////////////
+int ObjToEggConverter::VertexData::
+add_vertex(const ObjToEggConverter *converter, const VertexEntry &entry) {
+  pair<UniqueVertexEntries::iterator, bool> result;
+  UniqueVertexEntries::iterator ni;
+  int index;
+
+  if (entry._vni != 0 || entry._synth_vni != 0) {
+    // If we are storing a vertex with a normal, see if we
+    // have already stored a vertex without a normal first.
+    VertexEntry no_normal(entry);
+    no_normal._vni = 0;
+    no_normal._synth_vni = 0;
+    ni = _unique_entries.find(no_normal);
+    if (ni != _unique_entries.end()) {
+      // We did have such a vertex!  In this case, repurpose this
+      // vertex, resetting it to contain this normal.
+      index = (*ni).second;
+      _unique_entries.erase(ni);
+      result = _unique_entries.insert(UniqueVertexEntries::value_type(entry, index));
+      nassertr(result.second, index);
+      nassertr(_entries[index] == no_normal, index);
+      _entries[index]._vni = entry._vni;
+      _entries[index]._synth_vni = entry._synth_vni;
+      return index;
+    }
+  } else if (entry._vni == 0 && entry._synth_vni == 0) {
+    // If we are storing a vertex *without* any normal, see if we have
+    // already stored a vertex with a normal first.
+    ni = _unique_entries.lower_bound(entry);
+    if (ni != _unique_entries.end() && (*ni).first.matches_except_normal(entry)) {
+      // We had such a vertex, so use it.
+      index = (*ni).second;
+      return index;
+    }
+  }
+      
+  // We didn't already have a vertex we could repurpose, so try to add
+  // exactly the desired vertex.
+  result = _unique_entries.insert(UniqueVertexEntries::value_type(entry, _entries.size()));
+  ni = result.first;
+  index = (*ni).second;
+
+  if (result.second) {
+    // If the vertex was added to the table, it's a unique vertex, and
+    // now we have to add it to the vertex data too.
+    _entries.push_back(entry);
+
+    if (converter->_v4_given) {
+      _v4_given = true;
+    }
+    if (converter->_vt3_given) {
+      _vt3_given = true;
+    }
+    if (entry._vti != 0) {
+      _vt_given = true;
+    } else if (entry._vi - 1 < (int)converter->_xvt_table.size()) {
+      // We have an xvt texture coordinate.
+      _vt_given = true;
+    }
+    if (entry._vi - 1 < (int)converter->_rgb_table.size()) {
+      // We have a per-vertex color too.
+      _rgb_given = true;
     }
-    // Synthesize a vertex.
-    EggVertex synth(*vertices[0]);
-    if (vertices[1]->has_uv("")) {
-      synth.set_uv("", vertices[1]->get_uv(""));
-    } else if (vertices[1]->has_uvw("")) {
-      synth.set_uvw("", vertices[1]->get_uvw(""));
+    if (entry._vni != 0) {
+      _vn_given = true;
     }
+  }
+
+  return index;
+}
 
-    return _vpool->create_unique_vertex(synth);
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::VertexData::add_triangle
+//       Access: Public
+//  Description: Adds a triangle to the primitive, as a triple of
+//               three vertex index numbers (as returned by
+//               add_vertex()).
+////////////////////////////////////////////////////////////////////
+void ObjToEggConverter::VertexData::
+add_triangle(int v1, int v2, int v3) {
+  _prim->add_vertices(v1, v2, v3);
+  _prim->close_primitive();
+}
 
-  } else if (vertices.size() >= 3) {
-    // pos + uv + normal.
-    if (vertices[0] == vertices[1] && vertices[0] == vertices[2]) {
-      return vertices[0];
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::VertexData::close_geom
+//       Access: Public
+//  Description: Finishes the current geom and stores it as a child
+//               in the root.  Prepares for new geoms.
+////////////////////////////////////////////////////////////////////
+void ObjToEggConverter::VertexData::
+close_geom(const ObjToEggConverter *converter) {
+  if (_prim->get_num_vertices() != 0) {
+    // Create a new format that includes only the columns we actually
+    // used.
+    PT(GeomVertexArrayFormat) aformat = new GeomVertexArrayFormat;
+    if (_v4_given) {
+      aformat->add_column(InternalName::get_vertex(), 4, 
+                          GeomEnums::NT_stdfloat, GeomEnums::C_point);
+    } else {
+      aformat->add_column(InternalName::get_vertex(), 3, 
+                          GeomEnums::NT_stdfloat, GeomEnums::C_point);
+    }
+    
+    // We always add normals--if no normals appeared in the file, we
+    // synthesize them.
+    aformat->add_column(InternalName::get_normal(), 3,
+                        GeomEnums::NT_stdfloat, GeomEnums::C_vector);
+    
+    if (_vt_given) {
+      if (_vt3_given) {
+        aformat->add_column(InternalName::get_texcoord(), 3, 
+                            GeomEnums::NT_stdfloat, GeomEnums::C_texcoord);
+      } else {
+        aformat->add_column(InternalName::get_texcoord(), 2, 
+                            GeomEnums::NT_stdfloat, GeomEnums::C_texcoord);
+      }
+    }
+
+    if (_rgb_given) {
+      aformat->add_column(InternalName::get_color(), 4,
+                          GeomEnums::NT_uint8, GeomEnums::C_color);
+    }
+    
+    CPT(GeomVertexFormat) format = GeomVertexFormat::register_format(aformat);
+    
+    // Create and populate the vertex data.
+    PT(GeomVertexData) vdata = new GeomVertexData(_name, format, GeomEnums::UH_static);
+    GeomVertexWriter vertex_writer(vdata, InternalName::get_vertex());
+    GeomVertexWriter normal_writer(vdata, InternalName::get_normal());
+    GeomVertexWriter texcoord_writer(vdata, InternalName::get_texcoord());
+    GeomVertexWriter color_writer(vdata, InternalName::get_color());
+    
+    for (size_t i = 0; i < _entries.size(); ++i) {
+      const VertexEntry &entry = _entries[i];
+      
+      if (entry._vi != 0) {
+        vertex_writer.set_row(i);
+        vertex_writer.add_data4(converter->_v_table[entry._vi - 1]);
+      }
+      if (entry._vti != 0) {
+        texcoord_writer.set_row(i);
+        texcoord_writer.add_data3(converter->_vt_table[entry._vti - 1]);
+      } else if (entry._vi - 1 < (int)converter->_xvt_table.size()) {
+        // We have an xvt texture coordinate.
+        texcoord_writer.set_row(i);
+        texcoord_writer.add_data2(converter->_xvt_table[entry._vi - 1]);
+      }
+      if (entry._vni != 0) {
+        normal_writer.set_row(i);
+        normal_writer.add_data3(converter->_vn_table[entry._vni - 1]);
+      } else if (entry._synth_vni != 0) {
+        normal_writer.set_row(i);
+        normal_writer.add_data3(converter->_synth_vn_table[entry._synth_vni - 1]);
+      } else {
+        // In this case, the normal isn't used and doesn't matter; we
+        // fill it in a unit vector just for neatness.
+        normal_writer.set_row(i);
+        normal_writer.add_data3(0, 0, 1);
+      }        
+      if (_rgb_given) {
+        if (entry._vi - 1 < (int)converter->_rgb_table.size()) {
+          color_writer.set_row(i);
+          color_writer.add_data3(converter->_rgb_table[entry._vi - 1]);
+        }
+      }
     }
 
-    // Synthesize a vertex.
-    EggVertex synth(*vertices[0]);
-    if (vertices[1]->has_uv("")) {
-      synth.set_uv("", vertices[1]->get_uv(""));
-    } else if (vertices[1]->has_uvw("")) {
-      synth.set_uvw("", vertices[1]->get_uvw(""));
+    // Transform to zup-right.
+    vdata->transform_vertices(LMatrix4::convert_mat(CS_zup_right, CS_default));
+    
+    // Now create a Geom with this data.
+    CPT(RenderState) state = RenderState::make_empty();
+    if (_rgb_given) {
+      state = state->add_attrib(ColorAttrib::make_vertex());
+    } else {
+      state = state->add_attrib(ColorAttrib::make_flat(LColor(1, 1, 1, 1)));
     }
-    if (vertices[2]->has_normal()) {
-      synth.set_normal(vertices[2]->get_normal());
+    if (!_vn_given) {
+      // We have synthesized these normals; specify the flat-shading
+      // attrib.
+      state = state->add_attrib(ShadeModelAttrib::make(ShadeModelAttrib::M_flat));
+      _prim->set_shade_model(GeomEnums::SM_flat_last_vertex);
     }
 
-    return _vpool->create_unique_vertex(synth);
-  }
+    PT(Geom) geom = new Geom(vdata);
+    geom->add_primitive(_prim);
 
-  return NULL;
+    if (_geom_node == NULL) {
+      _geom_node = new GeomNode(_name);
+      _parent->add_child(_geom_node);
+    }
+    
+    _geom_node->add_geom(geom, state);
+  }
+  
+  _prim = new GeomTriangles(GeomEnums::UH_static);
+  _entries.clear();
+  _unique_entries.clear();
 }

+ 83 - 4
pandatool/src/objegg/objToEggConverter.h

@@ -20,6 +20,11 @@
 #include "somethingToEggConverter.h"
 #include "eggVertexPool.h"
 #include "eggGroup.h"
+#include "geomVertexData.h"
+#include "geomVertexWriter.h"
+#include "geomPrimitive.h"
+#include "geomNode.h"
+#include "pandaNode.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : ObjToEggConverter
@@ -36,8 +41,10 @@ public:
   virtual string get_name() const;
   virtual string get_extension() const;
   virtual bool supports_compressed() const;
+  virtual bool supports_convert_to_node(const LoaderOptions &options) const;
 
   virtual bool convert_file(const Filename &filename);
+  virtual PT(PandaNode) convert_to_node(const LoaderOptions &options, const Filename &filename);
 
 protected:
   bool process(const Filename &filename);
@@ -47,22 +54,94 @@ protected:
   bool process_v(vector_string &words);
   bool process_vt(vector_string &words);
   bool process_xvt(vector_string &words);
+  bool process_xvc(vector_string &words);
   bool process_vn(vector_string &words);
   bool process_f(vector_string &words);
   bool process_g(vector_string &words);
 
-  EggVertex *get_vertex(int n);
   EggVertex *get_face_vertex(const string &face_reference);
 
+  bool process_node(const Filename &filename);
+  bool process_line_node(const string &line);
+
+  bool process_f_node(vector_string &words);
+  bool process_g_node(vector_string &words);
+
+  void generate_points();
+  int add_synth_normal(const LVecBase3 &normal);
+
+  // Read from the obj file.
   int _line_number;
-  int _vi, _vti, _xvti, _vni;
+  typedef pvector<LVecBase4> Vec4Table;
+  typedef pvector<LVecBase3> Vec3Table;
+  typedef pvector<LVecBase2> Vec2Table;
+  typedef pmap<LVecBase3, int> UniqueVec3Table;
+
+  Vec4Table _v_table;
+  Vec3Table _vn_table, _rgb_table;
+  Vec3Table _vt_table;
+  Vec2Table _xvt_table;
+  Vec3Table _synth_vn_table;
+  UniqueVec3Table _unique_synth_vn_table;
+  LVecBase2 _ref_plane_res;  
+  bool _v4_given, _vt3_given;
+  bool _f_given;
+
+  pset<string> _ignored_tags;
+
+  // Structures filled when creating an egg file.
   PT(EggVertexPool) _vpool;
   PT(EggGroup) _root_group;
   EggGroup *_current_group;
 
-  LVecBase2d _ref_plane_res;  
+  // Structures filled when creating a PandaNode directly.
+  PT(PandaNode) _root_node;
 
-  pset<string> _ignored_tags;
+  class VertexEntry {
+  public:
+    VertexEntry();
+    VertexEntry(const ObjToEggConverter *converter, const string &obj_vertex);
+
+    INLINE bool operator < (const VertexEntry &other) const;
+    INLINE bool operator == (const VertexEntry &other) const;
+    INLINE bool matches_except_normal(const VertexEntry &other) const;
+
+    // The 1-based vertex, texcoord, and normal index numbers
+    // appearing in the obj file for this vertex.  0 if the index
+    // number is not given.
+    int _vi, _vti, _vni;
+
+    // The 1-based index number to the synthesized normal, if needed.
+    int _synth_vni;
+  };
+  typedef pmap<VertexEntry, int> UniqueVertexEntries;
+  typedef pvector<VertexEntry> VertexEntries;
+
+  class VertexData {
+  public:
+    VertexData(PandaNode *parent, const string &name);
+
+    int add_vertex(const ObjToEggConverter *converter, const VertexEntry &entry);
+    void add_triangle(int v1, int v2, int v3);
+    void close_geom(const ObjToEggConverter *converter);
+
+    PT(PandaNode) _parent;
+    string _name;
+    PT(GeomNode) _geom_node;
+
+    PT(GeomPrimitive) _prim;
+    VertexEntries _entries;
+    UniqueVertexEntries _unique_entries;
+
+    bool _v4_given, _vt3_given;
+    bool _vt_given, _rgb_given, _vn_given;
+  };
+
+  VertexData *_current_vertex_data;
+
+  friend class VertexData;
 };
 
+#include "objToEggConverter.I"
+
 #endif

+ 8 - 0
pandatool/src/ptloader/config_ptloader.cxx

@@ -54,6 +54,14 @@ ConfigVariableEnum<DistanceUnit> ptloader_units
           "when using libptloader to automatically convert files to Panda "
           "at load time, via e.g. \"pview myMayaFile.mb\"."));
 
+ConfigVariableBool ptloader_load_node
+("ptloader-load-node", true,
+ PRC_DESC("Specify true to allow libptloader to invoke the more efficient "
+          "but possibly-experimental code to load model files directly into "
+          "PandaNode when possible.  Specify false to force the loading to "
+          "always go through the egg library, which is more likely to be "
+          "reliable."));
+
 ////////////////////////////////////////////////////////////////////
 //     Function: init_libptloader
 //  Description: Initializes the library.  This must be called at

+ 2 - 0
pandatool/src/ptloader/config_ptloader.h

@@ -20,11 +20,13 @@
 #include "dconfig.h"
 #include "distanceUnit.h"
 #include "configVariableEnum.h"
+#include "configVariableBool.h"
 
 ConfigureDecl(config_ptloader, EXPCL_PTLOADER, EXPTP_PTLOADER);
 NotifyCategoryDecl(ptloader, EXPCL_PTLOADER, EXPTP_PTLOADER);
 
 extern ConfigVariableEnum<DistanceUnit> ptloader_units;
+extern ConfigVariableBool ptloader_load_node;
 
 extern EXPCL_PTLOADER void init_libptloader();
 

+ 14 - 2
pandatool/src/ptloader/loaderFileTypePandatool.cxx

@@ -160,8 +160,6 @@ load_file(const Filename &path, const LoaderOptions &options,
   PT(PandaNode) result;
 
   SomethingToEggConverter *loader = _loader->make_copy();
-  PT(EggData) egg_data = new EggData;
-  loader->set_egg_data(egg_data);
 
   DSearchPath file_path;
   file_path.append_directory(path.get_dirname());
@@ -185,6 +183,20 @@ load_file(const Filename &path, const LoaderOptions &options,
     break;
   }
 
+  // Try to convert directly to PandaNode first, if the converter type
+  // supports it.
+  if (ptloader_load_node && loader->supports_convert_to_node(options)) {
+    result = loader->convert_to_node(options, path);
+    if (!result.is_null()) {
+      return result;
+    }
+  }
+
+  // If the converter type doesn't support the direct PandaNode
+  // conversion, take the slower route through egg instead.
+  PT(EggData) egg_data = new EggData;
+  loader->set_egg_data(egg_data);
+
   if (loader->convert_file(path)) {
     DistanceUnit input_units = loader->get_input_units();
     if (input_units != DU_invalid && ptloader_units != DU_invalid &&