load obj files directly into PandaNodes for performance

This commit is contained in:
David Rose 2013-01-04 00:35:42 +00:00
parent 6f987a585d
commit b3b2308c6c
19 changed files with 1271 additions and 158 deletions

View File

@ -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);

View File

@ -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

View File

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

View File

@ -118,15 +118,28 @@ 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) {
if ((*hi).size() >= 3) {
make_segment(*hi, false);
}
}
// Shuffle the segment index.
int num_segments = (int)seg.size() - 1;
@ -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.

View File

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

View File

@ -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;
}

View File

@ -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();
}

View File

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

View File

@ -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();

View File

@ -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.

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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);
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -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 &&