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  */
59 make_copy() {
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  */
85 supports_compressed() const {
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 }
Geom
A container for geometry primitives.
Definition: geom.h:54
eggData.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Triangulator3
This is an extension of Triangulator to handle polygons with three- dimensional points.
Definition: triangulator3.h:28
SomethingToEggConverter::had_error
bool had_error() const
Returns true if an error was detected during the conversion process (unless _allow_errors is true),...
Definition: somethingToEggConverter.I:28
nodePath.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
shadeModelAttrib.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pvector< VertexEntry >
tokenize
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.
Definition: string_utils.cxx:170
GeomVertexData
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
Definition: geomVertexData.h:68
string_utils.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EggGroupNode::find_child
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...
Definition: eggGroupNode.cxx:296
objToEggConverter.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomVertexArrayFormat::add_column
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.
Definition: geomVertexArrayFormat.cxx:218
dcast.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
triangulator3.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomPoints
Defines a series of disconnected points.
Definition: geomPoints.h:23
colorAttrib.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomVertexWriter
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
Definition: geomVertexWriter.h:55
config_egg2pg.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
LoaderOptions
Specifies parameters that may be passed to the loader.
Definition: loaderOptions.h:23
RenderState
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
GeomNode
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
Triangulator::add_polygon_vertex
void add_polygon_vertex(int index)
Adds the next consecutive vertex of the polygon.
Definition: triangulator.cxx:63
ObjToEggConverter::get_extension
virtual std::string get_extension() const
Returns the common extension of the file type this converter supports.
Definition: objToEggConverter.cxx:76
EggVertex::set_uv
void set_uv(const LTexCoordd &texCoord)
Replaces the unnamed UV coordinate pair on the vertex with the indicated value.
Definition: eggVertex.I:193
config_objegg.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EggVertex
Any one-, two-, three-, or four-component vertex, possibly with attributes such as a normal.
Definition: eggVertex.h:39
Triangulator::get_triangle_v2
int get_triangle_v2(int n) const
Returns vertex 2 of the nth triangle generated by the previous call to triangulate().
Definition: triangulator.cxx:244
SomethingToEggConverter
This is a base class for a family of converter classes that manage a conversion from some file type t...
Definition: somethingToEggConverter.h:38
VirtualFileSystem
A hierarchy of directories and files that appears to be one continuous file system,...
Definition: virtualFileSystem.h:40
Triangulator::get_triangle_v0
int get_triangle_v0(int n) const
Returns vertex 0 of the nth triangle generated by the previous call to triangulate().
Definition: triangulator.cxx:218
EggPolygon
A single polygon.
Definition: eggPolygon.h:24
PT
PT(PandaNode) ObjToEggConverter
Reads the input file and directly produces a ready-to-render model file as a PandaNode.
Definition: objToEggConverter.cxx:125
Triangulator3::triangulate
void triangulate()
Does the work of triangulating the specified polygon.
Definition: triangulator3.cxx:51
GeomVertexFormat::get_v3
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
Definition: geomVertexFormat.I:251
ObjToEggConverter::get_name
virtual std::string get_name() const
Returns the English name of the file type this converter supports.
Definition: objToEggConverter.cxx:68
ObjToEggConverter::convert_file
virtual bool convert_file(const Filename &filename)
Handles the reading of the input file and converting it to egg.
Definition: objToEggConverter.cxx:106
geomTriangles.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Triangulator3::add_vertex
int add_vertex(const LPoint3d &point)
Adds a new vertex to the vertex pool.
Definition: triangulator3.cxx:39
string_to_int
int string_to_int(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
Definition: string_utils.cxx:324
SomethingToEggConverter::clear_error
void clear_error()
Resets the error flag to the no-error state.
Definition: somethingToEggConverter.I:19
EggVertex::set_uvw
void set_uvw(const std::string &name, const LTexCoord3d &texCoord)
Sets the indicated UV coordinate triple on the vertex.
Definition: eggVertex.cxx:206
ObjToEggConverter::supports_convert_to_node
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...
Definition: objToEggConverter.cxx:97
GeomVertexFormat
This class defines the physical layout of the vertex data stored within a Geom.
Definition: geomVertexFormat.h:55
ObjToEggConverter
Convert an Obj file to egg data.
Definition: objToEggConverter.h:33
string_to_double
double string_to_double(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
Definition: string_utils.cxx:354
GeomTriangles
Defines a series of disconnected triangles.
Definition: geomTriangles.h:23
NodePath
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:159
ObjToEggConverter::supports_compressed
virtual bool supports_compressed() const
Returns true if this file type can transparently load compressed files (with a .pz extension),...
Definition: objToEggConverter.cxx:85
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.
Triangulator::get_triangle_v1
int get_triangle_v1(int n) const
Returns vertex 1 of the nth triangle generated by the previous call to triangulate().
Definition: triangulator.cxx:231
streamReader.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
StreamReader
A class to read sequential binary data directly from an istream.
Definition: streamReader.h:28
NodePath::attach_new_node
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:596
geomPoints.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EggVertexPool
A collection of vertices.
Definition: eggVertexPool.h:41
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
Triangulator::get_num_triangles
int get_num_triangles() const
Returns the number of triangles generated by the previous call to triangulate().
Definition: triangulator.cxx:206
VirtualFileSystem::open_read_file
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,...
Definition: virtualFileSystem.cxx:846
ObjToEggConverter::make_copy
virtual SomethingToEggConverter * make_copy()
Allocates and returns a new copy of the converter.
Definition: objToEggConverter.cxx:59
PandaNode
A basic node of the scene graph or data graph.
Definition: pandaNode.h:65
EggNode
A base class for things that may be directly added into the egg hierarchy.
Definition: eggNode.h:35
EggGroup
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition: eggGroup.h:34
EggGroupNode::add_child
EggNode * add_child(EggNode *node)
Adds the indicated child to the group and returns it.
Definition: eggGroupNode.cxx:243
EggVertex::set_pos
void set_pos(double pos)
Sets the vertex position.
Definition: eggVertex.I:42
GeomVertexArrayFormat
This describes the structure of a single array within a Geom data.
Definition: geomVertexArrayFormat.h:47
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
GeomPrimitive
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Definition: geomPrimitive.h:56
eggPolygon.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.