Panda3D
objToEggConverter.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 objToEggConverter.cxx
10  * @author drose
11  * @date 2010-12-07
12  */
13 
14 #include "objToEggConverter.h"
15 #include "config_objegg.h"
16 #include "eggData.h"
17 #include "string_utils.h"
18 #include "streamReader.h"
19 #include "virtualFileSystem.h"
20 #include "eggPolygon.h"
21 #include "nodePath.h"
22 #include "geomTriangles.h"
23 #include "geomPoints.h"
24 #include "colorAttrib.h"
25 #include "shadeModelAttrib.h"
26 #include "dcast.h"
27 #include "triangulator3.h"
28 #include "config_egg2pg.h"
29 
30 using std::string;
31 
32 /**
33  *
34  */
35 ObjToEggConverter::
36 ObjToEggConverter() {
37 }
38 
39 /**
40  *
41  */
42 ObjToEggConverter::
43 ObjToEggConverter(const ObjToEggConverter &copy) :
45 {
46 }
47 
48 /**
49  *
50  */
51 ObjToEggConverter::
52 ~ObjToEggConverter() {
53 }
54 
55 /**
56  * Allocates and returns a new copy of the converter.
57  */
60  return new ObjToEggConverter(*this);
61 }
62 
63 
64 /**
65  * Returns the English name of the file type this converter supports.
66  */
68 get_name() const {
69  return "obj";
70 }
71 
72 /**
73  * Returns the common extension of the file type this converter supports.
74  */
76 get_extension() const {
77  return "obj";
78 }
79 
80 /**
81  * Returns true if this file type can transparently load compressed files
82  * (with a .pz extension), false otherwise.
83  */
86  return true;
87 }
88 
89 /**
90  * Returns true if this converter can directly convert the model type to
91  * internal Panda memory structures, given the indicated options, or false
92  * otherwise. If this returns true, then convert_to_node() may be called to
93  * perform the conversion, which may be faster than calling convert_file() if
94  * the ultimate goal is a PandaNode anyway.
95  */
97 supports_convert_to_node(const LoaderOptions &options) const {
98  return true;
99 }
100 
101 /**
102  * Handles the reading of the input file and converting it to egg. Returns
103  * true if successful, false otherwise.
104  */
106 convert_file(const Filename &filename) {
107  clear_error();
108 
109  if (_egg_data->get_coordinate_system() == CS_default) {
110  _egg_data->set_coordinate_system(CS_zup_right);
111  }
112 
113  if (!process(filename)) {
114  _error = true;
115  }
116  return !had_error();
117 }
118 
119 /**
120  * Reads the input file and directly produces a ready-to-render model file as
121  * a PandaNode. Returns NULL on failure, or if it is not supported. (This
122  * functionality is not supported by all converter types; see
123  * supports_convert_to_node()).
124  */
125 PT(PandaNode) ObjToEggConverter::
126 convert_to_node(const LoaderOptions &options, const Filename &filename) {
127  clear_error();
128 
129  _root_node = new PandaNode("");
130  _current_vertex_data = new VertexData(_root_node, "root");
131 
132  if (!process_node(filename)) {
133  _error = true;
134  }
135 
136  _current_vertex_data->close_geom(this);
137  delete _current_vertex_data;
138 
139  if (had_error()) {
140  return nullptr;
141  }
142 
143  return _root_node;
144 }
145 
146 /**
147  * Reads the file and converts it to egg structures.
148  */
149 bool ObjToEggConverter::
150 process(const Filename &filename) {
152  std::istream *strm = vfs->open_read_file(filename, true);
153  if (strm == nullptr) {
154  objegg_cat.error()
155  << "Couldn't read " << filename << "\n";
156  return false;
157  }
158 
159  _v_table.clear();
160  _vn_table.clear();
161  _rgb_table.clear();
162  _vt_table.clear();
163  _xvt_table.clear();
164  _ref_plane_res.set(1.0, 1.0);
165  _v4_given = false;
166  _vt3_given = false;
167  _f_given = false;
168 
169  _vpool = new EggVertexPool("vpool");
170  _egg_data->add_child(_vpool);
171  _root_group = new EggGroup("root");
172  _egg_data->add_child(_root_group);
173  _current_group = _root_group;
174 
175  StreamReader sr(strm, true);
176  string line = sr.readline();
177  _line_number = 1;
178  while (!line.empty()) {
179  line = trim(line);
180  if (line.empty()) {
181  line = sr.readline();
182  continue;
183  }
184 
185  while (line[line.length() - 1] == '\\') {
186  // If it ends on a backslash, it's a continuation character.
187  string line2 = sr.readline();
188  ++_line_number;
189  if (line2.empty()) {
190  break;
191  }
192  line = line.substr(0, line.length() - 1) + trim(line2);
193  }
194 
195  if (line.substr(0, 15) == "#_ref_plane_res") {
196  process_ref_plane_res(line);
197  line = sr.readline();
198  continue;
199  }
200 
201  if (line[0] == '#') {
202  line = sr.readline();
203  continue;
204  }
205 
206  if (!process_line(line)) {
207  return false;
208  }
209  line = sr.readline();
210  ++_line_number;
211  }
212 
213  if (!_f_given) {
214  generate_egg_points();
215  }
216 
217  return true;
218 }
219 
220 /**
221  *
222  */
223 bool ObjToEggConverter::
224 process_line(const string &line) {
225  vector_string words;
226  tokenize(line, words, " \t", true);
227  nassertr(!words.empty(), false);
228 
229  string tag = words[0];
230  if (tag == "v") {
231  return process_v(words);
232  } else if (tag == "vt") {
233  return process_vt(words);
234  } else if (tag == "xvt") {
235  return process_xvt(words);
236  } else if (tag == "xvc") {
237  return process_xvc(words);
238  } else if (tag == "vn") {
239  return process_vn(words);
240  } else if (tag == "f") {
241  return process_f(words);
242  } else if (tag == "g") {
243  return process_g(words);
244  } else {
245  bool inserted = _ignored_tags.insert(tag).second;
246  if (inserted) {
247  objegg_cat.info()
248  << "Ignoring tag " << tag << "\n";
249  }
250  }
251 
252  return true;
253 }
254 
255 /**
256  *
257  */
258 bool ObjToEggConverter::
259 process_ref_plane_res(const string &line) {
260  // the #_ref_plane_res line is a DRZ extension that defines the pixel
261  // resolution of the projector device. It's needed to properly scale the
262  // xvt lines.
263 
264  vector_string words;
265  tokenize(line, words, " \t", true);
266  nassertr(!words.empty(), false);
267 
268  if (words.size() != 3) {
269  objegg_cat.error()
270  << "Wrong number of tokens at line " << _line_number << "\n";
271  return false;
272  }
273 
274  bool okflag = true;
275  okflag &= string_to_double(words[1], _ref_plane_res[0]);
276  okflag &= string_to_double(words[2], _ref_plane_res[1]);
277 
278  if (!okflag) {
279  objegg_cat.error()
280  << "Invalid number at line " << _line_number << ":\n";
281  return false;
282  }
283 
284  return true;
285 }
286 
287 /**
288  *
289  */
290 bool ObjToEggConverter::
291 process_v(vector_string &words) {
292  if (words.size() != 4 && words.size() != 5 &&
293  words.size() != 7 && words.size() != 8) {
294  objegg_cat.error()
295  << "Wrong number of tokens at line " << _line_number << "\n";
296  return false;
297  }
298 
299  bool okflag = true;
300  LPoint4d pos;
301  okflag &= string_to_double(words[1], pos[0]);
302  okflag &= string_to_double(words[2], pos[1]);
303  okflag &= string_to_double(words[3], pos[2]);
304  if (words.size() == 5 || words.size() == 8) {
305  okflag &= string_to_double(words[4], pos[3]);
306  _v4_given = true;
307  } else {
308  pos[3] = 1.0;
309  }
310 
311  if (!okflag) {
312  objegg_cat.error()
313  << "Invalid number at line " << _line_number << "\n";
314  return false;
315  }
316 
317  _v_table.push_back(pos);
318 
319  // Meshlab format might include an RGB color following the vertex position.
320  if (words.size() == 7 && words.size() == 8) {
321  size_t si = words.size();
322  LVecBase3d rgb;
323  okflag &= string_to_double(words[si - 3], rgb[0]);
324  okflag &= string_to_double(words[si - 2], rgb[1]);
325  okflag &= string_to_double(words[si - 1], rgb[2]);
326 
327  if (!okflag) {
328  objegg_cat.error()
329  << "Invalid number at line " << _line_number << "\n";
330  return false;
331  }
332  while (_rgb_table.size() + 1 < _v_table.size()) {
333  _rgb_table.push_back(LVecBase3d(1.0, 1.0, 1.0));
334  }
335  _rgb_table.push_back(rgb);
336  }
337 
338  return true;
339 }
340 
341 /**
342  *
343  */
344 bool ObjToEggConverter::
345 process_vt(vector_string &words) {
346  if (words.size() != 3 && words.size() != 4) {
347  objegg_cat.error()
348  << "Wrong number of tokens at line " << _line_number << "\n";
349  return false;
350  }
351 
352  bool okflag = true;
353  LTexCoord3d uvw;
354  okflag &= string_to_double(words[1], uvw[0]);
355  okflag &= string_to_double(words[2], uvw[1]);
356  if (words.size() == 4) {
357  okflag &= string_to_double(words[3], uvw[2]);
358  _vt3_given = true;
359  } else {
360  uvw[2] = 0.0;
361  }
362 
363  if (!okflag) {
364  objegg_cat.error()
365  << "Invalid number at line " << _line_number << "\n";
366  return false;
367  }
368 
369  _vt_table.push_back(uvw);
370 
371  return true;
372 }
373 
374 /**
375  * "xvt" is an extended column invented by DRZ. It includes texture
376  * coordinates in pixel space of the projector device, as well as for each
377  * camera. We map it to the nominal texture coordinates here.
378  */
379 bool ObjToEggConverter::
380 process_xvt(vector_string &words) {
381  if (words.size() < 3) {
382  objegg_cat.error()
383  << "Wrong number of tokens at line " << _line_number << "\n";
384  return false;
385  }
386 
387  bool okflag = true;
388  LTexCoordd uv;
389  okflag &= string_to_double(words[1], uv[0]);
390  okflag &= string_to_double(words[2], uv[1]);
391 
392  if (!okflag) {
393  objegg_cat.error()
394  << "Invalid number at line " << _line_number << "\n";
395  return false;
396  }
397 
398  uv[0] /= _ref_plane_res[0];
399  uv[1] = 1.0 - uv[1] / _ref_plane_res[1];
400 
401  _xvt_table.push_back(uv);
402 
403  return true;
404 }
405 
406 /**
407  * "xvc" is another extended column invented by DRZ. We quietly ignore it.
408  */
409 bool ObjToEggConverter::
410 process_xvc(vector_string &words) {
411  return true;
412 }
413 
414 /**
415  *
416  */
417 bool ObjToEggConverter::
418 process_vn(vector_string &words) {
419  if (words.size() != 4) {
420  objegg_cat.error()
421  << "Wrong number of tokens at line " << _line_number << "\n";
422  return false;
423  }
424 
425  bool okflag = true;
426  LVector3d normal;
427  okflag &= string_to_double(words[1], normal[0]);
428  okflag &= string_to_double(words[2], normal[1]);
429  okflag &= string_to_double(words[3], normal[2]);
430 
431  if (!okflag) {
432  objegg_cat.error()
433  << "Invalid number at line " << _line_number << "\n";
434  return false;
435  }
436  normal.normalize();
437 
438  _vn_table.push_back(normal);
439 
440  return true;
441 }
442 
443 /**
444  * Defines a face in the obj file.
445  */
446 bool ObjToEggConverter::
447 process_f(vector_string &words) {
448  _f_given = true;
449 
450  PT(EggPolygon) poly = new EggPolygon;
451  for (size_t i = 1; i < words.size(); ++i) {
452  EggVertex *vertex = get_face_vertex(words[i]);
453  if (vertex == nullptr) {
454  return false;
455  }
456  poly->add_vertex(vertex);
457  }
458  _current_group->add_child(poly);
459 
460  return true;
461 }
462 
463 /**
464  * Defines a group in the obj file.
465  */
466 bool ObjToEggConverter::
467 process_g(vector_string &words) {
468  EggGroup *group = _root_group;
469 
470  // We assume the group names define a hierarchy of more-specific to less-
471  // specific group names, so that the first group name is the bottommost
472  // node, and the last group name is the topmost node.
473 
474  // Thus, iterate from the back to the front.
475  size_t i = words.size();
476  while (i > 1) {
477  --i;
478  EggNode *child = group->find_child(words[i]);
479  if (child == nullptr || !child->is_of_type(EggGroup::get_class_type())) {
480  child = new EggGroup(words[i]);
481  group->add_child(child);
482  }
483  group = DCAST(EggGroup, child);
484  }
485 
486  _current_group = group;
487  return true;
488 }
489 
490 /**
491  * Returns or creates a vertex in the vpool according to the indicated face
492  * reference.
493  */
494 EggVertex *ObjToEggConverter::
495 get_face_vertex(const string &reference) {
496  VertexEntry entry(this, reference);
497 
498  // Synthesize a vertex.
499  EggVertex synth;
500 
501  if (entry._vi != 0) {
502  if (_v4_given) {
503  synth.set_pos(LCAST(double, _v_table[entry._vi - 1]));
504  } else {
505  LPoint4d pos = _v_table[entry._vi - 1];
506  synth.set_pos(LPoint3d(pos[0], pos[1], pos[2]));
507  }
508 
509  if (entry._vi - 1 < (int)_rgb_table.size()) {
510  // We have a per-vertex color too.
511  LRGBColord rgb = _rgb_table[entry._vi - 1];
512  LColor rgba(rgb[0], rgb[1], rgb[2], 1.0);
513  synth.set_color(rgba);
514  }
515  }
516 
517  if (entry._vti != 0) {
518  // We have a texture coordinate; apply it.
519  if (_vt3_given) {
520  synth.set_uvw("", _vt_table[entry._vti - 1]);
521  } else {
522  LTexCoord3d uvw = _vt_table[entry._vti - 1];
523  synth.set_uv("", LTexCoordd(uvw[0], uvw[1]));
524  }
525  } else if (entry._vi - 1 < (int)_xvt_table.size()) {
526  // We have an xvt texture coordinate.
527  synth.set_uv("", _xvt_table[entry._vi - 1]);
528  }
529 
530  if (entry._vni != 0) {
531  // We have a normal; apply it.
532  synth.set_normal(_vn_table[entry._vni - 1]);
533  }
534 
535  return _vpool->create_unique_vertex(synth);
536 }
537 
538 /**
539  * If an obj file defines no faces, create a bunch of EggVertex objects to
540  * illustrate the vertex positions at least.
541  */
542 void ObjToEggConverter::
543 generate_egg_points() {
544  for (size_t vi = 0; vi < _v_table.size(); ++vi) {
545  const LVecBase4d &p = _v_table[vi];
546  _vpool->make_new_vertex(LVecBase3d(p[0], p[1], p[2]));
547  }
548 }
549 
550 
551 /**
552  * Reads the file and converts it to PandaNode structures.
553  */
554 bool ObjToEggConverter::
555 process_node(const Filename &filename) {
557  std::istream *strm = vfs->open_read_file(filename, true);
558  if (strm == nullptr) {
559  objegg_cat.error()
560  << "Couldn't read " << filename << "\n";
561  return false;
562  }
563 
564  _v_table.clear();
565  _vn_table.clear();
566  _rgb_table.clear();
567  _vt_table.clear();
568  _xvt_table.clear();
569  _ref_plane_res.set(1.0, 1.0);
570  _v4_given = false;
571  _vt3_given = false;
572  _f_given = false;
573 
574  StreamReader sr(strm, true);
575  string line = sr.readline();
576  _line_number = 1;
577  while (!line.empty()) {
578  line = trim(line);
579  if (line.empty()) {
580  line = sr.readline();
581  continue;
582  }
583 
584  if (line.substr(0, 15) == "#_ref_plane_res") {
585  process_ref_plane_res(line);
586  line = sr.readline();
587  continue;
588  }
589 
590  if (line[0] == '#') {
591  line = sr.readline();
592  continue;
593  }
594 
595  if (!process_line_node(line)) {
596  return false;
597  }
598  line = sr.readline();
599  ++_line_number;
600  }
601 
602  if (!_f_given) {
603  generate_points();
604  }
605 
606  return true;
607 }
608 
609 /**
610  *
611  */
612 bool ObjToEggConverter::
613 process_line_node(const string &line) {
614  vector_string words;
615  tokenize(line, words, " \t", true);
616  nassertr(!words.empty(), false);
617 
618  string tag = words[0];
619  if (tag == "v") {
620  return process_v(words);
621  } else if (tag == "vt") {
622  return process_vt(words);
623  } else if (tag == "xvt") {
624  return process_xvt(words);
625  } else if (tag == "xvc") {
626  return process_xvc(words);
627  } else if (tag == "vn") {
628  return process_vn(words);
629  } else if (tag == "f") {
630  return process_f_node(words);
631  } else if (tag == "g") {
632  return process_g_node(words);
633  } else {
634  bool inserted = _ignored_tags.insert(tag).second;
635  if (inserted) {
636  objegg_cat.info()
637  << "Ignoring tag " << tag << "\n";
638  }
639  }
640 
641  return true;
642 }
643 
644 /**
645  * Defines a face in the obj file.
646  */
647 bool ObjToEggConverter::
648 process_f_node(vector_string &words) {
649  _f_given = true;
650 
651  bool all_vn = true;
652  //int non_vn_index = -1;
653 
654  pvector<VertexEntry> verts;
655  verts.reserve(words.size() - 1);
656  for (size_t i = 1; i < words.size(); ++i) {
657  VertexEntry entry(this, words[i]);
658  verts.push_back(entry);
659  if (entry._vni == 0) {
660  all_vn = false;
661  //non_vn_index = i;
662  }
663  }
664 
665  if (verts.size() < 3) {
666  // Not enough vertices.
667  objegg_cat.error()
668  << "Degenerate face at " << _line_number << "\n";
669  return false;
670  }
671 
672  int synth_vni = 0;
673  if (!all_vn) {
674  // Synthesize a normal if we need it.
675  LNormald normal = LNormald::zero();
676  for (size_t i = 0; i < verts.size(); ++i) {
677  int vi0 = verts[i]._vi;
678  int vi1 = verts[(i + 1) % verts.size()]._vi;
679  if (vi0 == 0 || vi1 == 0) {
680  continue;
681  }
682  const LVecBase4d &p0 = _v_table[vi0 - 1];
683  const LVecBase4d &p1 = _v_table[vi1 - 1];
684 
685  normal[0] += p0[1] * p1[2] - p0[2] * p1[1];
686  normal[1] += p0[2] * p1[0] - p0[0] * p1[2];
687  normal[2] += p0[0] * p1[1] - p0[1] * p1[0];
688  }
689  normal.normalize();
690  synth_vni = add_synth_normal(normal);
691  }
692 
693  Triangulator3 tri;
694  int num_tris = 1;
695 
696  if (verts.size() != 3) {
697  // We have to triangulate a higher-order polygon.
698  for (size_t i = 0; i < verts.size(); ++i) {
699  const LVecBase4d &p = _v_table[verts[i]._vi - 1];
700  tri.add_vertex(p[0], p[1], p[2]);
701  tri.add_polygon_vertex(i);
702  }
703 
704  tri.triangulate();
705  num_tris = tri.get_num_triangles();
706  }
707 
708  if (_current_vertex_data->_prim->get_num_vertices() + 3 * num_tris > egg_max_indices ||
709  _current_vertex_data->_entries.size() + verts.size() > (size_t)egg_max_vertices) {
710  // We'll exceed our specified limit with these triangles; start a new
711  // Geom.
712  _current_vertex_data->close_geom(this);
713  }
714 
715  if (verts.size() == 3) {
716  // It's already a triangle; add it directly.
717  _current_vertex_data->add_triangle(this, verts[0], verts[1], verts[2], synth_vni);
718 
719  } else {
720  // Get the triangulated results.
721  for (int ti = 0; ti < num_tris; ++ti) {
722  int i0 = tri.get_triangle_v0(ti);
723  int i1 = tri.get_triangle_v1(ti);
724  int i2 = tri.get_triangle_v2(ti);
725  _current_vertex_data->add_triangle(this, verts[i0], verts[i1], verts[i2], synth_vni);
726  }
727  }
728 
729  return true;
730 }
731 
732 /**
733  * Defines a group in the obj file.
734  */
735 bool ObjToEggConverter::
736 process_g_node(vector_string &words) {
737  _current_vertex_data->close_geom(this);
738  delete _current_vertex_data;
739  _current_vertex_data = nullptr;
740 
741  NodePath np(_root_node);
742 
743  // We assume the group names define a hierarchy of more-specific to less-
744  // specific group names, so that the first group name is the bottommost
745  // node, and the last group name is the topmost node.
746 
747  // Thus, iterate from the back to the front.
748  size_t i = words.size();
749  string name;
750  while (i > 2) {
751  --i;
752  name = words[i];
753  NodePath child = np.find(name);
754  if (!child) {
755  child = np.attach_new_node(name);
756  }
757  np = child;
758  }
759 
760  if (i > 1) {
761  --i;
762  name = words[i];
763  }
764 
765  _current_vertex_data = new VertexData(np.node(), name);
766 
767  return true;
768 }
769 
770 /**
771  * If an obj file defines no faces, create a bunch of GeomPoints to illustrate
772  * the vertex positions at least.
773  */
774 void ObjToEggConverter::
775 generate_points() {
777  PT(GeomVertexData) vdata = new GeomVertexData("points", format, GeomEnums::UH_static);
778  vdata->set_num_rows(_v_table.size());
779  GeomVertexWriter vertex_writer(vdata, InternalName::get_vertex());
780 
781  for (size_t vi = 0; vi < _v_table.size(); ++vi) {
782  const LVecBase4d &p = _v_table[vi];
783  vertex_writer.add_data3d(p[0], p[1], p[2]);
784  }
785 
786  PT(GeomPrimitive) prim = new GeomPoints(GeomEnums::UH_static);
787  prim->add_next_vertices(_v_table.size());
788  prim->close_primitive();
789 
790  PT(Geom) geom = new Geom(vdata);
791  geom->add_primitive(prim);
792 
793  PT(GeomNode) geom_node = new GeomNode("points");
794  geom_node->add_geom(geom);
795  _root_node->add_child(geom_node);
796 }
797 
798 /**
799  * Adds a new normal to the synth_vn table, or returns an existing normal. In
800  * either case returns the 1-based index number to the normal.
801  */
802 int ObjToEggConverter::
803 add_synth_normal(const LVecBase3d &normal) {
804  std::pair<UniqueVec3Table::iterator, bool> result = _unique_synth_vn_table.insert(UniqueVec3Table::value_type(normal, _unique_synth_vn_table.size()));
805  UniqueVec3Table::iterator ni = result.first;
806  int index = (*ni).second;
807 
808  if (result.second) {
809  // If the normal was added to the table, it's a unique normal, and now we
810  // have to add it to the table too.
811  _synth_vn_table.push_back(normal);
812  }
813 
814  return index + 1;
815 }
816 
817 /**
818  * Creates a VertexEntry from the n/n/n string format in the obj file face
819  * reference.
820  */
821 ObjToEggConverter::VertexEntry::
822 VertexEntry(const ObjToEggConverter *converter, const string &obj_vertex) {
823  _vi = 0;
824  _vti = 0;
825  _vni = 0;
826  _synth_vni = 0;
827 
828  vector_string words;
829  tokenize(obj_vertex, words, "/", false);
830  nassertv(!words.empty());
831 
832  for (size_t i = 0; i < words.size(); ++i) {
833  int index;
834  if (trim(words[i]).empty()) {
835  index = 0;
836  } else {
837  if (!string_to_int(words[i], index)) {
838  index = 0;
839  }
840  }
841 
842  switch (i) {
843  case 0:
844  _vi = index;
845  if (_vi < 0) {
846  _vi = (int)converter->_v_table.size() + _vi;
847  }
848  if (_vi < 0 || _vi - 1 >= (int)converter->_v_table.size()) {
849  _vi = 0;
850  }
851  break;
852 
853  case 1:
854  _vti = index;
855  if (_vti < 0) {
856  _vti = (int)converter->_vt_table.size() + _vti;
857  }
858  if (_vti < 0 || _vti - 1 >= (int)converter->_vt_table.size()) {
859  _vti = 0;
860  }
861  break;
862 
863  case 2:
864  _vni = index;
865  if (_vni < 0) {
866  _vni = (int)converter->_vn_table.size() + _vni;
867  }
868  if (_vni < 0 || _vni - 1 >= (int)converter->_vn_table.size()) {
869  _vni = 0;
870  }
871  break;
872  }
873  }
874 }
875 
876 /**
877  *
878  */
879 ObjToEggConverter::VertexData::
880 VertexData(PandaNode *parent, const string &name) :
881  _parent(parent), _name(name)
882 {
883  _geom_node = nullptr;
884 
885  _v4_given = false;
886  _vt3_given = false;
887  _vt_given = false;
888  _rgb_given = false;
889  _vn_given = false;
890 
891  _prim = new GeomTriangles(GeomEnums::UH_static);
892 }
893 
894 /**
895  * Adds a new entry to the vertex data for the indicated VertexEntry, or
896  * returns an equivalent vertex already present.
897  */
898 int ObjToEggConverter::VertexData::
899 add_vertex(const ObjToEggConverter *converter, const VertexEntry &entry) {
900  std::pair<UniqueVertexEntries::iterator, bool> result;
901  UniqueVertexEntries::iterator ni;
902  int index;
903 
904  if (entry._vni != 0 || entry._synth_vni != 0) {
905  // If we are storing a vertex with a normal, see if we have already stored
906  // a vertex without a normal first.
907  VertexEntry no_normal(entry);
908  no_normal._vni = 0;
909  no_normal._synth_vni = 0;
910  ni = _unique_entries.find(no_normal);
911  if (ni != _unique_entries.end()) {
912  // We did have such a vertex! In this case, repurpose this vertex,
913  // resetting it to contain this normal.
914  index = (*ni).second;
915  _unique_entries.erase(ni);
916  result = _unique_entries.insert(UniqueVertexEntries::value_type(entry, index));
917  nassertr(result.second, index);
918  nassertr(_entries[index] == no_normal, index);
919  _entries[index]._vni = entry._vni;
920  _entries[index]._synth_vni = entry._synth_vni;
921  return index;
922  }
923  } else if (entry._vni == 0 && entry._synth_vni == 0) {
924  // If we are storing a vertex *without* any normal, see if we have already
925  // stored a vertex with a normal first.
926  ni = _unique_entries.lower_bound(entry);
927  if (ni != _unique_entries.end() && (*ni).first.matches_except_normal(entry)) {
928  // We had such a vertex, so use it.
929  index = (*ni).second;
930  return index;
931  }
932  }
933 
934  // We didn't already have a vertex we could repurpose, so try to add exactly
935  // the desired vertex.
936  result = _unique_entries.insert(UniqueVertexEntries::value_type(entry, _entries.size()));
937  ni = result.first;
938  index = (*ni).second;
939 
940  if (result.second) {
941  // If the vertex was added to the table, it's a unique vertex, and now we
942  // have to add it to the vertex data too.
943  _entries.push_back(entry);
944 
945  if (converter->_v4_given) {
946  _v4_given = true;
947  }
948  if (converter->_vt3_given) {
949  _vt3_given = true;
950  }
951  if (entry._vti != 0) {
952  _vt_given = true;
953  } else if (entry._vi - 1 < (int)converter->_xvt_table.size()) {
954  // We have an xvt texture coordinate.
955  _vt_given = true;
956  }
957  if (entry._vi - 1 < (int)converter->_rgb_table.size()) {
958  // We have a per-vertex color too.
959  _rgb_given = true;
960  }
961  if (entry._vni != 0) {
962  _vn_given = true;
963  }
964  }
965 
966  return index;
967 }
968 
969 /**
970  * Adds a triangle to the primitive, as a triple of three VertexEntry objects,
971  * which are each added to the vertex pool. If synth_vni is not 0, it is
972  * assigned to the last vertex.
973  */
974 void ObjToEggConverter::VertexData::
975 add_triangle(const ObjToEggConverter *converter, const VertexEntry &v0,
976  const VertexEntry &v1, const VertexEntry &v2,
977  int synth_vni) {
978  int v0i, v1i, v2i;
979 
980  v0i = add_vertex(converter, v0);
981  v1i = add_vertex(converter, v1);
982 
983  if (synth_vni != 0) {
984  VertexEntry v2n(v2);
985  v2n._synth_vni = synth_vni;
986  v2i = add_vertex(converter, v2n);
987  } else {
988  v2i = add_vertex(converter, v2);
989  }
990 
991  _prim->add_vertices(v0i, v1i, v2i);
992  _prim->close_primitive();
993 }
994 
995 /**
996  * Finishes the current geom and stores it as a child in the root. Prepares
997  * for new geoms.
998  */
999 void ObjToEggConverter::VertexData::
1000 close_geom(const ObjToEggConverter *converter) {
1001  if (_prim->get_num_vertices() != 0) {
1002  // Create a new format that includes only the columns we actually used.
1004  if (_v4_given) {
1005  aformat->add_column(InternalName::get_vertex(), 4,
1006  GeomEnums::NT_stdfloat, GeomEnums::C_point);
1007  } else {
1008  aformat->add_column(InternalName::get_vertex(), 3,
1009  GeomEnums::NT_stdfloat, GeomEnums::C_point);
1010  }
1011 
1012  // We always add normals--if no normals appeared in the file, we
1013  // synthesize them.
1014  aformat->add_column(InternalName::get_normal(), 3,
1015  GeomEnums::NT_stdfloat, GeomEnums::C_vector);
1016 
1017  if (_vt_given) {
1018  if (_vt3_given) {
1019  aformat->add_column(InternalName::get_texcoord(), 3,
1020  GeomEnums::NT_stdfloat, GeomEnums::C_texcoord);
1021  } else {
1022  aformat->add_column(InternalName::get_texcoord(), 2,
1023  GeomEnums::NT_stdfloat, GeomEnums::C_texcoord);
1024  }
1025  }
1026 
1027  if (_rgb_given) {
1028  aformat->add_column(InternalName::get_color(), 4,
1029  GeomEnums::NT_uint8, GeomEnums::C_color);
1030  }
1031 
1032  CPT(GeomVertexFormat) format = GeomVertexFormat::register_format(aformat);
1033 
1034  // Create and populate the vertex data.
1035  PT(GeomVertexData) vdata = new GeomVertexData(_name, format, GeomEnums::UH_static);
1036  GeomVertexWriter vertex_writer(vdata, InternalName::get_vertex());
1037  GeomVertexWriter normal_writer(vdata, InternalName::get_normal());
1038  GeomVertexWriter texcoord_writer(vdata, InternalName::get_texcoord());
1039  GeomVertexWriter color_writer(vdata, InternalName::get_color());
1040 
1041  for (size_t i = 0; i < _entries.size(); ++i) {
1042  const VertexEntry &entry = _entries[i];
1043 
1044  if (entry._vi != 0) {
1045  vertex_writer.set_row(i);
1046  vertex_writer.add_data4d(converter->_v_table[entry._vi - 1]);
1047  }
1048  if (entry._vti != 0) {
1049  texcoord_writer.set_row(i);
1050  texcoord_writer.add_data3d(converter->_vt_table[entry._vti - 1]);
1051  } else if (entry._vi - 1 < (int)converter->_xvt_table.size()) {
1052  // We have an xvt texture coordinate.
1053  texcoord_writer.set_row(i);
1054  texcoord_writer.add_data2d(converter->_xvt_table[entry._vi - 1]);
1055  }
1056  if (entry._vni != 0) {
1057  normal_writer.set_row(i);
1058  normal_writer.add_data3d(converter->_vn_table[entry._vni - 1]);
1059  } else if (entry._synth_vni != 0) {
1060  normal_writer.set_row(i);
1061  normal_writer.add_data3d(converter->_synth_vn_table[entry._synth_vni - 1]);
1062  } else {
1063  // In this case, the normal isn't used and doesn't matter; we fill it
1064  // in a unit vector just for neatness.
1065  normal_writer.set_row(i);
1066  normal_writer.add_data3d(0, 0, 1);
1067  }
1068  if (_rgb_given) {
1069  if (entry._vi - 1 < (int)converter->_rgb_table.size()) {
1070  color_writer.set_row(i);
1071  color_writer.add_data3d(converter->_rgb_table[entry._vi - 1]);
1072  }
1073  }
1074  }
1075 
1076  // Transform to zup-right.
1077  vdata->transform_vertices(LMatrix4::convert_mat(CS_zup_right, CS_default));
1078 
1079  // Now create a Geom with this data.
1080  CPT(RenderState) state = RenderState::make_empty();
1081  if (_rgb_given) {
1082  state = state->add_attrib(ColorAttrib::make_vertex());
1083  } else {
1084  state = state->add_attrib(ColorAttrib::make_flat(LColor(1, 1, 1, 1)));
1085  }
1086  if (!_vn_given) {
1087  // We have synthesized these normals; specify the flat-shading attrib.
1088  state = state->add_attrib(ShadeModelAttrib::make(ShadeModelAttrib::M_flat));
1089  _prim->set_shade_model(GeomEnums::SM_flat_last_vertex);
1090  }
1091 
1092  PT(Geom) geom = new Geom(vdata);
1093  geom->add_primitive(_prim);
1094 
1095  if (_geom_node == nullptr) {
1096  _geom_node = new GeomNode(_name);
1097  _parent->add_child(_geom_node);
1098  }
1099 
1100  _geom_node->add_geom(geom, state);
1101  }
1102 
1103  _prim = new GeomTriangles(GeomEnums::UH_static);
1104  _entries.clear();
1105  _unique_entries.clear();
1106 }
int string_to_int(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A basic node of the scene graph or data graph.
Definition: pandaNode.h:64
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
double string_to_double(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Defines a series of disconnected points.
Definition: geomPoints.h:23
virtual SomethingToEggConverter * make_copy()
Allocates and returns a new copy of the converter.
bool had_error() const
Returns true if an error was detected during the conversion process (unless _allow_errors is true),...
void set_pos(double pos)
Sets the vertex position.
Definition: eggVertex.I:42
Specifies parameters that may be passed to the loader.
Definition: loaderOptions.h:23
A hierarchy of directories and files that appears to be one continuous file system,...
std::istream * open_read_file(const Filename &filename, bool auto_unwrap) const
Convenience function; returns a newly allocated istream if the file exists and can be read,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_triangle_v0(int n) const
Returns vertex 0 of the nth triangle generated by the previous call to triangulate().
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Definition: geomPrimitive.h:56
int add_column(CPT_InternalName name, int num_components, NumericType numeric_type, Contents contents, int start=-1, int column_alignment=0)
Adds a new column to the specification.
virtual std::string get_extension() const
Returns the common extension of the file type this converter supports.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_triangle_v2(int n) const
Returns vertex 2 of the nth triangle generated by the previous call to triangulate().
virtual std::string get_name() const
Returns the English name of the file type this converter supports.
virtual bool convert_file(const Filename &filename)
Handles the reading of the input file and converting it to egg.
void triangulate()
Does the work of triangulating the specified polygon.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual bool supports_convert_to_node(const LoaderOptions &options) const
Returns true if this converter can directly convert the model type to internal Panda memory structure...
PT(PandaNode) ObjToEggConverter
Reads the input file and directly produces a ready-to-render model file as a PandaNode.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition: eggGroup.h:34
int add_vertex(const LPoint3d &point)
Adds a new vertex to the vertex pool.
void clear_error()
Resets the error flag to the no-error state.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
NodePath attach_new_node(PandaNode *node, int sort=0, Thread *current_thread=Thread::get_current_thread()) const
Attaches a new node, with or without existing parents, to the scene graph below the referenced node o...
Definition: nodePath.cxx:563
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.
void set_uvw(const std::string &name, const LTexCoord3d &texCoord)
Sets the indicated UV coordinate triple on the vertex.
Definition: eggVertex.cxx:206
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Convert an Obj file to egg data.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
virtual bool supports_compressed() const
Returns true if this file type can transparently load compressed files (with a .pz extension),...
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
A container for geometry primitives.
Definition: geom.h:54
string trim(const string &str)
Returns a new string representing the contents of the given string with both leading and trailing whi...
A single polygon.
Definition: eggPolygon.h:24
int get_num_triangles() const
Returns the number of triangles generated by the previous call to triangulate().
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
void add_polygon_vertex(int index)
Adds the next consecutive vertex of the polygon.
This is an extension of Triangulator to handle polygons with three- dimensional points.
Definition: triangulator3.h:28
This class defines the physical layout of the vertex data stored within a Geom.
void tokenize(const string &str, vector_string &words, const string &delimiters, bool discard_repeated_delimiters)
Chops the source string up into pieces delimited by any of the characters specified in delimiters.
EggNode * add_child(EggNode *node)
Adds the indicated child to the group and returns it.
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
This describes the structure of a single array within a Geom data.
Defines a series of disconnected triangles.
Definition: geomTriangles.h:23
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:28
int get_triangle_v1(int n) const
Returns vertex 1 of the nth triangle generated by the previous call to triangulate().
A class to read sequential binary data directly from an istream.
Definition: streamReader.h:28
void set_uv(const LTexCoordd &texCoord)
Replaces the unnamed UV coordinate pair on the vertex with the indicated value.
Definition: eggVertex.I:193
This is a base class for a family of converter classes that manage a conversion from some file type t...
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:161
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
A collection of vertices.
Definition: eggVertexPool.h:41
EggNode * find_child(const std::string &name) const
Returns the child of this node whose name is the indicated string, or NULL if there is no child of th...
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.