Panda3D
Loading...
Searching...
No Matches
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
30using std::string;
31
32/**
33 *
34 */
35ObjToEggConverter::
36ObjToEggConverter() {
37}
38
39/**
40 *
41 */
42ObjToEggConverter::
43ObjToEggConverter(const ObjToEggConverter &copy) :
45{
46}
47
48/**
49 *
50 */
51ObjToEggConverter::
52~ObjToEggConverter() {
53}
54
55/**
56 * Allocates and returns a new copy of the converter.
57 */
62
63
64/**
65 * Returns the English name of the file type this converter supports.
66 */
68get_name() const {
69 return "obj";
70}
71
72/**
73 * Returns the common extension of the file type this converter supports.
74 */
76get_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 */
85supports_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 */
97supports_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 */
106convert_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 */
125PT(PandaNode) ObjToEggConverter::
126convert_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 */
149bool ObjToEggConverter::
150process(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 */
223bool ObjToEggConverter::
224process_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 */
258bool ObjToEggConverter::
259process_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 */
290bool ObjToEggConverter::
291process_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 */
344bool ObjToEggConverter::
345process_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 */
379bool ObjToEggConverter::
380process_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 */
409bool ObjToEggConverter::
410process_xvc(vector_string &words) {
411 return true;
412}
413
414/**
415 *
416 */
417bool ObjToEggConverter::
418process_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 */
446bool ObjToEggConverter::
447process_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 */
466bool ObjToEggConverter::
467process_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 */
494EggVertex *ObjToEggConverter::
495get_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 */
542void ObjToEggConverter::
543generate_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 */
554bool ObjToEggConverter::
555process_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 */
612bool ObjToEggConverter::
613process_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 */
647bool ObjToEggConverter::
648process_f_node(vector_string &words) {
649 _f_given = true;
650
651 bool all_vn = true;
652 //int non_vn_index = -1;
653
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 */
735bool ObjToEggConverter::
736process_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 */
774void ObjToEggConverter::
775generate_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 */
802int ObjToEggConverter::
803add_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 */
821ObjToEggConverter::VertexEntry::
822VertexEntry(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 */
879ObjToEggConverter::VertexData::
880VertexData(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 */
898int ObjToEggConverter::VertexData::
899add_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 */
974void ObjToEggConverter::VertexData::
975add_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 */
999void ObjToEggConverter::VertexData::
1000close_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}
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...
EggNode * add_child(EggNode *node)
Adds the indicated child to the group and returns it.
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition eggGroup.h:34
A base class for things that may be directly added into the egg hierarchy.
Definition eggNode.h:36
A single polygon.
Definition eggPolygon.h:24
A collection of vertices.
Any one-, two-, three-, or four-component vertex, possibly with attributes such as a normal.
Definition eggVertex.h:39
void set_pos(double pos)
Sets the vertex position.
Definition eggVertex.I:42
void set_uvw(const std::string &name, const LTexCoord3d &texCoord)
Sets the indicated UV coordinate triple on the vertex.
void set_uv(const LTexCoordd &texCoord)
Replaces the unnamed UV coordinate pair on the vertex with the indicated value.
Definition eggVertex.I:193
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
A node that holds Geom objects, renderable pieces of geometry.
Definition geomNode.h:34
Defines a series of disconnected points.
Definition geomPoints.h:23
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Defines a series of disconnected triangles.
This describes the structure of a single array within a Geom data.
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.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
This class defines the physical layout of the vertex data stored within a Geom.
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
A container for geometry primitives.
Definition geom.h:54
Specifies parameters that may be passed to the loader.
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition nodePath.h:159
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:599
Convert an Obj file to egg data.
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...
virtual bool convert_file(const Filename &filename)
Handles the reading of the input file and converting it to egg.
virtual bool supports_compressed() const
Returns true if this file type can transparently load compressed files (with a .pz extension),...
virtual std::string get_name() const
Returns the English name of the file type this converter supports.
virtual std::string get_extension() const
Returns the common extension of the file type this converter supports.
virtual SomethingToEggConverter * make_copy()
Allocates and returns a new copy of the converter.
A basic node of the scene graph or data graph.
Definition pandaNode.h:65
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition renderState.h:47
This is a base class for a family of converter classes that manage a conversion from some file type t...
bool had_error() const
Returns true if an error was detected during the conversion process (unless _allow_errors is true),...
void clear_error()
Resets the error flag to the no-error state.
A class to read sequential binary data directly from an istream.
This is an extension of Triangulator to handle polygons with three- dimensional points.
int add_vertex(const LPoint3d &point)
Adds a new vertex to the vertex pool.
void triangulate()
Does the work of triangulating the specified polygon.
int get_triangle_v0(int n) const
Returns vertex 0 of the nth triangle generated by the previous call to triangulate().
void add_polygon_vertex(int index)
Adds the next consecutive vertex of the polygon.
int get_triangle_v1(int n) const
Returns vertex 1 of the nth triangle generated by the previous call to triangulate().
int get_num_triangles() const
Returns the number of triangles generated by the previous call to triangulate().
int get_triangle_v2(int n) const
Returns vertex 2 of the nth triangle generated by the previous call to triangulate().
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition typedObject.I:28
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,...
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
This is our own Panda specialization on the default STL vector.
Definition pvector.h:42
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
string trim(const string &str)
Returns a new string representing the contents of the given string with both leading and trailing whi...
double string_to_double(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
int string_to_int(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.