From b3b2308c6c04b309b8db2e36cd1952d03cbbc2ff Mon Sep 17 00:00:00 2001 From: David Rose Date: Fri, 4 Jan 2013 00:35:42 +0000 Subject: [PATCH] load obj files directly into PandaNodes for performance --- panda/src/gobj/geom.cxx | 17 +- panda/src/mathutil/Sources.pp | 3 + panda/src/mathutil/p3mathutil_composite2.cxx | 1 + panda/src/mathutil/triangulator.cxx | 56 +- panda/src/mathutil/triangulator.h | 9 +- panda/src/mathutil/triangulator3.I | 61 ++ panda/src/mathutil/triangulator3.cxx | 112 +++ panda/src/mathutil/triangulator3.h | 55 ++ panda/src/putil/string_utils.cxx | 18 +- panda/src/putil/string_utils.h | 3 +- .../src/converter/somethingToEggConverter.cxx | 30 + .../src/converter/somethingToEggConverter.h | 4 + pandatool/src/objegg/Sources.pp | 4 +- pandatool/src/objegg/objToEggConverter.I | 63 ++ pandatool/src/objegg/objToEggConverter.cxx | 880 +++++++++++++++--- pandatool/src/objegg/objToEggConverter.h | 87 +- pandatool/src/ptloader/config_ptloader.cxx | 8 + pandatool/src/ptloader/config_ptloader.h | 2 + .../src/ptloader/loaderFileTypePandatool.cxx | 16 +- 19 files changed, 1271 insertions(+), 158 deletions(-) create mode 100755 panda/src/mathutil/triangulator3.I create mode 100755 panda/src/mathutil/triangulator3.cxx create mode 100755 panda/src/mathutil/triangulator3.h create mode 100755 pandatool/src/objegg/objToEggConverter.I diff --git a/panda/src/gobj/geom.cxx b/panda/src/gobj/geom.cxx index 99aa45ef3f..1a4251e5f6 100644 --- a/panda/src/gobj/geom.cxx +++ b/panda/src/gobj/geom.cxx @@ -328,7 +328,7 @@ set_primitive(int i, const GeomPrimitive *primitive) { nassertv(cdata->_primitive_type == PT_none || cdata->_primitive_type == primitive->get_primitive_type()); - // They also should have the a compatible shade model. + // They also should have a compatible shade model. CPT(GeomPrimitive) compat = primitive->match_shade_model(cdata->_shade_model); nassertv_always(compat != (GeomPrimitive *)NULL); @@ -374,7 +374,7 @@ add_primitive(const GeomPrimitive *primitive) { nassertv(cdata->_primitive_type == PT_none || cdata->_primitive_type == primitive->get_primitive_type()); - // They also should have the a compatible shade model. + // They also should have a compatible shade model. CPT(GeomPrimitive) compat = primitive->match_shade_model(cdata->_shade_model); nassertv_always(compat != (GeomPrimitive *)NULL); @@ -591,6 +591,19 @@ rotate_in_place() { #endif } + switch (cdata->_shade_model) { + case SM_flat_first_vertex: + cdata->_shade_model = SM_flat_last_vertex; + break; + + case SM_flat_last_vertex: + cdata->_shade_model = SM_flat_first_vertex; + break; + + default: + break; + } + cdata->_modified = Geom::get_next_modified(); clear_cache_stage(current_thread); diff --git a/panda/src/mathutil/Sources.pp b/panda/src/mathutil/Sources.pp index 0ccafac9cb..2c9b169a00 100644 --- a/panda/src/mathutil/Sources.pp +++ b/panda/src/mathutil/Sources.pp @@ -40,6 +40,7 @@ stackedPerlinNoise2.h stackedPerlinNoise2.I \ stackedPerlinNoise3.h stackedPerlinNoise3.I \ triangulator.h triangulator.I \ + triangulator3.h triangulator3.I \ unionBoundingVolume.h unionBoundingVolume.I #define INCLUDED_SOURCES \ @@ -66,6 +67,7 @@ stackedPerlinNoise2.cxx \ stackedPerlinNoise3.cxx \ triangulator.cxx \ + triangulator3.cxx \ unionBoundingVolume.cxx #define INSTALL_HEADERS \ @@ -94,6 +96,7 @@ stackedPerlinNoise2.h stackedPerlinNoise2.I \ stackedPerlinNoise3.h stackedPerlinNoise3.I \ triangulator.h triangulator.I \ + triangulator3.h triangulator3.I \ unionBoundingVolume.h unionBoundingVolume.I diff --git a/panda/src/mathutil/p3mathutil_composite2.cxx b/panda/src/mathutil/p3mathutil_composite2.cxx index 0ebf6516d8..cc8d420f69 100644 --- a/panda/src/mathutil/p3mathutil_composite2.cxx +++ b/panda/src/mathutil/p3mathutil_composite2.cxx @@ -16,3 +16,4 @@ #include "stackedPerlinNoise2.cxx" #include "stackedPerlinNoise3.cxx" #include "triangulator.cxx" +#include "triangulator3.cxx" diff --git a/panda/src/mathutil/triangulator.cxx b/panda/src/mathutil/triangulator.cxx index dcc5027812..1e7a477e44 100644 --- a/panda/src/mathutil/triangulator.cxx +++ b/panda/src/mathutil/triangulator.cxx @@ -118,14 +118,27 @@ void Triangulator:: triangulate() { _result.clear(); + // Make sure our index numbers are reasonable. + cleanup_polygon_indices(_polygon); + Holes::iterator hi; + for (hi = _holes.begin(); hi != _holes.end(); ++hi) { + cleanup_polygon_indices(*hi); + } + + if (_polygon.size() < 3) { + // Degenerate case. + return; + } + // Set up the list of segments. seg.clear(); seg.push_back(segment_t()); // we don't use the first entry. make_segment(_polygon, true); - Holes::const_iterator hi; for (hi = _holes.begin(); hi != _holes.end(); ++hi) { - make_segment(*hi, false); + if ((*hi).size() >= 3) { + make_segment(*hi, false); + } } // Shuffle the segment index. @@ -151,6 +164,7 @@ triangulate() { */ choose_idx = 0; + /* //cerr << "got " << num_segments << " segments\n"; for (i = 1; i < (int)seg.size(); ++i) { segment_t &s = seg[i]; @@ -158,6 +172,7 @@ triangulate() { printf(" root0 = %d, root1 = %d\n", s.root0, s.root1); printf(" next = %d, prev = %d\n", s.next, s.prev); } + */ while (construct_trapezoids(num_segments) != 0) { // If there's an error, re-shuffle the index and try again. @@ -262,6 +277,43 @@ get_triangle_v2(int n) const { return _result[n]._v2; } +//////////////////////////////////////////////////////////////////// +// Function: Triangulator::cleanup_polygon_indices +// Access: Protected +// Description: Removes any invalid index numbers from the list. +//////////////////////////////////////////////////////////////////// +void Triangulator:: +cleanup_polygon_indices(vector_int &polygon) { + // First, check for index bounds. + size_t pi = 0; + while (pi < polygon.size()) { + if (polygon[pi] >= 0 && polygon[pi] < _vertices.size()) { + // This vertex is OK. + ++pi; + } else { + // This index is out-of-bounds; remove it. + polygon.erase(_polygon.begin() + pi); + } + } + + // Now, remove any consecutive repeated vertices. + pi = 1; + while (pi < polygon.size()) { + if (_vertices[polygon[pi]] != _vertices[polygon[pi - 1]]) { + // This vertex is OK. + ++pi; + } else { + // This vertex repeats the previous one; remove it. + polygon.erase(_polygon.begin() + pi); + } + } + + if (polygon.size() > 1 && _vertices[polygon.back()] == _vertices[_polygon.front()]) { + // The last vertex repeats the first one; remove it. + polygon.pop_back(); + } +} + // The remainder of the code in this file is adapted more or less from // the C code published with the referenced paper. diff --git a/panda/src/mathutil/triangulator.h b/panda/src/mathutil/triangulator.h index f47ee109c0..5b443a7f70 100644 --- a/panda/src/mathutil/triangulator.h +++ b/panda/src/mathutil/triangulator.h @@ -31,9 +31,8 @@ // // http://www.cs.unc.edu/~dm/CODE/GEM/chapter.html // -// It works strictly on 2-d points. You'll have to -// convert your polygon into a plane if you have 3-d -// points. +// It works strictly on 2-d points. See Triangulator3 +// for 3-d points. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_MATHUTIL Triangulator { PUBLISHED: @@ -61,7 +60,9 @@ PUBLISHED: int get_triangle_v1(int n) const; int get_triangle_v2(int n) const; -private: +protected: + void cleanup_polygon_indices(vector_int &polygon); + typedef pvector Vertices; Vertices _vertices; diff --git a/panda/src/mathutil/triangulator3.I b/panda/src/mathutil/triangulator3.I new file mode 100755 index 0000000000..30584906ac --- /dev/null +++ b/panda/src/mathutil/triangulator3.I @@ -0,0 +1,61 @@ +// Filename: triangulator3.I +// Created by: drose (03Jan13) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: Triangulator3::add_vertex +// Access: Published +// Description: Adds a new vertex to the vertex pool. Returns the +// vertex index number. +//////////////////////////////////////////////////////////////////// +INLINE int Triangulator3:: +add_vertex(double x, double y, double z) { + return add_vertex(LPoint3d(x, y, z)); +} + +//////////////////////////////////////////////////////////////////// +// Function: Triangulator3::get_num_vertices +// Access: Published +// Description: Returns the number of vertices in the pool. Note +// that the Triangulator might append new vertices, in +// addition to those added by the user, if any of the +// polygon is self-intersecting, or if any of the holes +// intersect some part of the polygon edges. +//////////////////////////////////////////////////////////////////// +INLINE int Triangulator3:: +get_num_vertices() const { + return _vertices3.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: Triangulator3::get_vertex +// Access: Published +// Description: Returns the nth vertex. +//////////////////////////////////////////////////////////////////// +INLINE const LPoint3d &Triangulator3:: +get_vertex(int n) const { + nassertr(n >= 0 && n < (int)_vertices3.size(), LPoint3d::zero()); + return _vertices3[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: Triangulator3::get_plane +// Access: Published +// Description: Returns the plane of the polygon. This is only +// available after calling triangulate(). +//////////////////////////////////////////////////////////////////// +INLINE const LPlaned &Triangulator3:: +get_plane() const { + return _plane; +} diff --git a/panda/src/mathutil/triangulator3.cxx b/panda/src/mathutil/triangulator3.cxx new file mode 100755 index 0000000000..92215a0bfc --- /dev/null +++ b/panda/src/mathutil/triangulator3.cxx @@ -0,0 +1,112 @@ +// Filename: triangulator3.cxx +// Created by: drose (03Jan13) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "triangulator3.h" + +//////////////////////////////////////////////////////////////////// +// Function: Triangulator3::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +Triangulator3:: +Triangulator3() { +} + +//////////////////////////////////////////////////////////////////// +// Function: Triangulator3::clear +// Access: Published +// Description: Removes all vertices and polygon specifications from +// the Triangulator, and prepares it to start over. +//////////////////////////////////////////////////////////////////// +void Triangulator3:: +clear() { + _vertices3.clear(); + _plane = LPlaned(); + Triangulator::clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: Triangulator3::add_vertex +// Access: Published +// Description: Adds a new vertex to the vertex pool. Returns the +// vertex index number. +//////////////////////////////////////////////////////////////////// +int Triangulator3:: +add_vertex(const LPoint3d &point) { + int index = (int)_vertices3.size(); + _vertices3.push_back(point); + return index; +} + +//////////////////////////////////////////////////////////////////// +// Function: Triangulator3::triangulate +// Access: Published +// Description: Does the work of triangulating the specified polygon. +// After this call, you may retrieve the new triangles +// one at a time by iterating through +// get_triangle_v0/1/2(). +//////////////////////////////////////////////////////////////////// +void Triangulator3:: +triangulate() { + _result.clear(); + + if (_polygon.size() < 3) { + // Degenerate case. + return; + } + + // First, determine the polygon normal. + LNormald normal = LNormald::zero(); + + // Project the polygon into each of the three major planes and + // calculate the area of each 2-d projection. This becomes the + // polygon normal. This works because the ratio between these + // different areas corresponds to the angle at which the polygon is + // tilted toward each plane. + size_t num_verts = _polygon.size(); + for (size_t i = 0; i < num_verts; i++) { + int i0 = _polygon[i]; + int i1 = _polygon[(i + 1) % num_verts];;;; + nassertv(i0 >= 0 && i0 < (int)_vertices3.size() && + i1 >= 0 && i1 < (int)_vertices3.size()); + const LPoint3d &p0 = _vertices3[i0]; + const LPoint3d &p1 = _vertices3[i1]; + normal[0] += p0[1] * p1[2] - p0[2] * p1[1]; + normal[1] += p0[2] * p1[0] - p0[0] * p1[2]; + normal[2] += p0[0] * p1[1] - p0[1] * p1[0]; + } + + if (!normal.normalize()) { + // The polygon is degenerate: it has zero area in each plane. In + // this case, the triangulation result produces no triangles + // anyway. + return; + } + + _plane = LPlaned(normal, _vertices3[0]); + + // Now determine the matrix to project each of the vertices into + // this 2-d plane. + LMatrix4d mat; + heads_up(mat, _vertices3[1] - _vertices3[2], normal, CS_zup_right); + mat.set_row(3, _vertices3[0]); + mat.invert_in_place(); + + _vertices.clear(); + for (size_t i = 0; i < _vertices3.size(); i++) { + LPoint3d p = _vertices3[i] * mat; + _vertices.push_back(LPoint2d(p[0], p[1])); + } + Triangulator::triangulate(); +} diff --git a/panda/src/mathutil/triangulator3.h b/panda/src/mathutil/triangulator3.h new file mode 100755 index 0000000000..cb7e060458 --- /dev/null +++ b/panda/src/mathutil/triangulator3.h @@ -0,0 +1,55 @@ +// Filename: triangulator3.h +// Created by: drose (03Jan13) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef TRIANGULATOR3_H +#define TRIANGULATOR3_H + +#include "pandabase.h" +#include "triangulator.h" +#include "plane.h" + + +//////////////////////////////////////////////////////////////////// +// Class : Triangulator3 +// Description : This is an extension of Triangulator to handle +// polygons with three-dimensional points. It assumes +// all of the points lie in a single plane, and +// internally projects the supplied points into 2-D for +// passing to the underlying Triangulator object. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_MATHUTIL Triangulator3 : public Triangulator { +PUBLISHED: + Triangulator3(); + + void clear(); + int add_vertex(const LPoint3d &point); + INLINE int add_vertex(double x, double y, double z); + + INLINE int get_num_vertices() const; + INLINE const LPoint3d &get_vertex(int n) const; + MAKE_SEQ(get_vertices, get_num_vertices, get_vertex); + + void triangulate(); + INLINE const LPlaned &get_plane() const; + +private: + typedef pvector Vertices3; + Vertices3 _vertices3; + + LPlaned _plane; +}; + +#include "triangulator3.I" + +#endif diff --git a/panda/src/putil/string_utils.cxx b/panda/src/putil/string_utils.cxx index d8ed734477..80a022f1e6 100644 --- a/panda/src/putil/string_utils.cxx +++ b/panda/src/putil/string_utils.cxx @@ -402,13 +402,21 @@ string_to_double(const string &str, double &result) { //////////////////////////////////////////////////////////////////// // Function: string_to_float -// Description: Another flavor of string_to_float(), this one -// returns true if the string is a perfectly valid -// number (and sets result to that value), or false -// otherwise. +// Description: //////////////////////////////////////////////////////////////////// bool -string_to_float(const string &str, PN_stdfloat &result) { +string_to_float(const string &str, float &result) { + string tail; + result = (float)string_to_double(str, tail); + return tail.empty(); +} + +//////////////////////////////////////////////////////////////////// +// Function: string_to_stdfloat +// Description: +//////////////////////////////////////////////////////////////////// +bool +string_to_stdfloat(const string &str, PN_stdfloat &result) { string tail; result = (PN_stdfloat)string_to_double(str, tail); return tail.empty(); diff --git a/panda/src/putil/string_utils.h b/panda/src/putil/string_utils.h index d3452f54f6..3e8c04d2f5 100644 --- a/panda/src/putil/string_utils.h +++ b/panda/src/putil/string_utils.h @@ -58,7 +58,8 @@ EXPCL_PANDA_PUTIL int string_to_int(const string &str, string &tail); EXPCL_PANDA_PUTIL bool string_to_int(const string &str, int &result); EXPCL_PANDA_PUTIL double string_to_double(const string &str, string &tail); EXPCL_PANDA_PUTIL bool string_to_double(const string &str, double &result); -EXPCL_PANDA_PUTIL bool string_to_float(const string &str, PN_stdfloat &result); +EXPCL_PANDA_PUTIL bool string_to_float(const string &str, float &result); +EXPCL_PANDA_PUTIL bool string_to_stdfloat(const string &str, PN_stdfloat &result); // Convenience function to make a string from anything that has an // ostream operator. diff --git a/pandatool/src/converter/somethingToEggConverter.cxx b/pandatool/src/converter/somethingToEggConverter.cxx index 8f7ec0be71..cd4bbf1afd 100644 --- a/pandatool/src/converter/somethingToEggConverter.cxx +++ b/pandatool/src/converter/somethingToEggConverter.cxx @@ -101,6 +101,22 @@ supports_compressed() const { return false; } +//////////////////////////////////////////////////////////////////// +// Function: SomethingToEggConverter::supports_convert_to_node +// Access: Published, Virtual +// Description: Returns true if this converter can directly convert +// the model type to internal Panda memory structures, +// given the indicated options, or false otherwise. If +// this returns true, then convert_to_node() may be +// called to perform the conversion, which may be faster +// than calling convert_file() if the ultimate goal is a +// PandaNode anyway. +//////////////////////////////////////////////////////////////////// +bool SomethingToEggConverter:: +supports_convert_to_node(const LoaderOptions &options) const { + return false; +} + //////////////////////////////////////////////////////////////////// // Function: SomethingToEggConverter::get_input_units // Access: Public, Virtual @@ -115,6 +131,20 @@ get_input_units() { return DU_invalid; } +//////////////////////////////////////////////////////////////////// +// Function: SomethingToEggConverter::convert_to_node +// Access: Public, Virtual +// Description: Reads the input file and directly produces a +// ready-to-render model file as a PandaNode. Returns +// NULL on failure, or if it is not supported. (This +// functionality is not supported by all converter +// types; see supports_convert_to_node()). +//////////////////////////////////////////////////////////////////// +PT(PandaNode) SomethingToEggConverter:: +convert_to_node(const LoaderOptions &options, const Filename &filename) { + return NULL; +} + //////////////////////////////////////////////////////////////////// // Function: SomethingToEggConverter::handle_external_reference // Access: Public diff --git a/pandatool/src/converter/somethingToEggConverter.h b/pandatool/src/converter/somethingToEggConverter.h index 9a90a5de71..636d7aa7f8 100644 --- a/pandatool/src/converter/somethingToEggConverter.h +++ b/pandatool/src/converter/somethingToEggConverter.h @@ -23,9 +23,11 @@ #include "pathReplace.h" #include "pointerTo.h" #include "distanceUnit.h" +#include "pandaNode.h" class EggData; class EggGroupNode; +class LoaderOptions; //////////////////////////////////////////////////////////////////// // Class : SomethingToEggConverter @@ -103,8 +105,10 @@ public: virtual string get_extension() const=0; virtual string get_additional_extensions() const; virtual bool supports_compressed() const; + virtual bool supports_convert_to_node(const LoaderOptions &options) const; virtual bool convert_file(const Filename &filename)=0; + virtual PT(PandaNode) convert_to_node(const LoaderOptions &options, const Filename &filename); virtual DistanceUnit get_input_units(); bool handle_external_reference(EggGroupNode *egg_parent, diff --git a/pandatool/src/objegg/Sources.pp b/pandatool/src/objegg/Sources.pp index 276712d8c1..7e372a8666 100755 --- a/pandatool/src/objegg/Sources.pp +++ b/pandatool/src/objegg/Sources.pp @@ -15,11 +15,11 @@ #define SOURCES \ config_objegg.cxx config_objegg.h \ - objToEggConverter.cxx objToEggConverter.h \ + objToEggConverter.cxx objToEggConverter.h objToEggConverter.I \ eggToObjConverter.cxx eggToObjConverter.h #define INSTALL_HEADERS \ - objToEggConverter.h \ + objToEggConverter.h objToEggConverter.I \ eggToObjConverter.h #end ss_lib_target diff --git a/pandatool/src/objegg/objToEggConverter.I b/pandatool/src/objegg/objToEggConverter.I new file mode 100755 index 0000000000..104c8898f0 --- /dev/null +++ b/pandatool/src/objegg/objToEggConverter.I @@ -0,0 +1,63 @@ +// Filename: objToEggConverter.I +// Created by: drose (03Jan13) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: ObjToEggConverter::VertexEntry::operator < +// Access: Public +// Description: Provides a unique but arbitrary ordering for +// VertexEntry objects in a map. +//////////////////////////////////////////////////////////////////// +INLINE bool ObjToEggConverter::VertexEntry:: +operator < (const VertexEntry &other) const { + if (_vi != other._vi) { + return _vi < other._vi; + } + if (_vti != other._vti) { + return _vti < other._vti; + } + + // It's important that these two tests are made last, so we can find + // the first vertex that has any normal but also matches the above + // properties. + if (_vni != other._vni) { + return _vni < other._vni; + } + if (_synth_vni != other._synth_vni) { + return _synth_vni < other._synth_vni; + } + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: ObjToEggConverter::VertexEntry::operator == +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool ObjToEggConverter::VertexEntry:: +operator == (const VertexEntry &other) const { + return (_vi == other._vi && _vti == other._vti && + _vni == other._vni && _synth_vni == other._synth_vni); +} + +//////////////////////////////////////////////////////////////////// +// Function: ObjToEggConverter::VertexEntry::matches_except_normal +// Access: Public +// Description: Returns true if all the properties except _vni and _synth_vni +// are equivalent. +//////////////////////////////////////////////////////////////////// +INLINE bool ObjToEggConverter::VertexEntry:: +matches_except_normal(const VertexEntry &other) const { + return (_vi == other._vi && _vti == other._vti); +} diff --git a/pandatool/src/objegg/objToEggConverter.cxx b/pandatool/src/objegg/objToEggConverter.cxx index bb73ae594d..4794a5edf3 100755 --- a/pandatool/src/objegg/objToEggConverter.cxx +++ b/pandatool/src/objegg/objToEggConverter.cxx @@ -19,7 +19,14 @@ #include "streamReader.h" #include "virtualFileSystem.h" #include "eggPolygon.h" +#include "nodePath.h" +#include "geomTriangles.h" +#include "geomPoints.h" +#include "colorAttrib.h" +#include "shadeModelAttrib.h" #include "dcast.h" +#include "triangulator3.h" +#include "config_egg2pg.h" //////////////////////////////////////////////////////////////////// // Function: ObjToEggConverter::Constructor @@ -95,6 +102,22 @@ supports_compressed() const { return true; } +//////////////////////////////////////////////////////////////////// +// Function: ObjToEggConverter::supports_convert_to_node +// Access: Published, Virtual +// Description: Returns true if this converter can directly convert +// the model type to internal Panda memory structures, +// given the indicated options, or false otherwise. If +// this returns true, then convert_to_node() may be +// called to perform the conversion, which may be faster +// than calling convert_file() if the ultimate goal is a +// PandaNode anyway. +//////////////////////////////////////////////////////////////////// +bool ObjToEggConverter:: +supports_convert_to_node(const LoaderOptions &options) const { + return true; +} + //////////////////////////////////////////////////////////////////// // Function: ObjToEggConverter::convert_file // Access: Public, Virtual @@ -116,10 +139,40 @@ convert_file(const Filename &filename) { return !had_error(); } +//////////////////////////////////////////////////////////////////// +// Function: ObjToEggConverter::convert_to_node +// Access: Public, Virtual +// Description: Reads the input file and directly produces a +// ready-to-render model file as a PandaNode. Returns +// NULL on failure, or if it is not supported. (This +// functionality is not supported by all converter +// types; see supports_convert_to_node()). +//////////////////////////////////////////////////////////////////// +PT(PandaNode) ObjToEggConverter:: +convert_to_node(const LoaderOptions &options, const Filename &filename) { + clear_error(); + + _root_node = new PandaNode(""); + _current_vertex_data = new VertexData(_root_node, "root"); + + if (!process_node(filename)) { + _error = true; + } + + _current_vertex_data->close_geom(this); + delete _current_vertex_data; + + if (had_error()) { + return NULL; + } + + return _root_node; +} + //////////////////////////////////////////////////////////////////// // Function: ObjToEggConverter::process // Access: Protected -// Description: +// Description: Reads the file and converts it to egg structures. //////////////////////////////////////////////////////////////////// bool ObjToEggConverter:: process(const Filename &filename) { @@ -131,11 +184,14 @@ process(const Filename &filename) { return false; } - _vi = 1; - _vti = 1; - _xvti = 1; - _vni = 1; + _v_table.clear(); + _vn_table.clear(); + _rgb_table.clear(); + _vt_table.clear(); _ref_plane_res.set(1.0, 1.0); + _v4_given = false; + _vt3_given = false; + _f_given = false; _vpool = new EggVertexPool("vpool"); _egg_data->add_child(_vpool); @@ -192,6 +248,8 @@ process_line(const string &line) { return process_vt(words); } else if (tag == "xvt") { return process_xvt(words); + } else if (tag == "xvc") { + return process_xvc(words); } else if (tag == "vn") { return process_vn(words); } else if (tag == "f") { @@ -231,9 +289,9 @@ process_ref_plane_res(const string &line) { } bool okflag = true; - LPoint3d pos; - okflag &= string_to_double(words[1], _ref_plane_res[0]); - okflag &= string_to_double(words[2], _ref_plane_res[1]); + LPoint3 pos; + okflag &= string_to_stdfloat(words[1], _ref_plane_res[0]); + okflag &= string_to_stdfloat(words[2], _ref_plane_res[1]); if (!okflag) { objegg_cat.error() @@ -251,17 +309,24 @@ process_ref_plane_res(const string &line) { //////////////////////////////////////////////////////////////////// bool ObjToEggConverter:: process_v(vector_string &words) { - if (words.size() != 4 && words.size() != 7) { + if (words.size() != 4 && words.size() != 5 && + words.size() != 7 && words.size() != 8) { objegg_cat.error() << "Wrong number of tokens at line " << _line_number << "\n"; return false; } bool okflag = true; - LPoint3d pos; - okflag &= string_to_double(words[1], pos[0]); - okflag &= string_to_double(words[2], pos[1]); - okflag &= string_to_double(words[3], pos[2]); + LPoint4 pos; + okflag &= string_to_stdfloat(words[1], pos[0]); + okflag &= string_to_stdfloat(words[2], pos[1]); + okflag &= string_to_stdfloat(words[3], pos[2]); + if (words.size() == 5 || words.size() == 8) { + okflag &= string_to_stdfloat(words[4], pos[3]); + _v4_given = true; + } else { + pos[3] = 1.0; + } if (!okflag) { objegg_cat.error() @@ -269,27 +334,28 @@ process_v(vector_string &words) { return false; } - EggVertex *vertex = get_vertex(_vi); - vertex->set_pos(pos); + _v_table.push_back(pos); // Meshlab format might include an RGB color following the vertex // position. - if (words.size() == 7) { - double r, g, b; - okflag &= string_to_double(words[4], r); - okflag &= string_to_double(words[5], g); - okflag &= string_to_double(words[6], b); + if (words.size() == 7 && words.size() == 8) { + size_t si = words.size(); + LVecBase3 rgb; + okflag &= string_to_stdfloat(words[si - 3], rgb[0]); + okflag &= string_to_stdfloat(words[si - 2], rgb[1]); + okflag &= string_to_stdfloat(words[si - 1], rgb[2]); if (!okflag) { objegg_cat.error() << "Invalid number at line " << _line_number << "\n"; return false; } - vertex->set_color(LColor(r, g, b, 1.0)); + while (_rgb_table.size() + 1 < _v_table.size()) { + _rgb_table.push_back(LVecBase3(1.0, 1.0, 1.0)); + } + _rgb_table.push_back(rgb); } - ++_vi; - return true; } @@ -307,11 +373,14 @@ process_vt(vector_string &words) { } bool okflag = true; - LTexCoord3d uvw; - okflag &= string_to_double(words[1], uvw[0]); - okflag &= string_to_double(words[2], uvw[1]); + LTexCoord3 uvw; + okflag &= string_to_stdfloat(words[1], uvw[0]); + okflag &= string_to_stdfloat(words[2], uvw[1]); if (words.size() == 4) { - okflag &= string_to_double(words[3], uvw[2]); + okflag &= string_to_stdfloat(words[3], uvw[2]); + _vt3_given = true; + } else { + uvw[2] = 0.0; } if (!okflag) { @@ -320,13 +389,7 @@ process_vt(vector_string &words) { return false; } - EggVertex *vertex = get_vertex(_vti); - if (words.size() == 4) { - vertex->set_uvw("", uvw); - } else { - vertex->set_uv("", LTexCoordd(uvw[0], uvw[1])); - } - ++_vti; + _vt_table.push_back(uvw); return true; } @@ -348,9 +411,9 @@ process_xvt(vector_string &words) { } bool okflag = true; - LTexCoordd uv; - okflag &= string_to_double(words[1], uv[0]); - okflag &= string_to_double(words[2], uv[1]); + LTexCoord uv; + okflag &= string_to_stdfloat(words[1], uv[0]); + okflag &= string_to_stdfloat(words[2], uv[1]); if (!okflag) { objegg_cat.error() @@ -361,13 +424,22 @@ process_xvt(vector_string &words) { uv[0] /= _ref_plane_res[0]; uv[1] = 1.0 - uv[1] / _ref_plane_res[1]; - EggVertex *vertex = get_vertex(_xvti); - vertex->set_uv("", uv); - ++_xvti; + _xvt_table.push_back(uv); return true; } +//////////////////////////////////////////////////////////////////// +// Function: ObjToEggConverter::process_xvc +// Access: Protected +// Description: "xvc" is another extended column invented by DRZ. We +// quietly ignore it. +//////////////////////////////////////////////////////////////////// +bool ObjToEggConverter:: +process_xvc(vector_string &words) { + return true; +} + //////////////////////////////////////////////////////////////////// // Function: ObjToEggConverter::process_vn // Access: Protected @@ -382,10 +454,10 @@ process_vn(vector_string &words) { } bool okflag = true; - LVector3d normal; - okflag &= string_to_double(words[1], normal[0]); - okflag &= string_to_double(words[2], normal[1]); - okflag &= string_to_double(words[3], normal[2]); + LVector3 normal; + okflag &= string_to_stdfloat(words[1], normal[0]); + okflag &= string_to_stdfloat(words[2], normal[1]); + okflag &= string_to_stdfloat(words[3], normal[2]); if (!okflag) { objegg_cat.error() @@ -394,9 +466,7 @@ process_vn(vector_string &words) { } normal.normalize(); - EggVertex *vertex = get_vertex(_vni); - vertex->set_normal(normal); - ++_vni; + _vn_table.push_back(normal); return true; } @@ -408,6 +478,8 @@ process_vn(vector_string &words) { //////////////////////////////////////////////////////////////////// bool ObjToEggConverter:: process_f(vector_string &words) { + _f_given = true; + PT(EggPolygon) poly = new EggPolygon; for (size_t i = 1; i < words.size(); ++i) { EggVertex *vertex = get_face_vertex(words[i]); @@ -450,27 +522,6 @@ process_g(vector_string &words) { return true; } -//////////////////////////////////////////////////////////////////// -// Function: ObjToEggConverter::get_vertex -// Access: Protected -// Description: Returns or creates a vertex in the vpool with the -// given index. -//////////////////////////////////////////////////////////////////// -EggVertex *ObjToEggConverter:: -get_vertex(int n) { - if (n < 0) { - // A negative index means to count backward from the end. - n = _vi + n; - } - EggVertex *vertex = _vpool->get_vertex(n); - if (vertex == NULL) { - vertex = new EggVertex; - _vpool->add_vertex(vertex, n); - } - - return vertex; -} - //////////////////////////////////////////////////////////////////// // Function: ObjToEggConverter::get_face_vertex // Access: Protected @@ -479,79 +530,636 @@ get_vertex(int n) { //////////////////////////////////////////////////////////////////// EggVertex *ObjToEggConverter:: get_face_vertex(const string &reference) { - vector_string words; - tokenize(reference, words, "/", false); - nassertr(!words.empty(), NULL); + VertexEntry entry(this, reference); - vector vertices; - vertices.reserve(words.size()); - for (size_t i = 0; i < words.size(); ++i) { - if (trim(words[i]).empty()) { - if (i == 0) { - objegg_cat.error() - << "Invalid null vertex at line " << _line_number << "\n"; - return NULL; - } else { - vertices.push_back(vertices[0]); + // Synthesize a vertex. + EggVertex synth; + + if (entry._vi != 0) { + if (_v4_given) { + synth.set_pos(LCAST(double, _v_table[entry._vi - 1])); + } else { + LPoint4 pos = _v_table[entry._vi - 1]; + synth.set_pos(LPoint3d(pos[0], pos[1], pos[2])); + } + + if (entry._vi - 1 < (int)_rgb_table.size()) { + // We have a per-vertex color too. + LRGBColor rgb = _rgb_table[entry._vi - 1]; + LColorf rgba(rgb[0], rgb[1], rgb[2], 1.0); + synth.set_color(rgba); + } + } + + if (entry._vti != 0) { + // We have a texture coordinate; apply it. + if (_vt3_given) { + synth.set_uvw("", LCAST(double, _vt_table[entry._vti - 1])); + } else { + LTexCoord3 uvw = _vt_table[entry._vti - 1]; + synth.set_uv("", LTexCoordd(uvw[0], uvw[1])); + } + } else if (entry._vi - 1 < (int)_xvt_table.size()) { + // We have an xvt texture coordinate. + synth.set_uv("", LCAST(double, _xvt_table[entry._vi - 1])); + } + + if (entry._vni != 0) { + // We have a normal; apply it. + synth.set_normal(LCAST(double, _vn_table[entry._vni - 1])); + } + + return _vpool->create_unique_vertex(synth); +} + + +//////////////////////////////////////////////////////////////////// +// 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(); + _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 verts; + verts.reserve(words.size() - 1); + for (size_t i = 1; i < words.size(); ++i) { + VertexEntry entry(this, words[i]); + verts.push_back(entry); + if (entry._vni == 0) { + all_vn = false; + non_vn_index = i; + } + } + + if (verts.size() < 3) { + // Not enough vertices. + objegg_cat.error() + << "Degenerate face at " << _line_number << "\n"; + return false; + } + + if (!all_vn) { + // Synthesize a normal if we need it. + LNormal normal = LNormal::zero(); + for (size_t i = 0; i < verts.size(); ++i) { + int vi0 = verts[i]._vi; + int vi1 = verts[(i + 1) % verts.size()]._vi; + if (vi0 == 0 || vi1 == 0) { continue; } + const LVecBase4f &p0 = _v_table[vi0 - 1]; + const LVecBase4f &p1 = _v_table[vi1 - 1]; + + normal[0] += p0[1] * p1[2] - p0[2] * p1[1]; + normal[1] += p0[2] * p1[0] - p0[0] * p1[2]; + normal[2] += p0[0] * p1[1] - p0[1] * p1[0]; + } + normal.normalize(); + int synth_vni = add_synth_normal(normal); + + // Roll the polygon around to put the "non-vn" vertex at the + // end. + while (non_vn_index + 1 < verts.size()) { + VertexEntry entry = verts.back(); + verts.pop_back(); + verts.insert(verts.begin(), entry); + ++non_vn_index; + } + + verts.back()._synth_vni = synth_vni; + } + + Triangulator3 tri; + int num_tris = 1; + + if (verts.size() != 3) { + // We have to triangulate a higher-order polygon. + for (size_t i = 0; i < verts.size(); ++i) { + const LVecBase4 &p = _v_table[verts[i]._vi - 1]; + tri.add_vertex(p[0], p[1], p[2]); + tri.add_polygon_vertex(i); + } + + tri.triangulate(); + num_tris = tri.get_num_triangles(); + } + + if (_current_vertex_data->_prim->get_num_vertices() + 3 * num_tris > egg_max_indices || + _current_vertex_data->_entries.size() + verts.size() > egg_max_vertices) { + // We'll exceed our specified limit with these triangles; start a new Geom. + _current_vertex_data->close_geom(this); + } + + pvector indices; + indices.reserve(verts.size()); + for (size_t i = 0; i < verts.size(); ++i) { + int index = _current_vertex_data->add_vertex(this, verts[i]); + indices.push_back(index); + } + + if (indices.size() == 3) { + // It's already a triangle; add it directly. + _current_vertex_data->add_triangle(indices[0], indices[1], indices[2]); + } else { + // We have to triangulate a higher-order polygon. + for (size_t i = 0; i < verts.size(); ++i) { + const LVecBase4 &p = _v_table[verts[i]._vi - 1]; + tri.add_vertex(p[0], p[1], p[2]); + tri.add_polygon_vertex(i); + } + + tri.triangulate(); + int num_tris = tri.get_num_triangles(); + for (int ti = 0; ti < num_tris; ++ti) { + int i0 = tri.get_triangle_v0(ti); + int i1 = tri.get_triangle_v1(ti); + int i2 = tri.get_triangle_v2(ti); + _current_vertex_data->add_triangle(indices[i0], indices[i1], indices[i2]); + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ObjToEggConverter::process_g_node +// Access: Protected +// Description: Defines a group in the obj file. +//////////////////////////////////////////////////////////////////// +bool ObjToEggConverter:: +process_g_node(vector_string &words) { + _current_vertex_data->close_geom(this); + delete _current_vertex_data; + _current_vertex_data = NULL; + + NodePath np(_root_node); + + // We assume the group names define a hierarchy of more-specific to + // less-specific group names, so that the first group name is the + // bottommost node, and the last group name is the topmost node. + + // Thus, iterate from the back to the front. + size_t i = words.size(); + string name; + while (i > 2) { + --i; + name = words[i]; + NodePath child = np.find(name); + if (!child) { + child = np.attach_new_node(name); + } + np = child; + } + + if (i > 1) { + --i; + name = words[i]; + } + + _current_vertex_data = new VertexData(np.node(), name); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ObjToEggConverter::generate_points +// Access: Protected +// Description: If an obj file defines no faces, create a bunch of +// GeomPoints to illustrate the vertex positions at +// least. +//////////////////////////////////////////////////////////////////// +void ObjToEggConverter:: +generate_points() { + CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3(); + PT(GeomVertexData) vdata = new GeomVertexData("points", format, GeomEnums::UH_static); + vdata->set_num_rows(_v_table.size()); + GeomVertexWriter vertex_writer(vdata, InternalName::get_vertex()); + + for (size_t vi = 0; vi < _v_table.size(); ++vi) { + const LVecBase4 &p = _v_table[vi]; + vertex_writer.add_data3(p[0], p[1], p[2]); + } + + PT(GeomPrimitive) prim = new GeomPoints(GeomEnums::UH_static); + prim->add_next_vertices(_v_table.size()); + prim->close_primitive(); + + PT(Geom) geom = new Geom(vdata); + geom->add_primitive(prim); + + PT(GeomNode) geom_node = new GeomNode("points"); + geom_node->add_geom(geom); + _root_node->add_child(geom_node); +} + +//////////////////////////////////////////////////////////////////// +// Function: ObjToEggConverter::add_synth_normal +// Access: Private +// Description: Adds a new normal to the synth_vn table, or returns +// an existing normal. In either case returns the +// 1-based index number to the normal. +//////////////////////////////////////////////////////////////////// +int ObjToEggConverter:: +add_synth_normal(const LVecBase3 &normal) { + pair 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; + } } - int index; - if (!string_to_int(words[i], index)) { - objegg_cat.error() - << "Invalid integer " << words[i] << " at line " << _line_number << "\n"; - return NULL; + switch (i) { + case 0: + _vi = index; + if (_vi < 0) { + _vi = (int)converter->_v_table.size() + _vi; + } + if (_vi < 0 || _vi - 1 >= (int)converter->_v_table.size()) { + _vi = 0; + } + break; + + case 1: + _vti = index; + if (_vti < 0) { + _vti = (int)converter->_vt_table.size() + _vti; + } + if (_vti < 0 || _vti - 1 >= (int)converter->_vt_table.size()) { + _vti = 0; + } + break; + + case 2: + _vni = index; + if (_vni < 0) { + _vni = (int)converter->_vn_table.size() + _vni; + } + if (_vni < 0 || _vni - 1 >= (int)converter->_vn_table.size()) { + _vni = 0; + } + break; } - EggVertex *vertex = get_vertex(index); - if (vertex == NULL){ - objegg_cat.error() - << "Invalid vertex " << index << " at line " << _line_number << "\n"; - return NULL; - } - vertices.push_back(vertex); } - nassertr(!vertices.empty(), NULL); - nassertr(vertices.size() == words.size(), NULL); - - if (vertices.size() == 1) { - // Just a pos reference. - return vertices[0]; - - } else if (vertices.size() == 2) { - // Pos + uv. - if (vertices[0] == vertices[1]) { - return vertices[0]; - } - // Synthesize a vertex. - EggVertex synth(*vertices[0]); - if (vertices[1]->has_uv("")) { - synth.set_uv("", vertices[1]->get_uv("")); - } else if (vertices[1]->has_uvw("")) { - synth.set_uvw("", vertices[1]->get_uvw("")); - } - - return _vpool->create_unique_vertex(synth); - - } else if (vertices.size() >= 3) { - // pos + uv + normal. - if (vertices[0] == vertices[1] && vertices[0] == vertices[2]) { - return vertices[0]; - } - - // Synthesize a vertex. - EggVertex synth(*vertices[0]); - if (vertices[1]->has_uv("")) { - synth.set_uv("", vertices[1]->get_uv("")); - } else if (vertices[1]->has_uvw("")) { - synth.set_uvw("", vertices[1]->get_uvw("")); - } - if (vertices[2]->has_normal()) { - synth.set_normal(vertices[2]->get_normal()); - } - - return _vpool->create_unique_vertex(synth); - } - - return NULL; +} + +//////////////////////////////////////////////////////////////////// +// 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 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 vertex index numbers (as returned by +// add_vertex()). +//////////////////////////////////////////////////////////////////// +void ObjToEggConverter::VertexData:: +add_triangle(int v1, int v2, int v3) { + _prim->add_vertices(v1, v2, v3); + _prim->close_primitive(); +} + +//////////////////////////////////////////////////////////////////// +// Function: ObjToEggConverter::VertexData::close_geom +// Access: Public +// Description: Finishes the current geom and stores it as a child +// in the root. Prepares for new geoms. +//////////////////////////////////////////////////////////////////// +void ObjToEggConverter::VertexData:: +close_geom(const ObjToEggConverter *converter) { + if (_prim->get_num_vertices() != 0) { + // Create a new format that includes only the columns we actually + // used. + PT(GeomVertexArrayFormat) aformat = new GeomVertexArrayFormat; + if (_v4_given) { + aformat->add_column(InternalName::get_vertex(), 4, + GeomEnums::NT_stdfloat, GeomEnums::C_point); + } else { + aformat->add_column(InternalName::get_vertex(), 3, + GeomEnums::NT_stdfloat, GeomEnums::C_point); + } + + // We always add normals--if no normals appeared in the file, we + // synthesize them. + aformat->add_column(InternalName::get_normal(), 3, + GeomEnums::NT_stdfloat, GeomEnums::C_vector); + + if (_vt_given) { + if (_vt3_given) { + aformat->add_column(InternalName::get_texcoord(), 3, + GeomEnums::NT_stdfloat, GeomEnums::C_texcoord); + } else { + aformat->add_column(InternalName::get_texcoord(), 2, + GeomEnums::NT_stdfloat, GeomEnums::C_texcoord); + } + } + + if (_rgb_given) { + aformat->add_column(InternalName::get_color(), 4, + GeomEnums::NT_uint8, GeomEnums::C_color); + } + + CPT(GeomVertexFormat) format = GeomVertexFormat::register_format(aformat); + + // Create and populate the vertex data. + PT(GeomVertexData) vdata = new GeomVertexData(_name, format, GeomEnums::UH_static); + GeomVertexWriter vertex_writer(vdata, InternalName::get_vertex()); + GeomVertexWriter normal_writer(vdata, InternalName::get_normal()); + GeomVertexWriter texcoord_writer(vdata, InternalName::get_texcoord()); + GeomVertexWriter color_writer(vdata, InternalName::get_color()); + + for (size_t i = 0; i < _entries.size(); ++i) { + const VertexEntry &entry = _entries[i]; + + if (entry._vi != 0) { + vertex_writer.set_row(i); + vertex_writer.add_data4(converter->_v_table[entry._vi - 1]); + } + if (entry._vti != 0) { + texcoord_writer.set_row(i); + texcoord_writer.add_data3(converter->_vt_table[entry._vti - 1]); + } else if (entry._vi - 1 < (int)converter->_xvt_table.size()) { + // We have an xvt texture coordinate. + texcoord_writer.set_row(i); + texcoord_writer.add_data2(converter->_xvt_table[entry._vi - 1]); + } + if (entry._vni != 0) { + normal_writer.set_row(i); + normal_writer.add_data3(converter->_vn_table[entry._vni - 1]); + } else if (entry._synth_vni != 0) { + normal_writer.set_row(i); + normal_writer.add_data3(converter->_synth_vn_table[entry._synth_vni - 1]); + } else { + // In this case, the normal isn't used and doesn't matter; we + // fill it in a unit vector just for neatness. + normal_writer.set_row(i); + normal_writer.add_data3(0, 0, 1); + } + if (_rgb_given) { + if (entry._vi - 1 < (int)converter->_rgb_table.size()) { + color_writer.set_row(i); + color_writer.add_data3(converter->_rgb_table[entry._vi - 1]); + } + } + } + + // 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(); } diff --git a/pandatool/src/objegg/objToEggConverter.h b/pandatool/src/objegg/objToEggConverter.h index f798b7a762..74b8ca0c4c 100755 --- a/pandatool/src/objegg/objToEggConverter.h +++ b/pandatool/src/objegg/objToEggConverter.h @@ -20,6 +20,11 @@ #include "somethingToEggConverter.h" #include "eggVertexPool.h" #include "eggGroup.h" +#include "geomVertexData.h" +#include "geomVertexWriter.h" +#include "geomPrimitive.h" +#include "geomNode.h" +#include "pandaNode.h" //////////////////////////////////////////////////////////////////// // Class : ObjToEggConverter @@ -36,8 +41,10 @@ public: virtual string get_name() const; virtual string get_extension() const; virtual bool supports_compressed() const; + virtual bool supports_convert_to_node(const LoaderOptions &options) const; virtual bool convert_file(const Filename &filename); + virtual PT(PandaNode) convert_to_node(const LoaderOptions &options, const Filename &filename); protected: bool process(const Filename &filename); @@ -47,22 +54,94 @@ protected: bool process_v(vector_string &words); bool process_vt(vector_string &words); bool process_xvt(vector_string &words); + bool process_xvc(vector_string &words); bool process_vn(vector_string &words); bool process_f(vector_string &words); bool process_g(vector_string &words); - EggVertex *get_vertex(int n); EggVertex *get_face_vertex(const string &face_reference); + bool process_node(const Filename &filename); + bool process_line_node(const string &line); + + bool process_f_node(vector_string &words); + bool process_g_node(vector_string &words); + + void generate_points(); + int add_synth_normal(const LVecBase3 &normal); + + // Read from the obj file. int _line_number; - int _vi, _vti, _xvti, _vni; + typedef pvector Vec4Table; + typedef pvector Vec3Table; + typedef pvector Vec2Table; + typedef pmap UniqueVec3Table; + + Vec4Table _v_table; + Vec3Table _vn_table, _rgb_table; + Vec3Table _vt_table; + Vec2Table _xvt_table; + Vec3Table _synth_vn_table; + UniqueVec3Table _unique_synth_vn_table; + LVecBase2 _ref_plane_res; + bool _v4_given, _vt3_given; + bool _f_given; + + pset _ignored_tags; + + // Structures filled when creating an egg file. PT(EggVertexPool) _vpool; PT(EggGroup) _root_group; EggGroup *_current_group; - LVecBase2d _ref_plane_res; + // Structures filled when creating a PandaNode directly. + PT(PandaNode) _root_node; - pset _ignored_tags; + class VertexEntry { + public: + VertexEntry(); + VertexEntry(const ObjToEggConverter *converter, const string &obj_vertex); + + INLINE bool operator < (const VertexEntry &other) const; + INLINE bool operator == (const VertexEntry &other) const; + INLINE bool matches_except_normal(const VertexEntry &other) const; + + // The 1-based vertex, texcoord, and normal index numbers + // appearing in the obj file for this vertex. 0 if the index + // number is not given. + int _vi, _vti, _vni; + + // The 1-based index number to the synthesized normal, if needed. + int _synth_vni; + }; + typedef pmap UniqueVertexEntries; + typedef pvector VertexEntries; + + class VertexData { + public: + VertexData(PandaNode *parent, const string &name); + + int add_vertex(const ObjToEggConverter *converter, const VertexEntry &entry); + void add_triangle(int v1, int v2, int v3); + void close_geom(const ObjToEggConverter *converter); + + PT(PandaNode) _parent; + string _name; + PT(GeomNode) _geom_node; + + PT(GeomPrimitive) _prim; + VertexEntries _entries; + UniqueVertexEntries _unique_entries; + + bool _v4_given, _vt3_given; + bool _vt_given, _rgb_given, _vn_given; + }; + + VertexData *_current_vertex_data; + + friend class VertexData; }; +#include "objToEggConverter.I" + #endif diff --git a/pandatool/src/ptloader/config_ptloader.cxx b/pandatool/src/ptloader/config_ptloader.cxx index 5222dae0bf..bb60749dab 100644 --- a/pandatool/src/ptloader/config_ptloader.cxx +++ b/pandatool/src/ptloader/config_ptloader.cxx @@ -54,6 +54,14 @@ ConfigVariableEnum ptloader_units "when using libptloader to automatically convert files to Panda " "at load time, via e.g. \"pview myMayaFile.mb\".")); +ConfigVariableBool ptloader_load_node +("ptloader-load-node", true, + PRC_DESC("Specify true to allow libptloader to invoke the more efficient " + "but possibly-experimental code to load model files directly into " + "PandaNode when possible. Specify false to force the loading to " + "always go through the egg library, which is more likely to be " + "reliable.")); + //////////////////////////////////////////////////////////////////// // Function: init_libptloader // Description: Initializes the library. This must be called at diff --git a/pandatool/src/ptloader/config_ptloader.h b/pandatool/src/ptloader/config_ptloader.h index 018f205169..3a8172c350 100644 --- a/pandatool/src/ptloader/config_ptloader.h +++ b/pandatool/src/ptloader/config_ptloader.h @@ -20,11 +20,13 @@ #include "dconfig.h" #include "distanceUnit.h" #include "configVariableEnum.h" +#include "configVariableBool.h" ConfigureDecl(config_ptloader, EXPCL_PTLOADER, EXPTP_PTLOADER); NotifyCategoryDecl(ptloader, EXPCL_PTLOADER, EXPTP_PTLOADER); extern ConfigVariableEnum ptloader_units; +extern ConfigVariableBool ptloader_load_node; extern EXPCL_PTLOADER void init_libptloader(); diff --git a/pandatool/src/ptloader/loaderFileTypePandatool.cxx b/pandatool/src/ptloader/loaderFileTypePandatool.cxx index 7f0fdbf832..6d6bd5e227 100644 --- a/pandatool/src/ptloader/loaderFileTypePandatool.cxx +++ b/pandatool/src/ptloader/loaderFileTypePandatool.cxx @@ -160,8 +160,6 @@ load_file(const Filename &path, const LoaderOptions &options, PT(PandaNode) result; SomethingToEggConverter *loader = _loader->make_copy(); - PT(EggData) egg_data = new EggData; - loader->set_egg_data(egg_data); DSearchPath file_path; file_path.append_directory(path.get_dirname()); @@ -185,6 +183,20 @@ load_file(const Filename &path, const LoaderOptions &options, break; } + // Try to convert directly to PandaNode first, if the converter type + // supports it. + if (ptloader_load_node && loader->supports_convert_to_node(options)) { + result = loader->convert_to_node(options, path); + if (!result.is_null()) { + return result; + } + } + + // If the converter type doesn't support the direct PandaNode + // conversion, take the slower route through egg instead. + PT(EggData) egg_data = new EggData; + loader->set_egg_data(egg_data); + if (loader->convert_file(path)) { DistanceUnit input_units = loader->get_input_units(); if (input_units != DU_invalid && ptloader_units != DU_invalid &&