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  */
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  */
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 }
A base class for any of a number of kinds of geometry primitives: polygons, point lights,...
Definition: eggPrimitive.h:47
bool had_error() const
Returns true if an error was detected during the conversion process, false otherwise.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_num_dimensions() const
Returns the number of dimensions the vertex uses.
Definition: eggVertex.I:99
virtual bool supports_compressed() const
Returns true if this file type can transparently save compressed files (with a .pz extension),...
virtual std::string get_extension() const
Returns the common extension of the file type this converter supports.
A base class for nodes in the hierarchy that are not leaf nodes.
Definition: eggGroupNode.h:46
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A hierarchy of directories and files that appears to be one continuous file system,...
virtual bool write_file(const Filename &filename)
Handles the conversion of the internal EggData to the target file format, written to the specified fi...
LTexCoordd get_uv() const
Returns the unnamed UV coordinate pair on the vertex.
Definition: eggVertex.I:179
LPoint2d get_pos2() const
Only valid if get_num_dimensions() returns 2.
Definition: eggVertex.I:120
LVertexd get_pos3() const
Valid if get_num_dimensions() returns 3 or 4.
Definition: eggVertex.I:131
virtual EggToSomethingConverter * make_copy()
Allocates and returns a new copy of the converter.
virtual std::string get_name() const
Returns the English name of the file type this converter supports.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is a base class for a family of converter classes that manage a conversion from egg format to so...
void clear_error()
Resets the error flag to the no-error state.
const LTexCoord3d & get_uvw(const std::string &name) const
Returns the named UV coordinate triple on the vertex.
Definition: eggVertex.cxx:163
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Any one-, two-, three-, or four-component vertex, possibly with attributes such as a normal.
Definition: eggVertex.h:39
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
bool has_uv() const
Returns true if the vertex has an unnamed UV coordinate pair, false otherwise.
Definition: eggVertex.I:158
string trim(const string &str)
Returns a new string representing the contents of the given string with both leading and trailing whi...
static void close_write_file(std::ostream *stream)
Closes a file opened by a previous call to 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,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A base class for things that may be directly added into the egg hierarchy.
Definition: eggNode.h:35
LPoint4d get_pos4() const
This is always valid, regardless of the value of get_num_dimensions.
Definition: eggVertex.I:145
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
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:28
bool delete_file(const Filename &filename)
Attempts to delete the indicated file or directory.
Convert an obj file to egg data.
double get_pos1() const
Only valid if get_num_dimensions() returns 1.
Definition: eggVertex.I:109
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.