Browse Source

Convert from DOS back to Unix newlines

rdb 10 years ago
parent
commit
0f2f27689a

+ 230 - 230
panda/src/egg/config_egg.cxx

@@ -1,230 +1,230 @@
-// Filename: config_egg.cxx
-// Created by:  drose (19Mar00)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 "config_egg.h"
-#include "eggRenderMode.h"
-#include "eggAnimData.h"
-#include "eggAnimPreload.h"
-#include "eggAttributes.h"
-#include "eggBin.h"
-#include "eggBinMaker.h"
-#include "eggComment.h"
-#include "eggCompositePrimitive.h"
-#include "eggCoordinateSystem.h"
-#include "eggCurve.h"
-#include "eggExternalReference.h"
-#include "eggFilenameNode.h"
-#include "eggGroup.h"
-#include "eggGroupNode.h"
-#include "eggGroupUniquifier.h"
-#include "eggLine.h"
-#include "eggMaterial.h"
-#include "eggNameUniquifier.h"
-#include "eggNamedObject.h"
-#include "eggNode.h"
-#include "eggNurbsCurve.h"
-#include "eggNurbsSurface.h"
-#include "eggObject.h"
-#include "eggPatch.h"
-#include "eggPoint.h"
-#include "eggPolygon.h"
-#include "eggPolysetMaker.h"
-#include "eggPoolUniquifier.h"
-#include "eggPrimitive.h"
-#include "eggSAnimData.h"
-#include "eggSurface.h"
-#include "eggSwitchCondition.h"
-#include "eggTable.h"
-#include "eggTexture.h"
-#include "eggTriangleFan.h"
-#include "eggTriangleStrip.h"
-#include "eggUserData.h"
-#include "eggVertex.h"
-#include "eggVertexPool.h"
-#include "eggVertexUV.h"
-#include "eggVertexAux.h"
-#include "eggXfmAnimData.h"
-#include "eggXfmSAnim.h"
-
-#include "dconfig.h"
-
-Configure(config_egg);
-NotifyCategoryDef(egg, "");
-
-ConfigureFn(config_egg) {
-  init_libegg();
-}
-
-ConfigVariableBool egg_support_old_anims
-("egg-support-old-anims", true,
- PRC_DESC("Set this true to support loading of old character animation files, which "
-          "had the convention that the order \"phr\" implied a reversed roll."));
-
-ConfigVariableBool egg_mesh
-("egg-mesh", true,
- PRC_DESC("Set this true to convert triangles and higher-order polygons "
-          "into triangle strips and triangle fans when an egg file is "
-          "loaded or converted to bam.  Set this false just to triangulate "
-          "everything into independent triangles."));
-
-ConfigVariableBool egg_retesselate_coplanar
-("egg-retesselate-coplanar", false,
- PRC_DESC("If this is true, the egg loader may reverse the "
-          "tesselation direction of a single pair of planar triangles that "
-          "share the same properties, if that will help get a better "
-          "triangle strip.  In some rare cases, doing so can distort the "
-          "UV's on a face; turning this off should eliminate that artifact "
-          "(at the cost of less-effective triangle stripping)."));
-
-ConfigVariableBool egg_unroll_fans
-("egg-unroll-fans", true,
- PRC_DESC("Set this true to allow the egg loader to convert weak triangle "
-          "fans--triangles that share the same vertex but aren't "
-          "connected enough to justify making a triangle fan primitive "
-          "from them--into a series of zig-zag triangles that can make "
-          "a triangle strip that might connect better with its neighbors."));
-
-ConfigVariableBool egg_show_tstrips
-("egg-show-tstrips", false,
- PRC_DESC("Set this true to color each triangle strip a random color, with "
-          "the leading triangle a little bit darker, so you can visually "
-          "observe the quality of the triangle stripping algorithm."));
-
-ConfigVariableBool egg_show_qsheets
-("egg-show-qsheets", false,
- PRC_DESC("Set this true to color each quadsheet a random color, so you "
-          "can visually observe the quadsheet algorithm."));
-
-ConfigVariableBool egg_show_quads
-("egg-show-quads", false,
- PRC_DESC("Set this true to color each detected quad a random color, so "
-          "you can visually observe the algorithm that unifies pairs of "
-          "triangles into quads (prior to generating triangle strips)."));
-
-ConfigVariableBool egg_subdivide_polys
-("egg-subdivide-polys", true,
- PRC_DESC("This is obsolete.  In the old Geom implementation, it used to "
-          "be true to force higher-order polygons that were not otherwise "
-          "meshed to be subdivided into triangles.  In the new "
-          "Geom implementation, this happens anyway."));
-
-ConfigVariableBool egg_consider_fans
-("egg-consider-fans", false,
- PRC_DESC("Set this true to enable the egg mesher to consider making "
-          "triangle fans out of triangles that are connected at a common "
-          "vertex.  This may help if your scene involves lots of such "
-          "connected triangles, but it can also make the overall stripping "
-          "less effective (by interfering with triangle strips)."));
-
-ConfigVariableDouble egg_max_tfan_angle
-("egg-max-tfan-angle", 40.0,
- PRC_DESC("The maximum average angle per triangle to allow in a triangle "
-          "fan.  If triangles are larger than this--that is, more loosely "
-          "packed--then we figure a triangle strip is likely to do a "
-          "more effective job than a triangle fan, and the fan maker leaves "
-          "it alone."));
-
-ConfigVariableInt egg_min_tfan_tris
-("egg-min-tfan-tris", 4,
- PRC_DESC("The minimum number of triangles that must be involved in order "
-          "to generate a triangle fan.  Fewer than this is just interrupting "
-          "a triangle strip."));
-
-ConfigVariableDouble egg_coplanar_threshold
-("egg-coplanar-threshold", 0.01,
- PRC_DESC("The numerical threshold below which polygons are considered "
-          "to be coplanar.  Determined empirically."));
-
-ConfigVariableInt egg_test_vref_integrity
-("egg-test-vref-integrity", 20,
- PRC_DESC("The maximum number of vertices a primitive may have before "
-          "its vertices will no longer be checked for internal integrity.  "
-          "This is meaningful in non-production builds only."));
-
-ConfigVariableInt egg_recursion_limit
-("egg-recursion-limit", 1000,
- PRC_DESC("The maximum number of levels that recursive algorithms within "
-          "the egg library are allowed to traverse.  This is a simple hack "
-          "to prevent deeply-recursive algorithms from triggering a stack "
-          "overflow.  Set it larger to run more efficiently if your stack "
-          "allows it; set it lower if you experience stack overflows."));
-
-ConfigVariableInt egg_precision
-("egg-precision", 15,
- PRC_DESC("The number of digits of precision to write out for values in "
-          "an egg file.  Leave this at 0 to use the default setting for the "
-          "stream."));
-
-////////////////////////////////////////////////////////////////////
-//     Function: init_libegg
-//  Description: Initializes the library.  This must be called at
-//               least once before any of the functions or classes in
-//               this library can be used.  Normally it will be
-//               called by the static initializers and need not be
-//               called explicitly, but special cases exist.
-////////////////////////////////////////////////////////////////////
-void
-init_libegg() {
-  static bool initialized = false;
-  if (initialized) {
-    return;
-  }
-  initialized = true;
-
-  EggRenderMode::init_type();
-  EggAnimData::init_type();
-  EggAnimPreload::init_type();
-  EggAttributes::init_type();
-  EggBin::init_type();
-  EggBinMaker::init_type();
-  EggComment::init_type();
-  EggCompositePrimitive::init_type();
-  EggCoordinateSystem::init_type();
-  EggCurve::init_type();
-  EggData::init_type();
-  EggExternalReference::init_type();
-  EggFilenameNode::init_type();
-  EggGroup::init_type();
-  EggGroupNode::init_type();
-  EggGroupUniquifier::init_type();
-  EggLine::init_type();
-  EggMaterial::init_type();
-  EggNameUniquifier::init_type();
-  EggNamedObject::init_type();
-  EggNode::init_type();
-  EggNurbsCurve::init_type();
-  EggNurbsSurface::init_type();
-  EggObject::init_type();
-  EggPatch::init_type();
-  EggPoint::init_type();
-  EggPolygon::init_type();
-  EggPolysetMaker::init_type();
-  EggPoolUniquifier::init_type();
-  EggPrimitive::init_type();
-  EggSAnimData::init_type();
-  EggSurface::init_type();
-  EggSwitchCondition::init_type();
-  EggSwitchConditionDistance::init_type();
-  EggTable::init_type();
-  EggTexture::init_type();
-  EggTriangleFan::init_type();
-  EggTriangleStrip::init_type();
-  EggUserData::init_type();
-  EggVertex::init_type();
-  EggVertexPool::init_type();
-  EggVertexUV::init_type();
-  EggVertexAux::init_type();
-  EggXfmAnimData::init_type();
-  EggXfmSAnim::init_type();
-}
+// Filename: config_egg.cxx
+// Created by:  drose (19Mar00)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "config_egg.h"
+#include "eggRenderMode.h"
+#include "eggAnimData.h"
+#include "eggAnimPreload.h"
+#include "eggAttributes.h"
+#include "eggBin.h"
+#include "eggBinMaker.h"
+#include "eggComment.h"
+#include "eggCompositePrimitive.h"
+#include "eggCoordinateSystem.h"
+#include "eggCurve.h"
+#include "eggExternalReference.h"
+#include "eggFilenameNode.h"
+#include "eggGroup.h"
+#include "eggGroupNode.h"
+#include "eggGroupUniquifier.h"
+#include "eggLine.h"
+#include "eggMaterial.h"
+#include "eggNameUniquifier.h"
+#include "eggNamedObject.h"
+#include "eggNode.h"
+#include "eggNurbsCurve.h"
+#include "eggNurbsSurface.h"
+#include "eggObject.h"
+#include "eggPatch.h"
+#include "eggPoint.h"
+#include "eggPolygon.h"
+#include "eggPolysetMaker.h"
+#include "eggPoolUniquifier.h"
+#include "eggPrimitive.h"
+#include "eggSAnimData.h"
+#include "eggSurface.h"
+#include "eggSwitchCondition.h"
+#include "eggTable.h"
+#include "eggTexture.h"
+#include "eggTriangleFan.h"
+#include "eggTriangleStrip.h"
+#include "eggUserData.h"
+#include "eggVertex.h"
+#include "eggVertexPool.h"
+#include "eggVertexUV.h"
+#include "eggVertexAux.h"
+#include "eggXfmAnimData.h"
+#include "eggXfmSAnim.h"
+
+#include "dconfig.h"
+
+Configure(config_egg);
+NotifyCategoryDef(egg, "");
+
+ConfigureFn(config_egg) {
+  init_libegg();
+}
+
+ConfigVariableBool egg_support_old_anims
+("egg-support-old-anims", true,
+ PRC_DESC("Set this true to support loading of old character animation files, which "
+          "had the convention that the order \"phr\" implied a reversed roll."));
+
+ConfigVariableBool egg_mesh
+("egg-mesh", true,
+ PRC_DESC("Set this true to convert triangles and higher-order polygons "
+          "into triangle strips and triangle fans when an egg file is "
+          "loaded or converted to bam.  Set this false just to triangulate "
+          "everything into independent triangles."));
+
+ConfigVariableBool egg_retesselate_coplanar
+("egg-retesselate-coplanar", false,
+ PRC_DESC("If this is true, the egg loader may reverse the "
+          "tesselation direction of a single pair of planar triangles that "
+          "share the same properties, if that will help get a better "
+          "triangle strip.  In some rare cases, doing so can distort the "
+          "UV's on a face; turning this off should eliminate that artifact "
+          "(at the cost of less-effective triangle stripping)."));
+
+ConfigVariableBool egg_unroll_fans
+("egg-unroll-fans", true,
+ PRC_DESC("Set this true to allow the egg loader to convert weak triangle "
+          "fans--triangles that share the same vertex but aren't "
+          "connected enough to justify making a triangle fan primitive "
+          "from them--into a series of zig-zag triangles that can make "
+          "a triangle strip that might connect better with its neighbors."));
+
+ConfigVariableBool egg_show_tstrips
+("egg-show-tstrips", false,
+ PRC_DESC("Set this true to color each triangle strip a random color, with "
+          "the leading triangle a little bit darker, so you can visually "
+          "observe the quality of the triangle stripping algorithm."));
+
+ConfigVariableBool egg_show_qsheets
+("egg-show-qsheets", false,
+ PRC_DESC("Set this true to color each quadsheet a random color, so you "
+          "can visually observe the quadsheet algorithm."));
+
+ConfigVariableBool egg_show_quads
+("egg-show-quads", false,
+ PRC_DESC("Set this true to color each detected quad a random color, so "
+          "you can visually observe the algorithm that unifies pairs of "
+          "triangles into quads (prior to generating triangle strips)."));
+
+ConfigVariableBool egg_subdivide_polys
+("egg-subdivide-polys", true,
+ PRC_DESC("This is obsolete.  In the old Geom implementation, it used to "
+          "be true to force higher-order polygons that were not otherwise "
+          "meshed to be subdivided into triangles.  In the new "
+          "Geom implementation, this happens anyway."));
+
+ConfigVariableBool egg_consider_fans
+("egg-consider-fans", false,
+ PRC_DESC("Set this true to enable the egg mesher to consider making "
+          "triangle fans out of triangles that are connected at a common "
+          "vertex.  This may help if your scene involves lots of such "
+          "connected triangles, but it can also make the overall stripping "
+          "less effective (by interfering with triangle strips)."));
+
+ConfigVariableDouble egg_max_tfan_angle
+("egg-max-tfan-angle", 40.0,
+ PRC_DESC("The maximum average angle per triangle to allow in a triangle "
+          "fan.  If triangles are larger than this--that is, more loosely "
+          "packed--then we figure a triangle strip is likely to do a "
+          "more effective job than a triangle fan, and the fan maker leaves "
+          "it alone."));
+
+ConfigVariableInt egg_min_tfan_tris
+("egg-min-tfan-tris", 4,
+ PRC_DESC("The minimum number of triangles that must be involved in order "
+          "to generate a triangle fan.  Fewer than this is just interrupting "
+          "a triangle strip."));
+
+ConfigVariableDouble egg_coplanar_threshold
+("egg-coplanar-threshold", 0.01,
+ PRC_DESC("The numerical threshold below which polygons are considered "
+          "to be coplanar.  Determined empirically."));
+
+ConfigVariableInt egg_test_vref_integrity
+("egg-test-vref-integrity", 20,
+ PRC_DESC("The maximum number of vertices a primitive may have before "
+          "its vertices will no longer be checked for internal integrity.  "
+          "This is meaningful in non-production builds only."));
+
+ConfigVariableInt egg_recursion_limit
+("egg-recursion-limit", 1000,
+ PRC_DESC("The maximum number of levels that recursive algorithms within "
+          "the egg library are allowed to traverse.  This is a simple hack "
+          "to prevent deeply-recursive algorithms from triggering a stack "
+          "overflow.  Set it larger to run more efficiently if your stack "
+          "allows it; set it lower if you experience stack overflows."));
+
+ConfigVariableInt egg_precision
+("egg-precision", 15,
+ PRC_DESC("The number of digits of precision to write out for values in "
+          "an egg file.  Leave this at 0 to use the default setting for the "
+          "stream."));
+
+////////////////////////////////////////////////////////////////////
+//     Function: init_libegg
+//  Description: Initializes the library.  This must be called at
+//               least once before any of the functions or classes in
+//               this library can be used.  Normally it will be
+//               called by the static initializers and need not be
+//               called explicitly, but special cases exist.
+////////////////////////////////////////////////////////////////////
+void
+init_libegg() {
+  static bool initialized = false;
+  if (initialized) {
+    return;
+  }
+  initialized = true;
+
+  EggRenderMode::init_type();
+  EggAnimData::init_type();
+  EggAnimPreload::init_type();
+  EggAttributes::init_type();
+  EggBin::init_type();
+  EggBinMaker::init_type();
+  EggComment::init_type();
+  EggCompositePrimitive::init_type();
+  EggCoordinateSystem::init_type();
+  EggCurve::init_type();
+  EggData::init_type();
+  EggExternalReference::init_type();
+  EggFilenameNode::init_type();
+  EggGroup::init_type();
+  EggGroupNode::init_type();
+  EggGroupUniquifier::init_type();
+  EggLine::init_type();
+  EggMaterial::init_type();
+  EggNameUniquifier::init_type();
+  EggNamedObject::init_type();
+  EggNode::init_type();
+  EggNurbsCurve::init_type();
+  EggNurbsSurface::init_type();
+  EggObject::init_type();
+  EggPatch::init_type();
+  EggPoint::init_type();
+  EggPolygon::init_type();
+  EggPolysetMaker::init_type();
+  EggPoolUniquifier::init_type();
+  EggPrimitive::init_type();
+  EggSAnimData::init_type();
+  EggSurface::init_type();
+  EggSwitchCondition::init_type();
+  EggSwitchConditionDistance::init_type();
+  EggTable::init_type();
+  EggTexture::init_type();
+  EggTriangleFan::init_type();
+  EggTriangleStrip::init_type();
+  EggUserData::init_type();
+  EggVertex::init_type();
+  EggVertexPool::init_type();
+  EggVertexUV::init_type();
+  EggVertexAux::init_type();
+  EggXfmAnimData::init_type();
+  EggXfmSAnim::init_type();
+}

