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