Panda3D
eggToObjConverter.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file eggToObjConverter.cxx
10  * @author drose
11  * @date 2012-12-19
12  */
13 
14 #include "eggToObjConverter.h"
15 #include "config_objegg.h"
16 #include "config_egg.h"
17 #include "eggData.h"
18 #include "string_utils.h"
19 #include "streamReader.h"
20 #include "virtualFileSystem.h"
21 #include "eggPolygon.h"
22 #include "eggPoint.h"
23 #include "eggLine.h"
24 #include "dcast.h"
25 
26 using std::ostream;
27 using std::string;
28 
29 /**
30  *
31  */
32 EggToObjConverter::
33 EggToObjConverter() {
34 }
35 
36 /**
37  *
38  */
39 EggToObjConverter::
40 EggToObjConverter(const EggToObjConverter &copy) :
42 {
43 }
44 
45 /**
46  *
47  */
48 EggToObjConverter::
49 ~EggToObjConverter() {
50 }
51 
52 /**
53  * Allocates and returns a new copy of the converter.
54  */
56 make_copy() {
57  return new EggToObjConverter(*this);
58 }
59 
60 
61 /**
62  * Returns the English name of the file type this converter supports.
63  */
65 get_name() const {
66  return "obj";
67 }
68 
69 /**
70  * Returns the common extension of the file type this converter supports.
71  */
73 get_extension() const {
74  return "obj";
75 }
76 
77 /**
78  * Returns true if this file type can transparently save compressed files
79  * (with a .pz extension), false otherwise.
80  */
82 supports_compressed() const {
83  return true;
84 }
85 
86 /**
87  * Handles the conversion of the internal EggData to the target file format,
88  * written to the specified filename.
89  */
91 write_file(const Filename &filename) {
92  clear_error();
93 
94  if (_egg_data->get_coordinate_system() == CS_default) {
95  _egg_data->set_coordinate_system(CS_zup_right);
96  }
97 
98  if (!process(filename)) {
99  _error = true;
100  }
101  return !had_error();
102 }
103 
104 /**
105  *
106  */
107 bool EggToObjConverter::
108 process(const Filename &filename) {
109  _egg_data->flatten_transforms();
110  collect_vertices(_egg_data);
111 
113  Filename obj_filename = Filename::text_filename(filename);
114  vfs->delete_file(obj_filename);
115  ostream *file = vfs->open_write_file(obj_filename, true, true);
116  if (file == nullptr) {
117  return false;
118  }
119  if (egg_precision != 0) {
120  file->precision(egg_precision);
121  }
122 
123  _current_group = nullptr;
124 
125  /*
126  (*file) << "\n#\n"
127  << "# obj file generated by the following command:\n"
128  << "# " << get_exec_command() << "\n"
129  << "#\n\n";
130  */
131 
132  write_vertices(*file, "v", 3, _unique_vert3);
133  write_vertices(*file, "v", 4, _unique_vert4);
134  write_vertices(*file, "vt", 2, _unique_uv2);
135  write_vertices(*file, "vt", 3, _unique_uv3);
136  write_vertices(*file, "vn", 3, _unique_norm);
137 
138  write_faces(*file, _egg_data);
139 
140  bool success = (file != nullptr);
141  vfs->close_write_file(file);
142 
143  return success;
144 }
145 
146 /**
147  * Recursively walks the egg structure, looking for vertices referenced by
148  * polygons or points. Any such vertices are added to the vertex tables for
149  * writing to the obj file.
150  */
151 void EggToObjConverter::
152 collect_vertices(EggNode *egg_node) {
153  if (egg_node->is_of_type(EggPrimitive::get_class_type())) {
154  EggPrimitive *egg_prim = DCAST(EggPrimitive, egg_node);
155  EggPrimitive::iterator pi;
156  for (pi = egg_prim->begin(); pi != egg_prim->end(); ++pi) {
157  record_vertex(*pi);
158  }
159 
160  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
161  EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
162 
163  EggGroupNode::iterator ci;
164  for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
165  collect_vertices(*ci);
166  }
167  }
168 }
169 
170 /**
171  * Recursively walks the egg structure again, this time writing out the face
172  * records for any polygons, points, or lines encountered.
173  */
174 void EggToObjConverter::
175 write_faces(ostream &out, EggNode *egg_node) {
176  if (egg_node->is_of_type(EggPrimitive::get_class_type())) {
177  const char *prim_type = nullptr;
178  if (egg_node->is_of_type(EggPolygon::get_class_type())) {
179  prim_type = "f";
180  } else if (egg_node->is_of_type(EggPoint::get_class_type())) {
181  prim_type = "p";
182  } else if (egg_node->is_of_type(EggLine::get_class_type())) {
183  prim_type = "l";
184  }
185 
186  if (prim_type != nullptr) {
187  write_group_reference(out, egg_node);
188 
189  EggPrimitive *egg_prim = DCAST(EggPrimitive, egg_node);
190 
191  out << prim_type;
192  EggPrimitive::iterator pi;
193  for (pi = egg_prim->begin(); pi != egg_prim->end(); ++pi) {
194  VertexDef &vdef = _vmap[(*pi)];
195  int vert_index = -1;
196  int uv_index = -1;
197  int norm_index = -1;
198 
199  if (vdef._vert3_index != -1) {
200  vert_index = vdef._vert3_index + 1;
201  } else if (vdef._vert4_index != -1) {
202  vert_index = vdef._vert4_index + 1 + (int)_unique_vert3.size();
203  }
204 
205  if (vdef._uv2_index != -1) {
206  uv_index = vdef._uv2_index + 1;
207  } else if (vdef._uv3_index != -1) {
208  uv_index = vdef._uv3_index + 1 + (int)_unique_uv2.size();
209  }
210 
211  if (vdef._norm_index != -1) {
212  norm_index = vdef._norm_index + 1;
213  }
214 
215  if (vert_index == -1) {
216  continue;
217  }
218 
219  if (norm_index != -1) {
220  if (uv_index != -1) {
221  out << " " << vert_index << "/" << uv_index << "/" << norm_index;
222  } else {
223  out << " " << vert_index << "//" << norm_index;
224  }
225  } else if (uv_index != -1) {
226  out << " " << vert_index << "/" << uv_index;
227  } else {
228  out << " " << vert_index;
229  }
230  }
231  out << "\n";
232  }
233  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
234  EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
235 
236  EggGroupNode::iterator ci;
237  for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
238  write_faces(out, *ci);
239  }
240  }
241 }
242 
243 /**
244  * Writes the "g" tag to describe this polygon's group, if needed.
245  */
246 void EggToObjConverter::
247 write_group_reference(ostream &out, EggNode *egg_node) {
248  EggGroupNode *egg_group = egg_node->get_parent();
249  if (egg_group == _current_group) {
250  // Same group we wrote last time.
251  return;
252  }
253 
254  string group_name;
255  get_group_name(group_name, egg_group);
256  if (group_name.empty()) {
257  out << "g default\n";
258  } else {
259  out << "g" << group_name << "\n";
260  }
261  _current_group = egg_group;
262 }
263 
264 /**
265  * Recursively determines the appropriate string to write for the "g" tag to
266  * describe a particular EggGroupNode.
267  */
268 void EggToObjConverter::
269 get_group_name(string &group_name, EggGroupNode *egg_group) {
270  string name = trim(egg_group->get_name());
271  if (!name.empty()) {
272  group_name += ' ';
273 
274  // Remove nonstandard characters.
275  for (string::const_iterator ni = name.begin(); ni != name.end(); ++ni) {
276  char c = (*ni);
277  if (!isalnum(c)) {
278  c = '_';
279  }
280  group_name += c;
281  }
282  }
283 
284  // Now recurse.
285  EggGroupNode *egg_parent = egg_group->get_parent();
286  if (egg_parent != nullptr) {
287  get_group_name(group_name, egg_parent);
288  }
289 }
290 
291 /**
292  * Adds the indicated EggVertex to the unique vertex tables, for writing later
293  * by write_vertices().
294  */
295 void EggToObjConverter::
296 record_vertex(EggVertex *vertex) {
297  VertexDef &vdef = _vmap[vertex];
298 
299  switch (vertex->get_num_dimensions()) {
300  case 1:
301  vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos1());
302  break;
303  case 2:
304  vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos2());
305  break;
306  case 3:
307  vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos3());
308  break;
309  case 4:
310  vdef._vert4_index = record_unique(_unique_vert4, vertex->get_pos4());
311  break;
312  }
313 
314  if (vertex->has_uv("")) {
315  vdef._uv2_index = record_unique(_unique_uv2, vertex->get_uv(""));
316  } else if (vertex->has_uvw("")) {
317  vdef._uv3_index = record_unique(_unique_uv3, vertex->get_uvw(""));
318  }
319 
320  if (vertex->has_normal()) {
321  vdef._norm_index = record_unique(_unique_norm, vertex->get_normal());
322  }
323 }
324 
325 /**
326  * Records the indicated vertex value, returning the shared index if this
327  * value already appears elsewhere in the table, or the new unique index if
328  * this is the first time this value appears.
329  */
330 int EggToObjConverter::
331 record_unique(UniqueVertices &unique, const LVecBase4d &vec) {
332  // We record a zero-based index. Note that we will actually write out a
333  // one-based index to the obj file, as required by the standard.
334  int index = unique.size();
335  UniqueVertices::iterator ui = unique.insert(UniqueVertices::value_type(vec, index)).first;
336  return (*ui).second;
337 }
338 
339 /**
340  * Records the indicated vertex value, returning the shared index if this
341  * value already appears elsewhere in the table, or the new unique index if
342  * this is the first time this value appears.
343  */
344 int EggToObjConverter::
345 record_unique(UniqueVertices &unique, const LVecBase3d &vec) {
346  return record_unique(unique, LVecBase4d(vec[0], vec[1], vec[2], 0.0));
347 }
348 
349 /**
350  * Records the indicated vertex value, returning the shared index if this
351  * value already appears elsewhere in the table, or the new unique index if
352  * this is the first time this value appears.
353  */
354 int EggToObjConverter::
355 record_unique(UniqueVertices &unique, const LVecBase2d &vec) {
356  return record_unique(unique, LVecBase4d(vec[0], vec[1], 0.0, 0.0));
357 }
358 
359 /**
360  * Records the indicated vertex value, returning the shared index if this
361  * value already appears elsewhere in the table, or the new unique index if
362  * this is the first time this value appears.
363  */
364 int EggToObjConverter::
365 record_unique(UniqueVertices &unique, double pos) {
366  return record_unique(unique, LVecBase4d(pos, 0.0, 0.0, 0.0));
367 }
368 
369 /**
370  * Actually writes the vertex values recorded in the indicated table to the
371  * obj output stream.
372  */
373 void EggToObjConverter::
374 write_vertices(ostream &out, const string &prefix, int num_components,
375  const UniqueVertices &unique) {
376  // First, sort the list into numeric order.
377  int num_vertices = (int)unique.size();
378  const LVecBase4d **vertices = (const LVecBase4d **)PANDA_MALLOC_ARRAY(num_vertices * sizeof(LVecBase4d *));
379  memset(vertices, 0, num_vertices * sizeof(LVecBase4d *));
380  UniqueVertices::const_iterator ui;
381  for (ui = unique.begin(); ui != unique.end(); ++ui) {
382  int index = (*ui).second;
383  const LVecBase4d &vec = (*ui).first;
384  nassertv(index >= 0 && index < num_vertices);
385  nassertv(vertices[index] == nullptr);
386  vertices[index] = &vec;
387  }
388 
389  for (int i = 0; i < num_vertices; ++i) {
390  out << prefix;
391  const LVecBase4d &vec = *(vertices[i]);
392  for (int ci = 0; ci < num_components; ++ci) {
393  out << " " << vec[ci];
394  }
395  out << "\n";
396  }
397  PANDA_FREE_ARRAY(vertices);
398 }
399 
400 /**
401  *
402  */
403 EggToObjConverter::VertexDef::
404 VertexDef() :
405  _vert3_index(-1),
406  _vert4_index(-1),
407  _uv2_index(-1),
408  _uv3_index(-1),
409  _norm_index(-1)
410 {
411 }
eggData.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
eggLine.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EggVertex::get_pos4
LPoint4d get_pos4() const
This is always valid, regardless of the value of get_num_dimensions.
Definition: eggVertex.I:145
EggToObjConverter::get_extension
virtual std::string get_extension() const
Returns the common extension of the file type this converter supports.
Definition: eggToObjConverter.cxx:73
eggToObjConverter.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
string_utils.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
eggPoint.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EggVertex::get_num_dimensions
int get_num_dimensions() const
Returns the number of dimensions the vertex uses.
Definition: eggVertex.I:99
EggToObjConverter::supports_compressed
virtual bool supports_compressed() const
Returns true if this file type can transparently save compressed files (with a .pz extension),...
Definition: eggToObjConverter.cxx:82
dcast.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EggVertex::get_uvw
const LTexCoord3d & get_uvw(const std::string &name) const
Returns the named UV coordinate triple on the vertex.
Definition: eggVertex.cxx:163
EggVertex::get_pos1
double get_pos1() const
Only valid if get_num_dimensions() returns 1.
Definition: eggVertex.I:109
EggToObjConverter::write_file
virtual bool write_file(const Filename &filename)
Handles the conversion of the internal EggData to the target file format, written to the specified fi...
Definition: eggToObjConverter.cxx:91
EggToSomethingConverter::clear_error
void clear_error()
Resets the error flag to the no-error state.
Definition: eggToSomethingConverter.I:19
EggToSomethingConverter
This is a base class for a family of converter classes that manage a conversion from egg format to so...
Definition: eggToSomethingConverter.h:34
EggGroupNode
A base class for nodes in the hierarchy that are not leaf nodes.
Definition: eggGroupNode.h:46
EggPrimitive
A base class for any of a number of kinds of geometry primitives: polygons, point lights,...
Definition: eggPrimitive.h:49
EggVertex::has_uvw
bool has_uvw(const std::string &name) const
Returns true if the vertex has the named UV coordinate triple, and the named UV coordinate triple is ...
Definition: eggVertex.cxx:129
EggToObjConverter
Convert an obj file to egg data.
Definition: eggToObjConverter.h:26
EggToObjConverter::get_name
virtual std::string get_name() const
Returns the English name of the file type this converter supports.
Definition: eggToObjConverter.cxx:65
VirtualFileSystem::close_write_file
static void close_write_file(std::ostream *stream)
Closes a file opened by a previous call to open_write_file().
Definition: virtualFileSystem.cxx:929
VirtualFileSystem::open_write_file
std::ostream * open_write_file(const Filename &filename, bool auto_wrap, bool truncate)
Convenience function; returns a newly allocated ostream if the file exists and can be written,...
Definition: virtualFileSystem.cxx:890
config_objegg.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EggToObjConverter::make_copy
virtual EggToSomethingConverter * make_copy()
Allocates and returns a new copy of the converter.
Definition: eggToObjConverter.cxx:56
EggVertex
Any one-, two-, three-, or four-component vertex, possibly with attributes such as a normal.
Definition: eggVertex.h:39
VirtualFileSystem
A hierarchy of directories and files that appears to be one continuous file system,...
Definition: virtualFileSystem.h:40
EggVertex::get_pos2
LPoint2d get_pos2() const
Only valid if get_num_dimensions() returns 2.
Definition: eggVertex.I:120
VirtualFileSystem::delete_file
bool delete_file(const Filename &filename)
Attempts to delete the indicated file or directory.
Definition: virtualFileSystem.cxx:575
EggVertex::get_uv
LTexCoordd get_uv() const
Returns the unnamed UV coordinate pair on the vertex.
Definition: eggVertex.I:179
EggVertex::has_uv
bool has_uv() const
Returns true if the vertex has an unnamed UV coordinate pair, false otherwise.
Definition: eggVertex.I:158
VirtualFileSystem::get_global_ptr
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
Definition: virtualFileSystem.cxx:741
virtualFileSystem.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
streamReader.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EggVertex::get_pos3
LVertexd get_pos3() const
Valid if get_num_dimensions() returns 3 or 4.
Definition: eggVertex.I:131
trim
string trim(const string &str)
Returns a new string representing the contents of the given string with both leading and trailing whi...
Definition: string_utils.cxx:281
config_egg.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EggNode
A base class for things that may be directly added into the egg hierarchy.
Definition: eggNode.h:35
EggToSomethingConverter::had_error
bool had_error() const
Returns true if an error was detected during the conversion process, false otherwise.
Definition: eggToSomethingConverter.I:28
Filename
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
TypedObject::is_of_type
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:28
eggPolygon.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.