+ 48 - 48
panda/src/egg/config_egg.h

@@ -1,48 +1,48 @@
-// Filename: config_egg.h
-// Created by:  drose (19Mar00)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 CONFIG_EGG_H
-#define CONFIG_EGG_H
-
-#include "pandabase.h"
-#include "notifyCategoryProxy.h"
-#include "configVariableSearchPath.h"
-#include "configVariableBool.h"
-#include "configVariableEnum.h"
-#include "configVariableDouble.h"
-#include "configVariableInt.h"
-
-NotifyCategoryDecl(egg, EXPCL_PANDAEGG, EXPTP_PANDAEGG);
-
-extern ConfigVariableBool egg_support_old_anims;
-
-extern EXPCL_PANDAEGG ConfigVariableBool egg_mesh;
-extern EXPCL_PANDAEGG ConfigVariableBool egg_retesselate_coplanar;
-extern EXPCL_PANDAEGG ConfigVariableBool egg_unroll_fans;
-extern EXPCL_PANDAEGG ConfigVariableBool egg_show_tstrips;
-extern EXPCL_PANDAEGG ConfigVariableBool egg_show_qsheets;
-extern EXPCL_PANDAEGG ConfigVariableBool egg_show_quads;
-#define egg_false_color (egg_show_tstrips | egg_show_qsheets | egg_show_quads)
-extern EXPCL_PANDAEGG ConfigVariableBool egg_subdivide_polys;
-extern EXPCL_PANDAEGG ConfigVariableBool egg_consider_fans;
-extern EXPCL_PANDAEGG ConfigVariableDouble egg_max_tfan_angle;
-extern EXPCL_PANDAEGG ConfigVariableInt egg_min_tfan_tris;
-extern EXPCL_PANDAEGG ConfigVariableDouble egg_coplanar_threshold;
-extern EXPCL_PANDAEGG ConfigVariableInt egg_test_vref_integrity;
-extern EXPCL_PANDAEGG ConfigVariableInt egg_recursion_limit;
-extern EXPCL_PANDAEGG ConfigVariableInt egg_precision;
-
-extern EXPCL_PANDAEGG void init_libegg();
-
-#endif
+// Filename: config_egg.h
+// Created by:  drose (19Mar00)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 CONFIG_EGG_H
+#define CONFIG_EGG_H
+
+#include "pandabase.h"
+#include "notifyCategoryProxy.h"
+#include "configVariableSearchPath.h"
+#include "configVariableBool.h"
+#include "configVariableEnum.h"
+#include "configVariableDouble.h"
+#include "configVariableInt.h"
+
+NotifyCategoryDecl(egg, EXPCL_PANDAEGG, EXPTP_PANDAEGG);
+
+extern ConfigVariableBool egg_support_old_anims;
+
+extern EXPCL_PANDAEGG ConfigVariableBool egg_mesh;
+extern EXPCL_PANDAEGG ConfigVariableBool egg_retesselate_coplanar;
+extern EXPCL_PANDAEGG ConfigVariableBool egg_unroll_fans;
+extern EXPCL_PANDAEGG ConfigVariableBool egg_show_tstrips;
+extern EXPCL_PANDAEGG ConfigVariableBool egg_show_qsheets;
+extern EXPCL_PANDAEGG ConfigVariableBool egg_show_quads;
+#define egg_false_color (egg_show_tstrips | egg_show_qsheets | egg_show_quads)
+extern EXPCL_PANDAEGG ConfigVariableBool egg_subdivide_polys;
+extern EXPCL_PANDAEGG ConfigVariableBool egg_consider_fans;
+extern EXPCL_PANDAEGG ConfigVariableDouble egg_max_tfan_angle;
+extern EXPCL_PANDAEGG ConfigVariableInt egg_min_tfan_tris;
+extern EXPCL_PANDAEGG ConfigVariableDouble egg_coplanar_threshold;
+extern EXPCL_PANDAEGG ConfigVariableInt egg_test_vref_integrity;
+extern EXPCL_PANDAEGG ConfigVariableInt egg_recursion_limit;
+extern EXPCL_PANDAEGG ConfigVariableInt egg_precision;
+
+extern EXPCL_PANDAEGG void init_libegg();
+
+#endif

+ 414 - 414
panda/src/egg/eggData.cxx

@@ -1,414 +1,414 @@
-// Filename: eggData.cxx
-// Created by:  drose (20Jan99)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 "eggData.h"
-#include "eggCoordinateSystem.h"
-#include "eggTextureCollection.h"
-#include "eggMaterialCollection.h"
-#include "eggComment.h"
-#include "eggPoolUniquifier.h"
-#include "config_egg.h"
-#include "config_util.h"
-#include "config_express.h"
-#include "string_utils.h"
-#include "dSearchPath.h"
-#include "virtualFileSystem.h"
-#include "lightMutexHolder.h"
-#include "zStream.h"
-
-extern int eggyyparse();
-#include "parserDefs.h"
-#include "lexerDefs.h"
-
-TypeHandle EggData::_type_handle;
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::resolve_egg_filename
-//       Access: Public, Static
-//  Description: Looks for the indicated filename, first along the
-//               indicated searchpath, and then along the model_path.
-//               If found, updates the filename to the full path and
-//               returns true; otherwise, returns false.
-////////////////////////////////////////////////////////////////////
-bool EggData::
-resolve_egg_filename(Filename &egg_filename, const DSearchPath &searchpath) {
-  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
-
-  if (egg_filename.is_fully_qualified() && vfs->exists(egg_filename)) {
-    return true;
-  }
-
-  vfs->resolve_filename(egg_filename, searchpath, "egg") ||
-    vfs->resolve_filename(egg_filename, get_model_path(), "egg");
-
-  return vfs->exists(egg_filename);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::read
-//       Access: Public
-//  Description: Opens the indicated filename and reads the egg data
-//               contents from it.  Returns true if the file was
-//               successfully opened and read, false if there were
-//               some errors, in which case the data may be partially
-//               read.
-//
-//               error is the output stream to which to write error
-//               messages.
-////////////////////////////////////////////////////////////////////
-bool EggData::
-read(Filename filename, string display_name) {
-  filename.set_text();
-  set_egg_filename(filename);
-
-  if (display_name.empty()) {
-    display_name = filename;
-  }
-
-  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
-
-  PT(VirtualFile) vfile = vfs->get_file(filename);
-  if (vfile == NULL) {
-    egg_cat.error() << "Could not find " << display_name << "\n";
-    return false;
-  }
-  set_egg_timestamp(vfile->get_timestamp());
-
-  istream *file = vfile->open_read_file(true);
-  if (file == (istream *)NULL) {
-    egg_cat.error() << "Unable to open " << display_name << "\n";
-    return false;
-  }
-
-  egg_cat.info()
-    << "Reading " << display_name << "\n";
-
-  bool read_ok = read(*file);
-  vfile->close_read_file(file);
-  return read_ok;
-}
-
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::read
-//       Access: Public
-//  Description: Parses the egg syntax contained in the indicated
-//               input stream.  Returns true if the stream was a
-//               completely valid egg file, false if there were some
-//               errors, in which case the data may be partially read.
-//
-//               Before you call this routine, you should probably
-//               call set_egg_filename() to set the name of the egg
-//               file we're processing, if at all possible.  If there
-//               is no such filename, you may set it to the empty
-//               string.
-////////////////////////////////////////////////////////////////////
-bool EggData::
-read(istream &in) {
-  // First, dispense with any children we had previously.  We will
-  // replace them with the new data.
-  clear();
-
-  // Create a temporary EggData structure to read into.  We initialize
-  // it with a copy of ourselves, so that it will get our _coordsys
-  // value, if the user set it.
-  PT(EggData) data = new EggData(*this);
-
-  int error_count;
-  {
-    LightMutexHolder holder(egg_lock);
-    egg_init_parser(in, get_egg_filename(), data, data);
-    eggyyparse();
-    egg_cleanup_parser();
-    error_count = egg_error_count();
-  }
-
-  data->post_read();
-
-  steal_children(*data);
-  (*this) = *data;
-
-  return (error_count == 0);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::merge
-//       Access: Public
-//  Description: Appends the other egg structure to the end of this
-//               one.  The other egg structure is invalidated.
-////////////////////////////////////////////////////////////////////
-void EggData::
-merge(EggData &other) {
-  if (get_coordinate_system() == CS_default) {
-    // If we haven't specified a coordinate system yet, we inherit the
-    // other one's.
-    set_coordinate_system(other.get_coordinate_system());
-
-  } else {
-    // Otherwise, the other one is forced into our coordinate system
-    // before we merge.
-    other.set_coordinate_system(get_coordinate_system());
-  }
-  steal_children(other);
-}
-
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::load_externals
-//       Access: Public
-//  Description: Loads up all the egg files referenced by <File>
-//               entries within the egg structure, and inserts their
-//               contents in place of the <File> entries.  Searches
-//               for files in the searchpath, if not found directly,
-//               and writes error messages to the indicated output
-//               stream.  Returns true if all externals were loaded
-//               successfully, false otherwise.
-////////////////////////////////////////////////////////////////////
-bool EggData::
-load_externals(const DSearchPath &searchpath) {
-  return
-    r_load_externals(searchpath, get_coordinate_system(), NULL);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::load_externals
-//       Access: Public
-//  Description: Loads up all the egg files referenced by <File>
-//               entries within the egg structure, and inserts their
-//               contents in place of the <File> entries.  Searches
-//               for files in the searchpath, if not found directly,
-//               and writes error messages to the indicated output
-//               stream.  Returns true if all externals were loaded
-//               successfully, false otherwise.
-////////////////////////////////////////////////////////////////////
-bool EggData::
-load_externals(const DSearchPath &searchpath, BamCacheRecord *record) {
-  return
-    r_load_externals(searchpath, get_coordinate_system(), record);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::collapse_equivalent_textures
-//       Access: Public
-//  Description: Removes duplicate references to the same texture
-//               image with the same properties.  Considers two
-//               texture references with identical properties, but
-//               different tref names, to be equivalent, and collapses
-//               them, choosing one tref name to keep arbitrarily.
-//               Returns the number of textures removed.
-////////////////////////////////////////////////////////////////////
-int EggData::
-collapse_equivalent_textures() {
-  EggTextureCollection textures;
-  textures.find_used_textures(this);
-  return
-    textures.collapse_equivalent_textures(~EggTexture::E_tref_name, this);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::collapse_equivalent_materials
-//       Access: Public
-//  Description: Removes duplicate references to the same material
-//               with the same properties.  Considers two material
-//               references with identical properties, but different
-//               mref names, to be equivalent, and collapses them,
-//               choosing one mref name to keep arbitrarily.  Returns
-//               the number of materials removed.
-////////////////////////////////////////////////////////////////////
-int EggData::
-collapse_equivalent_materials() {
-  EggMaterialCollection materials;
-  materials.find_used_materials(this);
-  return
-    materials.collapse_equivalent_materials(~EggMaterial::E_mref_name, this);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::write_egg
-//       Access: Public
-//  Description: The main interface for writing complete egg files.
-////////////////////////////////////////////////////////////////////
-bool EggData::
-write_egg(Filename filename) {
-  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
-  filename.set_text();
-  vfs->delete_file(filename);
-  ostream *file = vfs->open_write_file(filename, true, true);
-  if (file == (ostream *)NULL) {
-    egg_cat.error() << "Unable to open " << filename << " for writing.\n";
-    return false;
-  }
-
-  bool wrote_ok = write_egg(*file);
-  vfs->close_write_file(file);
-  return wrote_ok;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::write_egg
-//       Access: Public
-//  Description: The main interface for writing complete egg files.
-////////////////////////////////////////////////////////////////////
-bool EggData::
-write_egg(ostream &out) {
-  pre_write();
-
-  if (egg_precision > 0) {
-    // Change the egg precision as requested.
-    streamsize orig_precision = out.precision();
-    out.precision((streamsize)egg_precision);
-    write(out, 0);
-    out.precision(orig_precision);
-  } else {
-    // Use the stream default precision.
-    write(out, 0);
-  }
-  return true;
-}
-
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::set_coordinate_system
-//       Access: Public
-//  Description: Changes the coordinate system of the EggData.  If the
-//               coordinate system was previously different, this may
-//               result in a conversion of the data.
-////////////////////////////////////////////////////////////////////
-void EggData::
-set_coordinate_system(CoordinateSystem new_coordsys) {
-  if (new_coordsys == CS_default) {
-    new_coordsys = get_default_coordinate_system();
-  }
-  if (new_coordsys != _coordsys &&
-      (_coordsys != CS_default && _coordsys != CS_invalid)) {
-    // Time to convert the data.
-    LMatrix4d mat = LMatrix4d::convert_mat(_coordsys, new_coordsys);
-    LMatrix4d inv = LMatrix4d::convert_mat(new_coordsys, _coordsys);
-
-    r_transform(mat, inv, new_coordsys);
-    r_transform_vertices(mat);
-
-    // Now we have to update the under_flags to ensure that all the
-    // cached relative matrices are correct.
-    update_under(0);
-  }
-
-  _coordsys = new_coordsys;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::write
-//       Access: Protected, Virtual
-//  Description: Writes the egg data out to the indicated output
-//               stream.
-////////////////////////////////////////////////////////////////////
-void EggData::
-write(ostream &out, int indent_level) const {
-  PT(EggCoordinateSystem) ecs = new EggCoordinateSystem(_coordsys);
-  ecs->write(out, indent_level);
-  EggGroupNode::write(out, indent_level);
-  out << flush;
-}
-
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::post_read
-//       Access: Private
-//  Description: Does whatever processing is appropriate after reading
-//               the data in from an egg file.
-////////////////////////////////////////////////////////////////////
-void EggData::
-post_read() {
-  CoordinateSystem old_coordsys = _coordsys;
-  _coordsys = find_coordsys_entry();
-
-  if (_coordsys == CS_default) {
-    // If the egg file didn't contain a <CoordinateSystem> entry,
-    // assume it's Y-up, by convention.
-    _coordsys = CS_yup_right;
-
-  } else if (_coordsys == CS_invalid) {
-    egg_cat.warning()
-      << "Contradictory <CoordinateSystem> entries encountered.\n";
-    _coordsys = CS_yup_right;
-  }
-
-  r_mark_coordsys(_coordsys);
-
-  if (old_coordsys != CS_default) {
-    // Now if we had a previous definition, enforce it.  This might
-    // convert the data to the given coordinate system.
-    set_coordinate_system(old_coordsys);
-  }
-
-  // Fill this in before we automatically resolve pathnames.
-  _had_absolute_pathnames = has_absolute_pathnames();
-
-  if (get_auto_resolve_externals()) {
-    // Resolve filenames that are relative to the egg file.
-    DSearchPath dir;
-    dir.append_directory(get_egg_filename().get_dirname());
-    resolve_filenames(dir);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggData::pre_write
-//       Access: Private
-//  Description: Does whatever processing is appropriate just before
-//               writing the data out to an egg file.  This includes
-//               verifying that vertex pool names are unique, etc.
-////////////////////////////////////////////////////////////////////
-void EggData::
-pre_write() {
-  // Pull out all of the texture definitions in the file and massage
-  // them a bit.
-  EggTextureCollection textures;
-  textures.extract_textures(this);
-
-  // Remove any textures that aren't being used.
-  textures.remove_unused_textures(this);
-
-  // Collapse out any textures that are completely equivalent.  For
-  // this purpose, we consider two textures with identical properties
-  // but different tref names to be different.
-  textures.collapse_equivalent_textures(~0, this);
-
-  // Make sure all of the textures have unique TRef names.
-  textures.uniquify_trefs();
-  textures.sort_by_tref();
-
-  // Do the same thing with the materials.
-  EggMaterialCollection materials;
-  materials.extract_materials(this);
-  materials.remove_unused_materials(this);
-  materials.collapse_equivalent_materials(~0, this);
-  materials.uniquify_mrefs();
-  materials.sort_by_mref();
-
-  // Now put them all back at the head of the file, after any initial
-  // comment records.
-  iterator ci = begin();
-  while (ci != end() && (*ci)->is_of_type(EggComment::get_class_type())) {
-    ++ci;
-  }
-  textures.insert_textures(this, ci);
-  materials.insert_materials(this, ci);
-
-  // Also make sure that the vertex pools are uniquely named.  This
-  // also checks textures and materials, which is kind of redundant
-  // since we just did that, but we don't mind.
-  EggPoolUniquifier pu;
-  pu.uniquify(this);
-}
+// Filename: eggData.cxx
+// Created by:  drose (20Jan99)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "eggData.h"
+#include "eggCoordinateSystem.h"
+#include "eggTextureCollection.h"
+#include "eggMaterialCollection.h"
+#include "eggComment.h"
+#include "eggPoolUniquifier.h"
+#include "config_egg.h"
+#include "config_util.h"
+#include "config_express.h"
+#include "string_utils.h"
+#include "dSearchPath.h"
+#include "virtualFileSystem.h"
+#include "lightMutexHolder.h"
+#include "zStream.h"
+
+extern int eggyyparse();
+#include "parserDefs.h"
+#include "lexerDefs.h"
+
+TypeHandle EggData::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::resolve_egg_filename
+//       Access: Public, Static
+//  Description: Looks for the indicated filename, first along the
+//               indicated searchpath, and then along the model_path.
+//               If found, updates the filename to the full path and
+//               returns true; otherwise, returns false.
+////////////////////////////////////////////////////////////////////
+bool EggData::
+resolve_egg_filename(Filename &egg_filename, const DSearchPath &searchpath) {
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+
+  if (egg_filename.is_fully_qualified() && vfs->exists(egg_filename)) {
+    return true;
+  }
+
+  vfs->resolve_filename(egg_filename, searchpath, "egg") ||
+    vfs->resolve_filename(egg_filename, get_model_path(), "egg");
+
+  return vfs->exists(egg_filename);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::read
+//       Access: Public
+//  Description: Opens the indicated filename and reads the egg data
+//               contents from it.  Returns true if the file was
+//               successfully opened and read, false if there were
+//               some errors, in which case the data may be partially
+//               read.
+//
+//               error is the output stream to which to write error
+//               messages.
+////////////////////////////////////////////////////////////////////
+bool EggData::
+read(Filename filename, string display_name) {
+  filename.set_text();
+  set_egg_filename(filename);
+
+  if (display_name.empty()) {
+    display_name = filename;
+  }
+
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+
+  PT(VirtualFile) vfile = vfs->get_file(filename);
+  if (vfile == NULL) {
+    egg_cat.error() << "Could not find " << display_name << "\n";
+    return false;
+  }
+  set_egg_timestamp(vfile->get_timestamp());
+
+  istream *file = vfile->open_read_file(true);
+  if (file == (istream *)NULL) {
+    egg_cat.error() << "Unable to open " << display_name << "\n";
+    return false;
+  }
+
+  egg_cat.info()
+    << "Reading " << display_name << "\n";
+
+  bool read_ok = read(*file);
+  vfile->close_read_file(file);
+  return read_ok;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::read
+//       Access: Public
+//  Description: Parses the egg syntax contained in the indicated
+//               input stream.  Returns true if the stream was a
+//               completely valid egg file, false if there were some
+//               errors, in which case the data may be partially read.
+//
+//               Before you call this routine, you should probably
+//               call set_egg_filename() to set the name of the egg
+//               file we're processing, if at all possible.  If there
+//               is no such filename, you may set it to the empty
+//               string.
+////////////////////////////////////////////////////////////////////
+bool EggData::
+read(istream &in) {
+  // First, dispense with any children we had previously.  We will
+  // replace them with the new data.
+  clear();
+
+  // Create a temporary EggData structure to read into.  We initialize
+  // it with a copy of ourselves, so that it will get our _coordsys
+  // value, if the user set it.
+  PT(EggData) data = new EggData(*this);
+
+  int error_count;
+  {
+    LightMutexHolder holder(egg_lock);
+    egg_init_parser(in, get_egg_filename(), data, data);
+    eggyyparse();
+    egg_cleanup_parser();
+    error_count = egg_error_count();
+  }
+
+  data->post_read();
+
+  steal_children(*data);
+  (*this) = *data;
+
+  return (error_count == 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::merge
+//       Access: Public
+//  Description: Appends the other egg structure to the end of this
+//               one.  The other egg structure is invalidated.
+////////////////////////////////////////////////////////////////////
+void EggData::
+merge(EggData &other) {
+  if (get_coordinate_system() == CS_default) {
+    // If we haven't specified a coordinate system yet, we inherit the
+    // other one's.
+    set_coordinate_system(other.get_coordinate_system());
+
+  } else {
+    // Otherwise, the other one is forced into our coordinate system
+    // before we merge.
+    other.set_coordinate_system(get_coordinate_system());
+  }
+  steal_children(other);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::load_externals
+//       Access: Public
+//  Description: Loads up all the egg files referenced by <File>
+//               entries within the egg structure, and inserts their
+//               contents in place of the <File> entries.  Searches
+//               for files in the searchpath, if not found directly,
+//               and writes error messages to the indicated output
+//               stream.  Returns true if all externals were loaded
+//               successfully, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool EggData::
+load_externals(const DSearchPath &searchpath) {
+  return
+    r_load_externals(searchpath, get_coordinate_system(), NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::load_externals
+//       Access: Public
+//  Description: Loads up all the egg files referenced by <File>
+//               entries within the egg structure, and inserts their
+//               contents in place of the <File> entries.  Searches
+//               for files in the searchpath, if not found directly,
+//               and writes error messages to the indicated output
+//               stream.  Returns true if all externals were loaded
+//               successfully, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool EggData::
+load_externals(const DSearchPath &searchpath, BamCacheRecord *record) {
+  return
+    r_load_externals(searchpath, get_coordinate_system(), record);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::collapse_equivalent_textures
+//       Access: Public
+//  Description: Removes duplicate references to the same texture
+//               image with the same properties.  Considers two
+//               texture references with identical properties, but
+//               different tref names, to be equivalent, and collapses
+//               them, choosing one tref name to keep arbitrarily.
+//               Returns the number of textures removed.
+////////////////////////////////////////////////////////////////////
+int EggData::
+collapse_equivalent_textures() {
+  EggTextureCollection textures;
+  textures.find_used_textures(this);
+  return
+    textures.collapse_equivalent_textures(~EggTexture::E_tref_name, this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::collapse_equivalent_materials
+//       Access: Public
+//  Description: Removes duplicate references to the same material
+//               with the same properties.  Considers two material
+//               references with identical properties, but different
+//               mref names, to be equivalent, and collapses them,
+//               choosing one mref name to keep arbitrarily.  Returns
+//               the number of materials removed.
+////////////////////////////////////////////////////////////////////
+int EggData::
+collapse_equivalent_materials() {
+  EggMaterialCollection materials;
+  materials.find_used_materials(this);
+  return
+    materials.collapse_equivalent_materials(~EggMaterial::E_mref_name, this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::write_egg
+//       Access: Public
+//  Description: The main interface for writing complete egg files.
+////////////////////////////////////////////////////////////////////
+bool EggData::
+write_egg(Filename filename) {
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+  filename.set_text();
+  vfs->delete_file(filename);
+  ostream *file = vfs->open_write_file(filename, true, true);
+  if (file == (ostream *)NULL) {
+    egg_cat.error() << "Unable to open " << filename << " for writing.\n";
+    return false;
+  }
+
+  bool wrote_ok = write_egg(*file);
+  vfs->close_write_file(file);
+  return wrote_ok;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::write_egg
+//       Access: Public
+//  Description: The main interface for writing complete egg files.
+////////////////////////////////////////////////////////////////////
+bool EggData::
+write_egg(ostream &out) {
+  pre_write();
+
+  if (egg_precision > 0) {
+    // Change the egg precision as requested.
+    streamsize orig_precision = out.precision();
+    out.precision((streamsize)egg_precision);
+    write(out, 0);
+    out.precision(orig_precision);
+  } else {
+    // Use the stream default precision.
+    write(out, 0);
+  }
+  return true;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::set_coordinate_system
+//       Access: Public
+//  Description: Changes the coordinate system of the EggData.  If the
+//               coordinate system was previously different, this may
+//               result in a conversion of the data.
+////////////////////////////////////////////////////////////////////
+void EggData::
+set_coordinate_system(CoordinateSystem new_coordsys) {
+  if (new_coordsys == CS_default) {
+    new_coordsys = get_default_coordinate_system();
+  }
+  if (new_coordsys != _coordsys &&
+      (_coordsys != CS_default && _coordsys != CS_invalid)) {
+    // Time to convert the data.
+    LMatrix4d mat = LMatrix4d::convert_mat(_coordsys, new_coordsys);
+    LMatrix4d inv = LMatrix4d::convert_mat(new_coordsys, _coordsys);
+
+    r_transform(mat, inv, new_coordsys);
+    r_transform_vertices(mat);
+
+    // Now we have to update the under_flags to ensure that all the
+    // cached relative matrices are correct.
+    update_under(0);
+  }
+
+  _coordsys = new_coordsys;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::write
+//       Access: Protected, Virtual
+//  Description: Writes the egg data out to the indicated output
+//               stream.
+////////////////////////////////////////////////////////////////////
+void EggData::
+write(ostream &out, int indent_level) const {
+  PT(EggCoordinateSystem) ecs = new EggCoordinateSystem(_coordsys);
+  ecs->write(out, indent_level);
+  EggGroupNode::write(out, indent_level);
+  out << flush;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::post_read
+//       Access: Private
+//  Description: Does whatever processing is appropriate after reading
+//               the data in from an egg file.
+////////////////////////////////////////////////////////////////////
+void EggData::
+post_read() {
+  CoordinateSystem old_coordsys = _coordsys;
+  _coordsys = find_coordsys_entry();
+
+  if (_coordsys == CS_default) {
+    // If the egg file didn't contain a <CoordinateSystem> entry,
+    // assume it's Y-up, by convention.
+    _coordsys = CS_yup_right;
+
+  } else if (_coordsys == CS_invalid) {
+    egg_cat.warning()
+      << "Contradictory <CoordinateSystem> entries encountered.\n";
+    _coordsys = CS_yup_right;
+  }
+
+  r_mark_coordsys(_coordsys);
+
+  if (old_coordsys != CS_default) {
+    // Now if we had a previous definition, enforce it.  This might
+    // convert the data to the given coordinate system.
+    set_coordinate_system(old_coordsys);
+  }
+
+  // Fill this in before we automatically resolve pathnames.
+  _had_absolute_pathnames = has_absolute_pathnames();
+
+  if (get_auto_resolve_externals()) {
+    // Resolve filenames that are relative to the egg file.
+    DSearchPath dir;
+    dir.append_directory(get_egg_filename().get_dirname());
+    resolve_filenames(dir);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggData::pre_write
+//       Access: Private
+//  Description: Does whatever processing is appropriate just before
+//               writing the data out to an egg file.  This includes
+//               verifying that vertex pool names are unique, etc.
+////////////////////////////////////////////////////////////////////
+void EggData::
+pre_write() {
+  // Pull out all of the texture definitions in the file and massage
+  // them a bit.
+  EggTextureCollection textures;
+  textures.extract_textures(this);
+
+  // Remove any textures that aren't being used.
+  textures.remove_unused_textures(this);
+
+  // Collapse out any textures that are completely equivalent.  For
+  // this purpose, we consider two textures with identical properties
+  // but different tref names to be different.
+  textures.collapse_equivalent_textures(~0, this);
+
+  // Make sure all of the textures have unique TRef names.
+  textures.uniquify_trefs();
+  textures.sort_by_tref();
+
+  // Do the same thing with the materials.
+  EggMaterialCollection materials;
+  materials.extract_materials(this);
+  materials.remove_unused_materials(this);
+  materials.collapse_equivalent_materials(~0, this);
+  materials.uniquify_mrefs();
+  materials.sort_by_mref();
+
+  // Now put them all back at the head of the file, after any initial
+  // comment records.
+  iterator ci = begin();
+  while (ci != end() && (*ci)->is_of_type(EggComment::get_class_type())) {
+    ++ci;
+  }
+  textures.insert_textures(this, ci);
+  materials.insert_materials(this, ci);
+
+  // Also make sure that the vertex pools are uniquely named.  This
+  // also checks textures and materials, which is kind of redundant
+  // since we just did that, but we don't mind.
+  EggPoolUniquifier pu;
+  pu.uniquify(this);
+}

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

@@ -1,25 +1,25 @@
-#begin ss_lib_target
-  #define TARGET p3objegg
-  #define LOCAL_LIBS p3converter p3pandatoolbase
-  #define OTHER_LIBS \
-    p3egg:c pandaegg:m \
-    p3pipeline:c p3event:c p3pstatclient:c panda:m \
-    p3pandabase:c p3pnmimage:c p3mathutil:c p3linmath:c p3putil:c p3express:c \
-    p3interrogatedb:c p3prc:c p3dconfig:c p3dtoolconfig:m \
-    p3dtoolutil:c p3dtoolbase:c p3dtool:m \
-    $[if $[WANT_NATIVE_NET],p3nativenet:c] \
-    $[if $[and $[HAVE_NET],$[WANT_NATIVE_NET]],p3net:c p3downloader:c]
-
-  #define UNIX_SYS_LIBS \
-    m
-
-  #define SOURCES \
-    config_objegg.cxx config_objegg.h \
-    objToEggConverter.cxx objToEggConverter.h objToEggConverter.I \
-    eggToObjConverter.cxx eggToObjConverter.h
-
-  #define INSTALL_HEADERS \
-    objToEggConverter.h objToEggConverter.I \
-    eggToObjConverter.h
-
-#end ss_lib_target
+#begin ss_lib_target
+  #define TARGET p3objegg
+  #define LOCAL_LIBS p3converter p3pandatoolbase
+  #define OTHER_LIBS \
+    p3egg:c pandaegg:m \
+    p3pipeline:c p3event:c p3pstatclient:c panda:m \
+    p3pandabase:c p3pnmimage:c p3mathutil:c p3linmath:c p3putil:c p3express:c \
+    p3interrogatedb:c p3prc:c p3dconfig:c p3dtoolconfig:m \
+    p3dtoolutil:c p3dtoolbase:c p3dtool:m \
+    $[if $[WANT_NATIVE_NET],p3nativenet:c] \
+    $[if $[and $[HAVE_NET],$[WANT_NATIVE_NET]],p3net:c p3downloader:c]
+
+  #define UNIX_SYS_LIBS \
+    m
+
+  #define SOURCES \
+    config_objegg.cxx config_objegg.h \
+    objToEggConverter.cxx objToEggConverter.h objToEggConverter.I \
+    eggToObjConverter.cxx eggToObjConverter.h
+
+  #define INSTALL_HEADERS \
+    objToEggConverter.h objToEggConverter.I \
+    eggToObjConverter.h
+
+#end ss_lib_target

+ 462 - 462
pandatool/src/objegg/eggToObjConverter.cxx

@@ -1,462 +1,462 @@
-// Filename: eggToObjConverter.cxx
-// Created by:  drose (19Dec12)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 "eggToObjConverter.h"
-#include "config_objegg.h"
-#include "config_egg.h"
-#include "eggData.h"
-#include "string_utils.h"
-#include "streamReader.h"
-#include "virtualFileSystem.h"
-#include "eggPolygon.h"
-#include "eggPoint.h"
-#include "eggLine.h"
-#include "dcast.h"
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::Constructor
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-EggToObjConverter::
-EggToObjConverter() {
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::Copy Constructor
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-EggToObjConverter::
-EggToObjConverter(const EggToObjConverter &copy) :
-  EggToSomethingConverter(copy)
-{
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::Destructor
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-EggToObjConverter::
-~EggToObjConverter() {
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::make_copy
-//       Access: Public, Virtual
-//  Description: Allocates and returns a new copy of the converter.
-////////////////////////////////////////////////////////////////////
-EggToSomethingConverter *EggToObjConverter::
-make_copy() {
-  return new EggToObjConverter(*this);
-}
-
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::get_name
-//       Access: Public, Virtual
-//  Description: Returns the English name of the file type this
-//               converter supports.
-////////////////////////////////////////////////////////////////////
-string EggToObjConverter::
-get_name() const {
-  return "obj";
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::get_extension
-//       Access: Public, Virtual
-//  Description: Returns the common extension of the file type this
-//               converter supports.
-////////////////////////////////////////////////////////////////////
-string EggToObjConverter::
-get_extension() const {
-  return "obj";
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::supports_compressed
-//       Access: Published, Virtual
-//  Description: Returns true if this file type can transparently save
-//               compressed files (with a .pz extension), false
-//               otherwise.
-////////////////////////////////////////////////////////////////////
-bool EggToObjConverter::
-supports_compressed() const {
-  return true;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::write_file
-//       Access: Public, Virtual
-//  Description: Handles the conversion of the internal EggData to the
-//               target file format, written to the specified
-//               filename.
-////////////////////////////////////////////////////////////////////
-bool EggToObjConverter::
-write_file(const Filename &filename) {
-  clear_error();
-
-  if (_egg_data->get_coordinate_system() == CS_default) {
-    _egg_data->set_coordinate_system(CS_zup_right);
-  }
-
-  if (!process(filename)) {
-    _error = true;
-  }
-  return !had_error();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::process
-//       Access: Private
-//  Description:
-////////////////////////////////////////////////////////////////////
-bool EggToObjConverter::
-process(const Filename &filename) {
-  _egg_data->flatten_transforms();
-  collect_vertices(_egg_data);
-
-  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
-  Filename obj_filename = Filename::text_filename(filename);
-  vfs->delete_file(obj_filename);
-  ostream *file = vfs->open_write_file(obj_filename, true, true);
-  if (file == (ostream *)NULL) {
-    return false;
-  }
-  if (egg_precision != 0) {
-    file->precision(egg_precision);
-  }
-
-  _current_group = NULL;
-
-  /*
-  (*file) << "\n#\n"
-          << "# obj file generated by the following command:\n"
-          << "# " << get_exec_command() << "\n"
-          << "#\n\n";
-  */
-
-  write_vertices(*file, "v", 3, _unique_vert3);
-  write_vertices(*file, "v", 4, _unique_vert4);
-  write_vertices(*file, "vt", 2, _unique_uv2);
-  write_vertices(*file, "vt", 3, _unique_uv3);
-  write_vertices(*file, "vn", 3, _unique_norm);
-
-  write_faces(*file, _egg_data);
-
-  bool success = (file != (ostream *)NULL);
-  vfs->close_write_file(file);
-
-  return success;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::collect_vertices
-//       Access: Private
-//  Description: Recursively walks the egg structure, looking for
-//               vertices referenced by polygons or points.  Any such
-//               vertices are added to the vertex tables for writing
-//               to the obj file.
-////////////////////////////////////////////////////////////////////
-void EggToObjConverter::
-collect_vertices(EggNode *egg_node) {
-  if (egg_node->is_of_type(EggPrimitive::get_class_type())) {
-    EggPrimitive *egg_prim = DCAST(EggPrimitive, egg_node);
-    EggPrimitive::iterator pi;
-    for (pi = egg_prim->begin(); pi != egg_prim->end(); ++pi) {
-      record_vertex(*pi);
-    }
-
-  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
-    EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
-
-    EggGroupNode::iterator ci;
-    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
-      collect_vertices(*ci);
-    }
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::write_faces
-//       Access: Private
-//  Description: Recursively walks the egg structure again, this time
-//               writing out the face records for any polygons,
-//               points, or lines encountered.
-////////////////////////////////////////////////////////////////////
-void EggToObjConverter::
-write_faces(ostream &out, EggNode *egg_node) {
-  if (egg_node->is_of_type(EggPrimitive::get_class_type())) {
-    const char *prim_type = NULL;
-    if (egg_node->is_of_type(EggPolygon::get_class_type())) {
-      prim_type = "f";
-    } else if (egg_node->is_of_type(EggPoint::get_class_type())) {
-      prim_type = "p";
-    } else if (egg_node->is_of_type(EggLine::get_class_type())) {
-      prim_type = "l";
-    }
-
-    if (prim_type != NULL) {
-      write_group_reference(out, egg_node);
-
-      EggPrimitive *egg_prim = DCAST(EggPrimitive, egg_node);
-
-      out << prim_type;
-      EggPrimitive::iterator pi;
-      for (pi = egg_prim->begin(); pi != egg_prim->end(); ++pi) {
-        VertexDef &vdef = _vmap[(*pi)];
-        int vert_index = -1;
-        int uv_index = -1;
-        int norm_index = -1;
-
-        if (vdef._vert3_index != -1) {
-          vert_index = vdef._vert3_index + 1;
-        } else if (vdef._vert4_index != -1) {
-          vert_index = vdef._vert4_index + 1 + (int)_unique_vert3.size();
-        }
-
-        if (vdef._uv2_index != -1) {
-          uv_index = vdef._uv2_index + 1;
-        } else if (vdef._uv3_index != -1) {
-          uv_index = vdef._uv3_index + 1 + (int)_unique_uv2.size();
-        }
-
-        if (vdef._norm_index != -1) {
-          norm_index = vdef._norm_index + 1;
-        }
-
-        if (vert_index == -1) {
-          continue;
-        }
-
-        if (norm_index != -1) {
-          if (uv_index != -1) {
-            out << " " << vert_index << "/" << uv_index << "/" << norm_index;
-          } else {
-            out << " " << vert_index << "//" << norm_index;
-          }
-        } else if (uv_index != -1) {
-          out << " " << vert_index << "/" << uv_index;
-        } else {
-          out << " " << vert_index;
-        }
-      }
-      out << "\n";
-    }
-  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
-    EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
-
-    EggGroupNode::iterator ci;
-    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
-      write_faces(out, *ci);
-    }
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::write_group_reference
-//       Access: Private
-//  Description: Writes the "g" tag to describe this polygon's group,
-//               if needed.
-////////////////////////////////////////////////////////////////////
-void EggToObjConverter::
-write_group_reference(ostream &out, EggNode *egg_node) {
-  EggGroupNode *egg_group = egg_node->get_parent();
-  if (egg_group == _current_group) {
-    // Same group we wrote last time.
-    return;
-  }
-
-  string group_name;
-  get_group_name(group_name, egg_group);
-  if (group_name.empty()) {
-    out << "g default\n";
-  } else {
-    out << "g" << group_name << "\n";
-  }
-  _current_group = egg_group;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::get_group_name
-//       Access: Private
-//  Description: Recursively determines the appropriate string to
-//               write for the "g" tag to describe a particular
-//               EggGroupNode.
-////////////////////////////////////////////////////////////////////
-void EggToObjConverter::
-get_group_name(string &group_name, EggGroupNode *egg_group) {
-  string name = trim(egg_group->get_name());
-  if (!name.empty()) {
-    group_name += ' ';
-
-    // Remove nonstandard characters.
-    for (string::const_iterator ni = name.begin(); ni != name.end(); ++ni) {
-      char c = (*ni);
-      if (!isalnum(c)) {
-        c = '_';
-      }
-      group_name += c;
-    }
-  }
-
-  // Now recurse.
-  EggGroupNode *egg_parent = egg_group->get_parent();
-  if (egg_parent != NULL) {
-    get_group_name(group_name, egg_parent);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::record_vertex
-//       Access: Private
-//  Description: Adds the indicated EggVertex to the unique vertex
-//               tables, for writing later by write_vertices().
-////////////////////////////////////////////////////////////////////
-void EggToObjConverter::
-record_vertex(EggVertex *vertex) {
-  VertexDef &vdef = _vmap[vertex];
-
-  switch (vertex->get_num_dimensions()) {
-  case 1:
-    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos1());
-    break;
-  case 2:
-    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos2());
-    break;
-  case 3:
-    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos3());
-    break;
-  case 4:
-    vdef._vert4_index = record_unique(_unique_vert4, vertex->get_pos4());
-    break;
-  }
-
-  if (vertex->has_uv("")) {
-    vdef._uv2_index = record_unique(_unique_uv2, vertex->get_uv(""));
-  } else if (vertex->has_uvw("")) {
-    vdef._uv3_index = record_unique(_unique_uv3, vertex->get_uvw(""));
-  }
-
-  if (vertex->has_normal()) {
-    vdef._norm_index = record_unique(_unique_norm, vertex->get_normal());
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::record_unique
-//       Access: Private
-//  Description: Records the indicated vertex value, returning the
-//               shared index if this value already appears elsewhere
-//               in the table, or the new unique index if this is the
-//               first time this value appears.
-////////////////////////////////////////////////////////////////////
-int EggToObjConverter::
-record_unique(UniqueVertices &unique, const LVecBase4d &vec) {
-  // We record a zero-based index.  Note that we will actually write
-  // out a one-based index to the obj file, as required by the
-  // standard.
-  int index = unique.size();
-  UniqueVertices::iterator ui = unique.insert(UniqueVertices::value_type(vec, index)).first;
-  return (*ui).second;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::record_unique
-//       Access: Private
-//  Description: Records the indicated vertex value, returning the
-//               shared index if this value already appears elsewhere
-//               in the table, or the new unique index if this is the
-//               first time this value appears.
-////////////////////////////////////////////////////////////////////
-int EggToObjConverter::
-record_unique(UniqueVertices &unique, const LVecBase3d &vec) {
-  return record_unique(unique, LVecBase4d(vec[0], vec[1], vec[2], 0.0));
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::record_unique
-//       Access: Private
-//  Description: Records the indicated vertex value, returning the
-//               shared index if this value already appears elsewhere
-//               in the table, or the new unique index if this is the
-//               first time this value appears.
-////////////////////////////////////////////////////////////////////
-int EggToObjConverter::
-record_unique(UniqueVertices &unique, const LVecBase2d &vec) {
-  return record_unique(unique, LVecBase4d(vec[0], vec[1], 0.0, 0.0));
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::record_unique
-//       Access: Private
-//  Description: Records the indicated vertex value, returning the
-//               shared index if this value already appears elsewhere
-//               in the table, or the new unique index if this is the
-//               first time this value appears.
-////////////////////////////////////////////////////////////////////
-int EggToObjConverter::
-record_unique(UniqueVertices &unique, double pos) {
-  return record_unique(unique, LVecBase4d(pos, 0.0, 0.0, 0.0));
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::write_vertices
-//       Access: Private
-//  Description: Actually writes the vertex values recorded in the
-//               indicated table to the obj output stream.
-////////////////////////////////////////////////////////////////////
-void EggToObjConverter::
-write_vertices(ostream &out, const string &prefix, int num_components,
-               const UniqueVertices &unique) {
-  // First, sort the list into numeric order.
-  int num_vertices = (int)unique.size();
-  const LVecBase4d **vertices = (const LVecBase4d **)PANDA_MALLOC_ARRAY(num_vertices * sizeof(LVecBase4d *));
-  memset(vertices, 0, num_vertices * sizeof(LVecBase4d *));
-  UniqueVertices::const_iterator ui;
-  for (ui = unique.begin(); ui != unique.end(); ++ui) {
-    int index = (*ui).second;
-    const LVecBase4d &vec = (*ui).first;
-    nassertv(index >= 0 && index < num_vertices);
-    nassertv(vertices[index] == NULL);
-    vertices[index] = &vec;
-  }
-
-  for (int i = 0; i < num_vertices; ++i) {
-    out << prefix;
-    const LVecBase4d &vec = *(vertices[i]);
-    for (int ci = 0; ci < num_components; ++ci) {
-      out << " " << vec[ci];
-    }
-    out << "\n";
-  }
-  PANDA_FREE_ARRAY(vertices);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObjConverter::VertexDef::Constructor
-//       Access: Private
-//  Description:
-////////////////////////////////////////////////////////////////////
-EggToObjConverter::VertexDef::
-VertexDef() :
-  _vert3_index(-1),
-  _vert4_index(-1),
-  _uv2_index(-1),
-  _uv3_index(-1),
-  _norm_index(-1)
-{
-}
+// Filename: eggToObjConverter.cxx
+// Created by:  drose (19Dec12)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "eggToObjConverter.h"
+#include "config_objegg.h"
+#include "config_egg.h"
+#include "eggData.h"
+#include "string_utils.h"
+#include "streamReader.h"
+#include "virtualFileSystem.h"
+#include "eggPolygon.h"
+#include "eggPoint.h"
+#include "eggLine.h"
+#include "dcast.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+EggToObjConverter::
+EggToObjConverter() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+EggToObjConverter::
+EggToObjConverter(const EggToObjConverter &copy) :
+  EggToSomethingConverter(copy)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::Destructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+EggToObjConverter::
+~EggToObjConverter() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::make_copy
+//       Access: Public, Virtual
+//  Description: Allocates and returns a new copy of the converter.
+////////////////////////////////////////////////////////////////////
+EggToSomethingConverter *EggToObjConverter::
+make_copy() {
+  return new EggToObjConverter(*this);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::get_name
+//       Access: Public, Virtual
+//  Description: Returns the English name of the file type this
+//               converter supports.
+////////////////////////////////////////////////////////////////////
+string EggToObjConverter::
+get_name() const {
+  return "obj";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::get_extension
+//       Access: Public, Virtual
+//  Description: Returns the common extension of the file type this
+//               converter supports.
+////////////////////////////////////////////////////////////////////
+string EggToObjConverter::
+get_extension() const {
+  return "obj";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::supports_compressed
+//       Access: Published, Virtual
+//  Description: Returns true if this file type can transparently save
+//               compressed files (with a .pz extension), false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool EggToObjConverter::
+supports_compressed() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::write_file
+//       Access: Public, Virtual
+//  Description: Handles the conversion of the internal EggData to the
+//               target file format, written to the specified
+//               filename.
+////////////////////////////////////////////////////////////////////
+bool EggToObjConverter::
+write_file(const Filename &filename) {
+  clear_error();
+
+  if (_egg_data->get_coordinate_system() == CS_default) {
+    _egg_data->set_coordinate_system(CS_zup_right);
+  }
+
+  if (!process(filename)) {
+    _error = true;
+  }
+  return !had_error();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::process
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool EggToObjConverter::
+process(const Filename &filename) {
+  _egg_data->flatten_transforms();
+  collect_vertices(_egg_data);
+
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+  Filename obj_filename = Filename::text_filename(filename);
+  vfs->delete_file(obj_filename);
+  ostream *file = vfs->open_write_file(obj_filename, true, true);
+  if (file == (ostream *)NULL) {
+    return false;
+  }
+  if (egg_precision != 0) {
+    file->precision(egg_precision);
+  }
+
+  _current_group = NULL;
+
+  /*
+  (*file) << "\n#\n"
+          << "# obj file generated by the following command:\n"
+          << "# " << get_exec_command() << "\n"
+          << "#\n\n";
+  */
+
+  write_vertices(*file, "v", 3, _unique_vert3);
+  write_vertices(*file, "v", 4, _unique_vert4);
+  write_vertices(*file, "vt", 2, _unique_uv2);
+  write_vertices(*file, "vt", 3, _unique_uv3);
+  write_vertices(*file, "vn", 3, _unique_norm);
+
+  write_faces(*file, _egg_data);
+
+  bool success = (file != (ostream *)NULL);
+  vfs->close_write_file(file);
+
+  return success;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::collect_vertices
+//       Access: Private
+//  Description: Recursively walks the egg structure, looking for
+//               vertices referenced by polygons or points.  Any such
+//               vertices are added to the vertex tables for writing
+//               to the obj file.
+////////////////////////////////////////////////////////////////////
+void EggToObjConverter::
+collect_vertices(EggNode *egg_node) {
+  if (egg_node->is_of_type(EggPrimitive::get_class_type())) {
+    EggPrimitive *egg_prim = DCAST(EggPrimitive, egg_node);
+    EggPrimitive::iterator pi;
+    for (pi = egg_prim->begin(); pi != egg_prim->end(); ++pi) {
+      record_vertex(*pi);
+    }
+
+  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
+    EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
+
+    EggGroupNode::iterator ci;
+    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+      collect_vertices(*ci);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::write_faces
+//       Access: Private
+//  Description: Recursively walks the egg structure again, this time
+//               writing out the face records for any polygons,
+//               points, or lines encountered.
+////////////////////////////////////////////////////////////////////
+void EggToObjConverter::
+write_faces(ostream &out, EggNode *egg_node) {
+  if (egg_node->is_of_type(EggPrimitive::get_class_type())) {
+    const char *prim_type = NULL;
+    if (egg_node->is_of_type(EggPolygon::get_class_type())) {
+      prim_type = "f";
+    } else if (egg_node->is_of_type(EggPoint::get_class_type())) {
+      prim_type = "p";
+    } else if (egg_node->is_of_type(EggLine::get_class_type())) {
+      prim_type = "l";
+    }
+
+    if (prim_type != NULL) {
+      write_group_reference(out, egg_node);
+
+      EggPrimitive *egg_prim = DCAST(EggPrimitive, egg_node);
+
+      out << prim_type;
+      EggPrimitive::iterator pi;
+      for (pi = egg_prim->begin(); pi != egg_prim->end(); ++pi) {
+        VertexDef &vdef = _vmap[(*pi)];
+        int vert_index = -1;
+        int uv_index = -1;
+        int norm_index = -1;
+
+        if (vdef._vert3_index != -1) {
+          vert_index = vdef._vert3_index + 1;
+        } else if (vdef._vert4_index != -1) {
+          vert_index = vdef._vert4_index + 1 + (int)_unique_vert3.size();
+        }
+
+        if (vdef._uv2_index != -1) {
+          uv_index = vdef._uv2_index + 1;
+        } else if (vdef._uv3_index != -1) {
+          uv_index = vdef._uv3_index + 1 + (int)_unique_uv2.size();
+        }
+
+        if (vdef._norm_index != -1) {
+          norm_index = vdef._norm_index + 1;
+        }
+
+        if (vert_index == -1) {
+          continue;
+        }
+
+        if (norm_index != -1) {
+          if (uv_index != -1) {
+            out << " " << vert_index << "/" << uv_index << "/" << norm_index;
+          } else {
+            out << " " << vert_index << "//" << norm_index;
+          }
+        } else if (uv_index != -1) {
+          out << " " << vert_index << "/" << uv_index;
+        } else {
+          out << " " << vert_index;
+        }
+      }
+      out << "\n";
+    }
+  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
+    EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
+
+    EggGroupNode::iterator ci;
+    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+      write_faces(out, *ci);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::write_group_reference
+//       Access: Private
+//  Description: Writes the "g" tag to describe this polygon's group,
+//               if needed.
+////////////////////////////////////////////////////////////////////
+void EggToObjConverter::
+write_group_reference(ostream &out, EggNode *egg_node) {
+  EggGroupNode *egg_group = egg_node->get_parent();
+  if (egg_group == _current_group) {
+    // Same group we wrote last time.
+    return;
+  }
+
+  string group_name;
+  get_group_name(group_name, egg_group);
+  if (group_name.empty()) {
+    out << "g default\n";
+  } else {
+    out << "g" << group_name << "\n";
+  }
+  _current_group = egg_group;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::get_group_name
+//       Access: Private
+//  Description: Recursively determines the appropriate string to
+//               write for the "g" tag to describe a particular
+//               EggGroupNode.
+////////////////////////////////////////////////////////////////////
+void EggToObjConverter::
+get_group_name(string &group_name, EggGroupNode *egg_group) {
+  string name = trim(egg_group->get_name());
+  if (!name.empty()) {
+    group_name += ' ';
+
+    // Remove nonstandard characters.
+    for (string::const_iterator ni = name.begin(); ni != name.end(); ++ni) {
+      char c = (*ni);
+      if (!isalnum(c)) {
+        c = '_';
+      }
+      group_name += c;
+    }
+  }
+
+  // Now recurse.
+  EggGroupNode *egg_parent = egg_group->get_parent();
+  if (egg_parent != NULL) {
+    get_group_name(group_name, egg_parent);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::record_vertex
+//       Access: Private
+//  Description: Adds the indicated EggVertex to the unique vertex
+//               tables, for writing later by write_vertices().
+////////////////////////////////////////////////////////////////////
+void EggToObjConverter::
+record_vertex(EggVertex *vertex) {
+  VertexDef &vdef = _vmap[vertex];
+
+  switch (vertex->get_num_dimensions()) {
+  case 1:
+    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos1());
+    break;
+  case 2:
+    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos2());
+    break;
+  case 3:
+    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos3());
+    break;
+  case 4:
+    vdef._vert4_index = record_unique(_unique_vert4, vertex->get_pos4());
+    break;
+  }
+
+  if (vertex->has_uv("")) {
+    vdef._uv2_index = record_unique(_unique_uv2, vertex->get_uv(""));
+  } else if (vertex->has_uvw("")) {
+    vdef._uv3_index = record_unique(_unique_uv3, vertex->get_uvw(""));
+  }
+
+  if (vertex->has_normal()) {
+    vdef._norm_index = record_unique(_unique_norm, vertex->get_normal());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::record_unique
+//       Access: Private
+//  Description: Records the indicated vertex value, returning the
+//               shared index if this value already appears elsewhere
+//               in the table, or the new unique index if this is the
+//               first time this value appears.
+////////////////////////////////////////////////////////////////////
+int EggToObjConverter::
+record_unique(UniqueVertices &unique, const LVecBase4d &vec) {
+  // We record a zero-based index.  Note that we will actually write
+  // out a one-based index to the obj file, as required by the
+  // standard.
+  int index = unique.size();
+  UniqueVertices::iterator ui = unique.insert(UniqueVertices::value_type(vec, index)).first;
+  return (*ui).second;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::record_unique
+//       Access: Private
+//  Description: Records the indicated vertex value, returning the
+//               shared index if this value already appears elsewhere
+//               in the table, or the new unique index if this is the
+//               first time this value appears.
+////////////////////////////////////////////////////////////////////
+int EggToObjConverter::
+record_unique(UniqueVertices &unique, const LVecBase3d &vec) {
+  return record_unique(unique, LVecBase4d(vec[0], vec[1], vec[2], 0.0));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::record_unique
+//       Access: Private
+//  Description: Records the indicated vertex value, returning the
+//               shared index if this value already appears elsewhere
+//               in the table, or the new unique index if this is the
+//               first time this value appears.
+////////////////////////////////////////////////////////////////////
+int EggToObjConverter::
+record_unique(UniqueVertices &unique, const LVecBase2d &vec) {
+  return record_unique(unique, LVecBase4d(vec[0], vec[1], 0.0, 0.0));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::record_unique
+//       Access: Private
+//  Description: Records the indicated vertex value, returning the
+//               shared index if this value already appears elsewhere
+//               in the table, or the new unique index if this is the
+//               first time this value appears.
+////////////////////////////////////////////////////////////////////
+int EggToObjConverter::
+record_unique(UniqueVertices &unique, double pos) {
+  return record_unique(unique, LVecBase4d(pos, 0.0, 0.0, 0.0));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::write_vertices
+//       Access: Private
+//  Description: Actually writes the vertex values recorded in the
+//               indicated table to the obj output stream.
+////////////////////////////////////////////////////////////////////
+void EggToObjConverter::
+write_vertices(ostream &out, const string &prefix, int num_components,
+               const UniqueVertices &unique) {
+  // First, sort the list into numeric order.
+  int num_vertices = (int)unique.size();
+  const LVecBase4d **vertices = (const LVecBase4d **)PANDA_MALLOC_ARRAY(num_vertices * sizeof(LVecBase4d *));
+  memset(vertices, 0, num_vertices * sizeof(LVecBase4d *));
+  UniqueVertices::const_iterator ui;
+  for (ui = unique.begin(); ui != unique.end(); ++ui) {
+    int index = (*ui).second;
+    const LVecBase4d &vec = (*ui).first;
+    nassertv(index >= 0 && index < num_vertices);
+    nassertv(vertices[index] == NULL);
+    vertices[index] = &vec;
+  }
+
+  for (int i = 0; i < num_vertices; ++i) {
+    out << prefix;
+    const LVecBase4d &vec = *(vertices[i]);
+    for (int ci = 0; ci < num_components; ++ci) {
+      out << " " << vec[ci];
+    }
+    out << "\n";
+  }
+  PANDA_FREE_ARRAY(vertices);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::VertexDef::Constructor
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+EggToObjConverter::VertexDef::
+VertexDef() :
+  _vert3_index(-1),
+  _vert4_index(-1),
+  _uv2_index(-1),
+  _uv3_index(-1),
+  _norm_index(-1)
+{
+}

+ 1188 - 1188
pandatool/src/objegg/objToEggConverter.cxx

@@ -1,1188 +1,1188 @@
-// Filename: objToEggConverter.cxx
-// Created by:  drose (07Dec10)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 "objToEggConverter.h"
-#include "config_objegg.h"
-#include "eggData.h"
-#include "string_utils.h"
-#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
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-ObjToEggConverter::
-ObjToEggConverter() {
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::Copy Constructor
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-ObjToEggConverter::
-ObjToEggConverter(const ObjToEggConverter &copy) :
-  SomethingToEggConverter(copy)
-{
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::Destructor
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-ObjToEggConverter::
-~ObjToEggConverter() {
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::make_copy
-//       Access: Public, Virtual
-//  Description: Allocates and returns a new copy of the converter.
-////////////////////////////////////////////////////////////////////
-SomethingToEggConverter *ObjToEggConverter::
-make_copy() {
-  return new ObjToEggConverter(*this);
-}
-
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::get_name
-//       Access: Public, Virtual
-//  Description: Returns the English name of the file type this
-//               converter supports.
-////////////////////////////////////////////////////////////////////
-string ObjToEggConverter::
-get_name() const {
-  return "obj";
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::get_extension
-//       Access: Public, Virtual
-//  Description: Returns the common extension of the file type this
-//               converter supports.
-////////////////////////////////////////////////////////////////////
-string ObjToEggConverter::
-get_extension() const {
-  return "obj";
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::supports_compressed
-//       Access: Published, Virtual
-//  Description: Returns true if this file type can transparently load
-//               compressed files (with a .pz extension), false
-//               otherwise.
-////////////////////////////////////////////////////////////////////
-bool ObjToEggConverter::
-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
-//  Description: Handles the reading of the input file and converting
-//               it to egg.  Returns true if successful, false
-//               otherwise.
-////////////////////////////////////////////////////////////////////
-bool ObjToEggConverter::
-convert_file(const Filename &filename) {
-  clear_error();
-
-  if (_egg_data->get_coordinate_system() == CS_default) {
-    _egg_data->set_coordinate_system(CS_zup_right);
-  }
-
-  if (!process(filename)) {
-    _error = true;
-  }
-  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: Reads the file and converts it to egg structures.
-////////////////////////////////////////////////////////////////////
-bool ObjToEggConverter::
-process(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();
-  _xvt_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);
-  _root_group = new EggGroup("root");
-  _egg_data->add_child(_root_group);
-  _current_group = _root_group;
-
-  StreamReader sr(strm, true);
-  string line = sr.readline();
-  _line_number = 1;
-  while (!line.empty()) {
-    line = trim(line);
-    if (line.empty()) {
-      line = sr.readline();
-      continue;
-    }
-
-    while (line[line.length() - 1] == '\\') {
-      // If it ends on a backslash, it's a continuation character.
-      string line2 = sr.readline();
-      ++_line_number;
-      if (line2.empty()) {
-        break;
-      }
-      line = line.substr(0, line.length() - 1) + trim(line2);
-    }
-
-    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(line)) {
-      return false;
-    }
-    line = sr.readline();
-    ++_line_number;
-  }
-
-  if (!_f_given) {
-    generate_egg_points();
-  }
-
-  return true;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::process_line
-//       Access: Protected
-//  Description:
-////////////////////////////////////////////////////////////////////
-bool ObjToEggConverter::
-process_line(const string &line) {
-  vector_string words;
-  tokenize(line, words, " \t", true);
-  nassertr(!words.empty(), false);
-
-  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(words);
-  } else if (tag == "g") {
-    return process_g(words);
-  } else {
-    bool inserted = _ignored_tags.insert(tag).second;
-    if (inserted) {
-      objegg_cat.info()
-        << "Ignoring tag " << tag << "\n";
-    }
-  }
-
-  return true;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::process_line
-//       Access: Protected
-//  Description:
-////////////////////////////////////////////////////////////////////
-bool ObjToEggConverter::
-process_ref_plane_res(const string &line) {
-  // the #_ref_plane_res line is a DRZ extension that defines the
-  // pixel resolution of the projector device.  It's needed to
-  // properly scale the xvt lines.
-
-  vector_string words;
-  tokenize(line, words, " \t", true);
-  nassertr(!words.empty(), false);
-
-  if (words.size() != 3) {
-    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], _ref_plane_res[0]);
-  okflag &= string_to_double(words[2], _ref_plane_res[1]);
-
-  if (!okflag) {
-    objegg_cat.error()
-      << "Invalid number at line " << _line_number << ":\n";
-    return false;
-  }
-
-  return true;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::process_v
-//       Access: Protected
-//  Description:
-////////////////////////////////////////////////////////////////////
-bool ObjToEggConverter::
-process_v(vector_string &words) {
-  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;
-  LPoint4d 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]);
-  if (words.size() == 5 || words.size() == 8) {
-    okflag &= string_to_double(words[4], pos[3]);
-    _v4_given = true;
-  } else {
-    pos[3] = 1.0;
-  }
-
-  if (!okflag) {
-    objegg_cat.error()
-      << "Invalid number at line " << _line_number << "\n";
-    return false;
-  }
-
-  _v_table.push_back(pos);
-
-  // Meshlab format might include an RGB color following the vertex
-  // position.
-  if (words.size() == 7 && words.size() == 8) {
-    size_t si = words.size();
-    LVecBase3d rgb;
-    okflag &= string_to_double(words[si - 3], rgb[0]);
-    okflag &= string_to_double(words[si - 2], rgb[1]);
-    okflag &= string_to_double(words[si - 1], rgb[2]);
-
-    if (!okflag) {
-      objegg_cat.error()
-        << "Invalid number at line " << _line_number << "\n";
-      return false;
-    }
-    while (_rgb_table.size() + 1 < _v_table.size()) {
-      _rgb_table.push_back(LVecBase3d(1.0, 1.0, 1.0));
-    }
-    _rgb_table.push_back(rgb);
-  }
-
-  return true;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::process_vt
-//       Access: Protected
-//  Description:
-////////////////////////////////////////////////////////////////////
-bool ObjToEggConverter::
-process_vt(vector_string &words) {
-  if (words.size() != 3 && words.size() != 4) {
-    objegg_cat.error()
-      << "Wrong number of tokens at line " << _line_number << "\n";
-    return false;
-  }
-
-  bool okflag = true;
-  LTexCoord3d uvw;
-  okflag &= string_to_double(words[1], uvw[0]);
-  okflag &= string_to_double(words[2], uvw[1]);
-  if (words.size() == 4) {
-    okflag &= string_to_double(words[3], uvw[2]);
-    _vt3_given = true;
-  } else {
-    uvw[2] = 0.0;
-  }
-
-  if (!okflag) {
-    objegg_cat.error()
-      << "Invalid number at line " << _line_number << "\n";
-    return false;
-  }
-
-  _vt_table.push_back(uvw);
-
-  return true;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::process_xvt
-//       Access: Protected
-//  Description: "xvt" is an extended column invented by DRZ.  It
-//               includes texture coordinates in pixel space of the
-//               projector device, as well as for each camera.  We map
-//               it to the nominal texture coordinates here.
-////////////////////////////////////////////////////////////////////
-bool ObjToEggConverter::
-process_xvt(vector_string &words) {
-  if (words.size() < 3) {
-    objegg_cat.error()
-      << "Wrong number of tokens at line " << _line_number << "\n";
-    return false;
-  }
-
-  bool okflag = true;
-  LTexCoordd uv;
-  okflag &= string_to_double(words[1], uv[0]);
-  okflag &= string_to_double(words[2], uv[1]);
-
-  if (!okflag) {
-    objegg_cat.error()
-      << "Invalid number at line " << _line_number << "\n";
-    return false;
-  }
-
-  uv[0] /= _ref_plane_res[0];
-  uv[1] = 1.0 - uv[1] / _ref_plane_res[1];
-
-  _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
-//  Description:
-////////////////////////////////////////////////////////////////////
-bool ObjToEggConverter::
-process_vn(vector_string &words) {
-  if (words.size() != 4) {
-    objegg_cat.error()
-      << "Wrong number of tokens at line " << _line_number << "\n";
-    return false;
-  }
-
-  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]);
-
-  if (!okflag) {
-    objegg_cat.error()
-      << "Invalid number at line " << _line_number << "\n";
-    return false;
-  }
-  normal.normalize();
-
-  _vn_table.push_back(normal);
-
-  return true;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::process_f
-//       Access: Protected
-//  Description: Defines a face in the obj file.
-////////////////////////////////////////////////////////////////////
-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]);
-    if (vertex == NULL) {
-      return false;
-    }
-    poly->add_vertex(vertex);
-  }
-  _current_group->add_child(poly);
-
-  return true;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::process_g
-//       Access: Protected
-//  Description: Defines a group in the obj file.
-////////////////////////////////////////////////////////////////////
-bool ObjToEggConverter::
-process_g(vector_string &words) {
-  EggGroup *group = _root_group;
-
-  // 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();
-  while (i > 1) {
-    --i;
-    EggNode *child = group->find_child(words[i]);
-    if (child == NULL || !child->is_of_type(EggGroup::get_class_type())) {
-      child = new EggGroup(words[i]);
-      group->add_child(child);
-    }
-    group = DCAST(EggGroup, child);
-  }
-
-  _current_group = group;
-  return true;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::get_face_vertex
-//       Access: Protected
-//  Description: Returns or creates a vertex in the vpool according to
-//               the indicated face reference.
-////////////////////////////////////////////////////////////////////
-EggVertex *ObjToEggConverter::
-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 {
-      LPoint4d 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.
-      LRGBColord rgb = _rgb_table[entry._vi - 1];
-      LColor rgba(rgb[0], rgb[1], rgb[2], 1.0);
-      synth.set_color(rgba);
-    }
-  }
-
-  if (entry._vti != 0) {
-    // We have a texture coordinate; apply it.
-    if (_vt3_given) {
-      synth.set_uvw("", _vt_table[entry._vti - 1]);
-    } else {
-      LTexCoord3d 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("", _xvt_table[entry._vi - 1]);
-  }
-
-  if (entry._vni != 0) {
-    // We have a normal; apply it.
-    synth.set_normal(_vn_table[entry._vni - 1]);
-  }
-
-  return _vpool->create_unique_vertex(synth);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::generate_egg_points
-//       Access: Protected
-//  Description: If an obj file defines no faces, create a bunch of
-//               EggVertex objects to illustrate the vertex positions
-//               at least.
-////////////////////////////////////////////////////////////////////
-void ObjToEggConverter::
-generate_egg_points() {
-  for (size_t vi = 0; vi < _v_table.size(); ++vi) {
-    const LVecBase4d &p = _v_table[vi];
-    _vpool->make_new_vertex(LVecBase3d(p[0], p[1], p[2]));
-  }
-}
-
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::process_node
-//       Access: Protected
-//  Description: Reads the file and converts it to PandaNode structures.
-////////////////////////////////////////////////////////////////////
-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();
-  _xvt_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(line, words, " \t", true);
-  nassertr(!words.empty(), false);
-
-  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;
-  }
-
-  int synth_vni = 0;
-  if (!all_vn) {
-    // Synthesize a normal if we need it.
-    LNormald normal = LNormald::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 LVecBase4d &p0 = _v_table[vi0 - 1];
-      const LVecBase4d &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();
-    synth_vni = add_synth_normal(normal);
-  }
-
-  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 LVecBase4d &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);
-  }
-
-  if (verts.size() == 3) {
-    // It's already a triangle; add it directly.
-    _current_vertex_data->add_triangle(this, verts[0], verts[1], verts[2], synth_vni);
-
-  } else {
-    // Get the triangulated results.
-    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(this, verts[i0], verts[i1], verts[i2], synth_vni);
-    }
-  }
-
-  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 LVecBase4d &p = _v_table[vi];
-    vertex_writer.add_data3d(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 LVecBase3d &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 (trim(words[i]).empty()) {
-      index = 0;
-    } else {
-      if (!string_to_int(words[i], index)) {
-        index = 0;
-      }
-    }
-
-    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;
-    }
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     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);
-}
-
-////////////////////////////////////////////////////////////////////
-//     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;
-    }
-    if (entry._vni != 0) {
-      _vn_given = true;
-    }
-  }
-
-  return index;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: ObjToEggConverter::VertexData::add_triangle
-//       Access: Public
-//  Description: Adds a triangle to the primitive, as a triple of
-//               three VertexEntry objects, which are each added to
-//               the vertex pool.  If synth_vni is not 0, it is
-//               assigned to the last vertex.
-////////////////////////////////////////////////////////////////////
-void ObjToEggConverter::VertexData::
-add_triangle(const ObjToEggConverter *converter, const VertexEntry &v0,
-             const VertexEntry &v1, const VertexEntry &v2,
-             int synth_vni) {
-  int v0i, v1i, v2i;
-
-  v0i = add_vertex(converter, v0);
-  v1i = add_vertex(converter, v1);
-
-  if (synth_vni != 0) {
-    VertexEntry v2n(v2);
-    v2n._synth_vni = synth_vni;
-    v2i = add_vertex(converter, v2n);
-  } else {
-    v2i = add_vertex(converter, v2);
-  }
-
-  _prim->add_vertices(v0i, v1i, v2i);
-  _prim->close_primitive();
-}
-
-////////////////////////////////////////////////////////////////////
-//     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_data4d(converter->_v_table[entry._vi - 1]);
-      }
-      if (entry._vti != 0) {
-        texcoord_writer.set_row(i);
-        texcoord_writer.add_data3d(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_data2d(converter->_xvt_table[entry._vi - 1]);
-      }
-      if (entry._vni != 0) {
-        normal_writer.set_row(i);
-        normal_writer.add_data3d(converter->_vn_table[entry._vni - 1]);
-      } else if (entry._synth_vni != 0) {
-        normal_writer.set_row(i);
-        normal_writer.add_data3d(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_data3d(0, 0, 1);
-      }
-      if (_rgb_given) {
-        if (entry._vi - 1 < (int)converter->_rgb_table.size()) {
-          color_writer.set_row(i);
-          color_writer.add_data3d(converter->_rgb_table[entry._vi - 1]);
-        }
-      }
-    }
-
-    // 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 (!_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);
-    }
-
-    PT(Geom) geom = new Geom(vdata);
-    geom->add_primitive(_prim);
-
-    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();
-}
+// Filename: objToEggConverter.cxx
+// Created by:  drose (07Dec10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "objToEggConverter.h"
+#include "config_objegg.h"
+#include "eggData.h"
+#include "string_utils.h"
+#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
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+ObjToEggConverter::
+ObjToEggConverter() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+ObjToEggConverter::
+ObjToEggConverter(const ObjToEggConverter &copy) :
+  SomethingToEggConverter(copy)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::Destructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+ObjToEggConverter::
+~ObjToEggConverter() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::make_copy
+//       Access: Public, Virtual
+//  Description: Allocates and returns a new copy of the converter.
+////////////////////////////////////////////////////////////////////
+SomethingToEggConverter *ObjToEggConverter::
+make_copy() {
+  return new ObjToEggConverter(*this);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::get_name
+//       Access: Public, Virtual
+//  Description: Returns the English name of the file type this
+//               converter supports.
+////////////////////////////////////////////////////////////////////
+string ObjToEggConverter::
+get_name() const {
+  return "obj";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::get_extension
+//       Access: Public, Virtual
+//  Description: Returns the common extension of the file type this
+//               converter supports.
+////////////////////////////////////////////////////////////////////
+string ObjToEggConverter::
+get_extension() const {
+  return "obj";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::supports_compressed
+//       Access: Published, Virtual
+//  Description: Returns true if this file type can transparently load
+//               compressed files (with a .pz extension), false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+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
+//  Description: Handles the reading of the input file and converting
+//               it to egg.  Returns true if successful, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+convert_file(const Filename &filename) {
+  clear_error();
+
+  if (_egg_data->get_coordinate_system() == CS_default) {
+    _egg_data->set_coordinate_system(CS_zup_right);
+  }
+
+  if (!process(filename)) {
+    _error = true;
+  }
+  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: Reads the file and converts it to egg structures.
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+process(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();
+  _xvt_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);
+  _root_group = new EggGroup("root");
+  _egg_data->add_child(_root_group);
+  _current_group = _root_group;
+
+  StreamReader sr(strm, true);
+  string line = sr.readline();
+  _line_number = 1;
+  while (!line.empty()) {
+    line = trim(line);
+    if (line.empty()) {
+      line = sr.readline();
+      continue;
+    }
+
+    while (line[line.length() - 1] == '\\') {
+      // If it ends on a backslash, it's a continuation character.
+      string line2 = sr.readline();
+      ++_line_number;
+      if (line2.empty()) {
+        break;
+      }
+      line = line.substr(0, line.length() - 1) + trim(line2);
+    }
+
+    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(line)) {
+      return false;
+    }
+    line = sr.readline();
+    ++_line_number;
+  }
+
+  if (!_f_given) {
+    generate_egg_points();
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::process_line
+//       Access: Protected
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+process_line(const string &line) {
+  vector_string words;
+  tokenize(line, words, " \t", true);
+  nassertr(!words.empty(), false);
+
+  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(words);
+  } else if (tag == "g") {
+    return process_g(words);
+  } else {
+    bool inserted = _ignored_tags.insert(tag).second;
+    if (inserted) {
+      objegg_cat.info()
+        << "Ignoring tag " << tag << "\n";
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::process_line
+//       Access: Protected
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+process_ref_plane_res(const string &line) {
+  // the #_ref_plane_res line is a DRZ extension that defines the
+  // pixel resolution of the projector device.  It's needed to
+  // properly scale the xvt lines.
+
+  vector_string words;
+  tokenize(line, words, " \t", true);
+  nassertr(!words.empty(), false);
+
+  if (words.size() != 3) {
+    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], _ref_plane_res[0]);
+  okflag &= string_to_double(words[2], _ref_plane_res[1]);
+
+  if (!okflag) {
+    objegg_cat.error()
+      << "Invalid number at line " << _line_number << ":\n";
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::process_v
+//       Access: Protected
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+process_v(vector_string &words) {
+  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;
+  LPoint4d 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]);
+  if (words.size() == 5 || words.size() == 8) {
+    okflag &= string_to_double(words[4], pos[3]);
+    _v4_given = true;
+  } else {
+    pos[3] = 1.0;
+  }
+
+  if (!okflag) {
+    objegg_cat.error()
+      << "Invalid number at line " << _line_number << "\n";
+    return false;
+  }
+
+  _v_table.push_back(pos);
+
+  // Meshlab format might include an RGB color following the vertex
+  // position.
+  if (words.size() == 7 && words.size() == 8) {
+    size_t si = words.size();
+    LVecBase3d rgb;
+    okflag &= string_to_double(words[si - 3], rgb[0]);
+    okflag &= string_to_double(words[si - 2], rgb[1]);
+    okflag &= string_to_double(words[si - 1], rgb[2]);
+
+    if (!okflag) {
+      objegg_cat.error()
+        << "Invalid number at line " << _line_number << "\n";
+      return false;
+    }
+    while (_rgb_table.size() + 1 < _v_table.size()) {
+      _rgb_table.push_back(LVecBase3d(1.0, 1.0, 1.0));
+    }
+    _rgb_table.push_back(rgb);
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::process_vt
+//       Access: Protected
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+process_vt(vector_string &words) {
+  if (words.size() != 3 && words.size() != 4) {
+    objegg_cat.error()
+      << "Wrong number of tokens at line " << _line_number << "\n";
+    return false;
+  }
+
+  bool okflag = true;
+  LTexCoord3d uvw;
+  okflag &= string_to_double(words[1], uvw[0]);
+  okflag &= string_to_double(words[2], uvw[1]);
+  if (words.size() == 4) {
+    okflag &= string_to_double(words[3], uvw[2]);
+    _vt3_given = true;
+  } else {
+    uvw[2] = 0.0;
+  }
+
+  if (!okflag) {
+    objegg_cat.error()
+      << "Invalid number at line " << _line_number << "\n";
+    return false;
+  }
+
+  _vt_table.push_back(uvw);
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::process_xvt
+//       Access: Protected
+//  Description: "xvt" is an extended column invented by DRZ.  It
+//               includes texture coordinates in pixel space of the
+//               projector device, as well as for each camera.  We map
+//               it to the nominal texture coordinates here.
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+process_xvt(vector_string &words) {
+  if (words.size() < 3) {
+    objegg_cat.error()
+      << "Wrong number of tokens at line " << _line_number << "\n";
+    return false;
+  }
+
+  bool okflag = true;
+  LTexCoordd uv;
+  okflag &= string_to_double(words[1], uv[0]);
+  okflag &= string_to_double(words[2], uv[1]);
+
+  if (!okflag) {
+    objegg_cat.error()
+      << "Invalid number at line " << _line_number << "\n";
+    return false;
+  }
+
+  uv[0] /= _ref_plane_res[0];
+  uv[1] = 1.0 - uv[1] / _ref_plane_res[1];
+
+  _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
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+process_vn(vector_string &words) {
+  if (words.size() != 4) {
+    objegg_cat.error()
+      << "Wrong number of tokens at line " << _line_number << "\n";
+    return false;
+  }
+
+  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]);
+
+  if (!okflag) {
+    objegg_cat.error()
+      << "Invalid number at line " << _line_number << "\n";
+    return false;
+  }
+  normal.normalize();
+
+  _vn_table.push_back(normal);
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::process_f
+//       Access: Protected
+//  Description: Defines a face in the obj file.
+////////////////////////////////////////////////////////////////////
+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]);
+    if (vertex == NULL) {
+      return false;
+    }
+    poly->add_vertex(vertex);
+  }
+  _current_group->add_child(poly);
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::process_g
+//       Access: Protected
+//  Description: Defines a group in the obj file.
+////////////////////////////////////////////////////////////////////
+bool ObjToEggConverter::
+process_g(vector_string &words) {
+  EggGroup *group = _root_group;
+
+  // 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();
+  while (i > 1) {
+    --i;
+    EggNode *child = group->find_child(words[i]);
+    if (child == NULL || !child->is_of_type(EggGroup::get_class_type())) {
+      child = new EggGroup(words[i]);
+      group->add_child(child);
+    }
+    group = DCAST(EggGroup, child);
+  }
+
+  _current_group = group;
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::get_face_vertex
+//       Access: Protected
+//  Description: Returns or creates a vertex in the vpool according to
+//               the indicated face reference.
+////////////////////////////////////////////////////////////////////
+EggVertex *ObjToEggConverter::
+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 {
+      LPoint4d 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.
+      LRGBColord rgb = _rgb_table[entry._vi - 1];
+      LColor rgba(rgb[0], rgb[1], rgb[2], 1.0);
+      synth.set_color(rgba);
+    }
+  }
+
+  if (entry._vti != 0) {
+    // We have a texture coordinate; apply it.
+    if (_vt3_given) {
+      synth.set_uvw("", _vt_table[entry._vti - 1]);
+    } else {
+      LTexCoord3d 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("", _xvt_table[entry._vi - 1]);
+  }
+
+  if (entry._vni != 0) {
+    // We have a normal; apply it.
+    synth.set_normal(_vn_table[entry._vni - 1]);
+  }
+
+  return _vpool->create_unique_vertex(synth);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::generate_egg_points
+//       Access: Protected
+//  Description: If an obj file defines no faces, create a bunch of
+//               EggVertex objects to illustrate the vertex positions
+//               at least.
+////////////////////////////////////////////////////////////////////
+void ObjToEggConverter::
+generate_egg_points() {
+  for (size_t vi = 0; vi < _v_table.size(); ++vi) {
+    const LVecBase4d &p = _v_table[vi];
+    _vpool->make_new_vertex(LVecBase3d(p[0], p[1], p[2]));
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::process_node
+//       Access: Protected
+//  Description: Reads the file and converts it to PandaNode structures.
+////////////////////////////////////////////////////////////////////
+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();
+  _xvt_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(line, words, " \t", true);
+  nassertr(!words.empty(), false);
+
+  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;
+  }
+
+  int synth_vni = 0;
+  if (!all_vn) {
+    // Synthesize a normal if we need it.
+    LNormald normal = LNormald::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 LVecBase4d &p0 = _v_table[vi0 - 1];
+      const LVecBase4d &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();
+    synth_vni = add_synth_normal(normal);
+  }
+
+  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 LVecBase4d &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);
+  }
+
+  if (verts.size() == 3) {
+    // It's already a triangle; add it directly.
+    _current_vertex_data->add_triangle(this, verts[0], verts[1], verts[2], synth_vni);
+
+  } else {
+    // Get the triangulated results.
+    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(this, verts[i0], verts[i1], verts[i2], synth_vni);
+    }
+  }
+
+  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 LVecBase4d &p = _v_table[vi];
+    vertex_writer.add_data3d(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 LVecBase3d &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 (trim(words[i]).empty()) {
+      index = 0;
+    } else {
+      if (!string_to_int(words[i], index)) {
+        index = 0;
+      }
+    }
+
+    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;
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     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);
+}
+
+////////////////////////////////////////////////////////////////////
+//     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;
+    }
+    if (entry._vni != 0) {
+      _vn_given = true;
+    }
+  }
+
+  return index;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjToEggConverter::VertexData::add_triangle
+//       Access: Public
+//  Description: Adds a triangle to the primitive, as a triple of
+//               three VertexEntry objects, which are each added to
+//               the vertex pool.  If synth_vni is not 0, it is
+//               assigned to the last vertex.
+////////////////////////////////////////////////////////////////////
+void ObjToEggConverter::VertexData::
+add_triangle(const ObjToEggConverter *converter, const VertexEntry &v0,
+             const VertexEntry &v1, const VertexEntry &v2,
+             int synth_vni) {
+  int v0i, v1i, v2i;
+
+  v0i = add_vertex(converter, v0);
+  v1i = add_vertex(converter, v1);
+
+  if (synth_vni != 0) {
+    VertexEntry v2n(v2);
+    v2n._synth_vni = synth_vni;
+    v2i = add_vertex(converter, v2n);
+  } else {
+    v2i = add_vertex(converter, v2);
+  }
+
+  _prim->add_vertices(v0i, v1i, v2i);
+  _prim->close_primitive();
+}
+
+////////////////////////////////////////////////////////////////////
+//     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_data4d(converter->_v_table[entry._vi - 1]);
+      }
+      if (entry._vti != 0) {
+        texcoord_writer.set_row(i);
+        texcoord_writer.add_data3d(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_data2d(converter->_xvt_table[entry._vi - 1]);
+      }
+      if (entry._vni != 0) {
+        normal_writer.set_row(i);
+        normal_writer.add_data3d(converter->_vn_table[entry._vni - 1]);
+      } else if (entry._synth_vni != 0) {
+        normal_writer.set_row(i);
+        normal_writer.add_data3d(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_data3d(0, 0, 1);
+      }
+      if (_rgb_given) {
+        if (entry._vi - 1 < (int)converter->_rgb_table.size()) {
+          color_writer.set_row(i);
+          color_writer.add_data3d(converter->_rgb_table[entry._vi - 1]);
+        }
+      }
+    }
+
+    // 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 (!_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);
+    }
+
+    PT(Geom) geom = new Geom(vdata);
+    geom->add_primitive(_prim);
+
+    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();
+}

+ 152 - 152
pandatool/src/objegg/objToEggConverter.h

@@ -1,152 +1,152 @@
-// Filename: ObjToEggConverter.h
-// Created by:  drose (07Dec10)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 OBJTOEGGCONVERTER_H
-#define OBJTOEGGCONVERTER_H
-
-#include "pandatoolbase.h"
-
-#include "somethingToEggConverter.h"
-#include "eggVertexPool.h"
-#include "eggGroup.h"
-#include "geomVertexData.h"
-#include "geomVertexWriter.h"
-#include "geomPrimitive.h"
-#include "geomNode.h"
-#include "pandaNode.h"
-#include "pvector.h"
-#include "epvector.h"
-
-////////////////////////////////////////////////////////////////////
-//       Class : ObjToEggConverter
-// Description : Convert an Obj file to egg data.
-////////////////////////////////////////////////////////////////////
-class ObjToEggConverter : public SomethingToEggConverter {
-public:
-  ObjToEggConverter();
-  ObjToEggConverter(const ObjToEggConverter &copy);
-  ~ObjToEggConverter();
-
-  virtual SomethingToEggConverter *make_copy();
-
-  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);
-  bool process_line(const string &line);
-  bool process_ref_plane_res(const string &line);
-
-  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_face_vertex(const string &face_reference);
-  void generate_egg_points();
-
-  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 LVecBase3d &normal);
-
-  // Read from the obj file.
-  int _line_number;
-  typedef epvector<LVecBase4d> Vec4Table;
-  typedef epvector<LVecBase3d> Vec3Table;
-  typedef epvector<LVecBase2d> Vec2Table;
-  typedef pmap<LVecBase3d, 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;
-  LVecBase2d _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;
-
-  // Structures filled when creating a PandaNode directly.
-  PT(PandaNode) _root_node;
-
-  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(const ObjToEggConverter *converter, const VertexEntry &v0,
-                      const VertexEntry &v1, const VertexEntry &v2,
-                      int synth_vni);
-    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
+// Filename: ObjToEggConverter.h
+// Created by:  drose (07Dec10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 OBJTOEGGCONVERTER_H
+#define OBJTOEGGCONVERTER_H
+
+#include "pandatoolbase.h"
+
+#include "somethingToEggConverter.h"
+#include "eggVertexPool.h"
+#include "eggGroup.h"
+#include "geomVertexData.h"
+#include "geomVertexWriter.h"
+#include "geomPrimitive.h"
+#include "geomNode.h"
+#include "pandaNode.h"
+#include "pvector.h"
+#include "epvector.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : ObjToEggConverter
+// Description : Convert an Obj file to egg data.
+////////////////////////////////////////////////////////////////////
+class ObjToEggConverter : public SomethingToEggConverter {
+public:
+  ObjToEggConverter();
+  ObjToEggConverter(const ObjToEggConverter &copy);
+  ~ObjToEggConverter();
+
+  virtual SomethingToEggConverter *make_copy();
+
+  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);
+  bool process_line(const string &line);
+  bool process_ref_plane_res(const string &line);
+
+  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_face_vertex(const string &face_reference);
+  void generate_egg_points();
+
+  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 LVecBase3d &normal);
+
+  // Read from the obj file.
+  int _line_number;
+  typedef epvector<LVecBase4d> Vec4Table;
+  typedef epvector<LVecBase3d> Vec3Table;
+  typedef epvector<LVecBase2d> Vec2Table;
+  typedef pmap<LVecBase3d, 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;
+  LVecBase2d _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;
+
+  // Structures filled when creating a PandaNode directly.
+  PT(PandaNode) _root_node;
+
+  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(const ObjToEggConverter *converter, const VertexEntry &v0,
+                      const VertexEntry &v1, const VertexEntry &v2,
+                      int synth_vni);
+    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