Panda3D
Loading...
Searching...
No Matches
eggLoader.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 eggLoader.cxx
10 * @author drose
11 * @date 2002-02-26
12 */
13
14#include "pandabase.h"
15
16#include "eggLoader.h"
17#include "eggRenderState.h"
18#include "egg_parametrics.h"
19#include "config_egg2pg.h"
20#include "config_egg.h"
21#include "nodePath.h"
22#include "renderState.h"
23#include "transformState.h"
24#include "texturePool.h"
25#include "billboardEffect.h"
26#include "decalEffect.h"
27#include "colorAttrib.h"
28#include "textureAttrib.h"
29#include "materialPool.h"
30#include "geomNode.h"
31#include "geomVertexFormat.h"
33#include "geomVertexData.h"
34#include "geomVertexWriter.h"
35#include "geom.h"
36#include "geomTriangles.h"
37#include "geomTristrips.h"
38#include "geomTrifans.h"
39#include "geomLines.h"
40#include "geomLinestrips.h"
41#include "geomPoints.h"
42#include "geomPatches.h"
43#include "sequenceNode.h"
44#include "switchNode.h"
45#include "portalNode.h"
46#include "occluderNode.h"
47#include "polylightNode.h"
48#include "lodNode.h"
49#include "modelNode.h"
50#include "modelRoot.h"
51#include "string_utils.h"
52#include "eggPrimitive.h"
53#include "eggPatch.h"
54#include "eggPoint.h"
55#include "eggLine.h"
57#include "eggNurbsCurve.h"
58#include "eggNurbsSurface.h"
59#include "eggGroupNode.h"
60#include "eggGroup.h"
61#include "eggPolygon.h"
62#include "eggTriangleStrip.h"
63#include "eggTriangleFan.h"
64#include "eggBin.h"
65#include "eggTable.h"
66#include "eggBinner.h"
67#include "eggVertexPool.h"
68#include "pt_EggTexture.h"
69#include "characterMaker.h"
70#include "character.h"
71#include "animBundleMaker.h"
72#include "animBundleNode.h"
73#include "selectiveChildNode.h"
74#include "collisionNode.h"
75#include "collisionSphere.h"
76#include "collisionInvSphere.h"
77#include "collisionCapsule.h"
78#include "collisionPlane.h"
79#include "collisionPolygon.h"
80#include "collisionFloorMesh.h"
81#include "collisionBox.h"
82#include "parametricCurve.h"
83#include "nurbsCurve.h"
84#include "nurbsCurveInterface.h"
85#include "nurbsCurveEvaluator.h"
87#include "ropeNode.h"
88#include "sheetNode.h"
89#include "look_at.h"
91#include "transformBlendTable.h"
92#include "transformBlend.h"
93#include "sparseArray.h"
94#include "bitArray.h"
95#include "thread.h"
96#include "uvScrollNode.h"
97#include "textureStagePool.h"
98#include "cmath.h"
99
100#include <ctype.h>
101#include <algorithm>
102
103using std::max;
104using std::min;
105using std::string;
106
107// This class is used in make_node(EggBin *) to sort LOD instances in order by
108// switching distance.
109class LODInstance {
110public:
111 LODInstance(EggNode *egg_node);
112 bool operator < (const LODInstance &other) const {
113 return _d->_switch_in < other._d->_switch_in;
114 }
115
116 EggNode *_egg_node;
117 const EggSwitchConditionDistance *_d;
118};
119
120LODInstance::
121LODInstance(EggNode *egg_node) {
122 nassertv(egg_node != nullptr);
123 _egg_node = egg_node;
124
125 // We expect this egg node to be an EggGroup with an LOD specification.
126 // That's what the EggBinner collected together, after all.
127 EggGroup *egg_group = DCAST(EggGroup, egg_node);
128 nassertv(egg_group->has_lod());
129 const EggSwitchCondition &sw = egg_group->get_lod();
130
131 // For now, this is the only kind of switch condition there is.
132 _d = DCAST(EggSwitchConditionDistance, &sw);
133}
134
135
136/**
137 *
138 */
139EggLoader::
140EggLoader() {
141 // We need to enforce whatever coordinate system the user asked for.
142 _data = new EggData;
143 _data->set_coordinate_system(egg_coordinate_system);
144 _error = false;
145 _dynamic_override = false;
146 _dynamic_override_char_maker = nullptr;
147}
148
149/**
150 * The EggLoader constructor makes a copy of the EggData passed in.
151 */
152EggLoader::
153EggLoader(const EggData *data) :
154 _data(new EggData(*data))
155{
156 _error = false;
157 _dynamic_override = false;
158 _dynamic_override_char_maker = nullptr;
159}
160
161
162/**
163 *
164 */
165void EggLoader::
166build_graph() {
167 _deferred_nodes.clear();
168
169 // Expand all of the ObjectType flags before we do anything else; that might
170 // prune out large portions of the scene.
171 if (!expand_all_object_types(_data)) {
172 return;
173 }
174
175 // Now, load up all of the textures.
176 load_textures();
177
178 // Clean up the vertices.
179 _data->clear_connected_shading();
180 _data->remove_unused_vertices(true);
181 _data->get_connected_shading();
182 _data->unify_attributes(true, egg_flat_shading, true);
183
184 // Now we need to get the connected shading again, since in unifying the
185 // attributes we may have made vertices suddenly become identical to each
186 // other, thereby connecting more primitives than before.
187 _data->clear_connected_shading();
188 _data->remove_unused_vertices(true);
189 _data->get_connected_shading();
190
191 // Sequences and switches have special needs. Make sure that primitives
192 // parented directly to a sequence or switch are sorted into sub-groups
193 // first, to prevent them being unified into a single polyset.
194 separate_switches(_data);
195
196 if (egg_emulate_bface) {
197 emulate_bface(_data);
198 }
199
200 // Then bin up the polysets and LOD nodes.
201 _data->remove_invalid_primitives(true);
202 EggBinner binner(*this);
203 binner.make_bins(_data);
204
205 // ((EggGroupNode *)_data)->write(cerr, 0);
206
207 // Now build up the scene graph.
208 _root = new ModelRoot(_data->get_egg_filename(), _data->get_egg_timestamp());
209
210 EggGroupNode::const_iterator ci;
211 for (ci = _data->begin(); ci != _data->end(); ++ci) {
212 make_node(*ci, _root);
213 }
214
217
218 apply_deferred_nodes(_root, DeferredNodeProperty());
219}
220
221/**
222 * For each node representing a decal base geometry (i.e. a node
223 * corresponding to an EggGroup with the decal flag set), move all of its
224 * nested geometry directly below the GeomNode representing the group.
225 */
228 ExtraNodes::const_iterator di;
229 for (di = _decals.begin(); di != _decals.end(); ++di) {
230 PandaNode *node = (*di);
231 nassertv(node != nullptr);
232
233 // The NodePath interface is best for this.
234 NodePath parent(node);
235
236 // First, search for the GeomNode.
237 NodePath geom_parent;
238 int num_children = parent.get_num_children();
239 for (int i = 0; i < num_children; i++) {
240 NodePath child = parent.get_child(i);
241
242 if (child.node()->is_of_type(GeomNode::get_class_type())) {
243 if (!geom_parent.is_empty()) {
244 // Oops, too many GeomNodes.
245 egg2pg_cat.error()
246 << "Decal onto " << parent.node()->get_name()
247 << " uses base geometry with multiple GeomNodes.\n";
248 _error = true;
249 }
250 geom_parent = child;
251 }
252 }
253
254 if (geom_parent.is_empty()) {
255 // No children were GeomNodes.
256 egg2pg_cat.error()
257 << "Ignoring decal onto " << parent.node()->get_name()
258 << "; no geometry within group.\n";
259 _error = true;
260 } else {
261 // Now reparent all of the non-GeomNodes to this node. We have to be
262 // careful so we don't get lost as we self-modify this list.
263 int i = 0;
264 while (i < num_children) {
265 NodePath child = parent.get_child(i);
266
267 if (child.node()->is_of_type(GeomNode::get_class_type())) {
268 i++;
269 } else {
270 child.reparent_to(geom_parent);
271 num_children--;
272 }
273 }
274
275 // Finally, set the DecalEffect on the base geometry.
276 geom_parent.node()->set_effect(DecalEffect::make());
277 }
278 }
279}
280
281/**
282 * Starts all of the SequenceNodes we created looping. We have to wait until
283 * the entire graph is built up to do this, because the SequenceNode needs its
284 * full set of children before it can know how many frames to loop.
285 */
288 ExtraNodes::const_iterator ni;
289 for (ni = _sequences.begin(); ni != _sequences.end(); ++ni) {
290 SequenceNode *node = DCAST(SequenceNode, (*ni));
291 node->loop(true);
292 }
293}
294
295/**
296 * Creates a polyset--that is, a Geom--from the primitives that have already
297 * been grouped into a bin. If transform is non-NULL, it represents the
298 * transform to apply to the vertices (instead of the default transform based
299 * on the bin's position within the hierarchy).
300 */
302make_polyset(EggBin *egg_bin, PandaNode *parent, const LMatrix4d *transform,
303 bool is_dynamic, CharacterMaker *character_maker) {
304 if (egg_bin->empty()) {
305 // If there are no children--no primitives--never mind.
306 return;
307 }
308
309 // We know that all of the primitives in the bin have the same render state,
310 // so we can get that information from the first primitive.
311 EggGroupNode::const_iterator ci = egg_bin->begin();
312 nassertv(ci != egg_bin->end());
313 CPT(EggPrimitive) first_prim = DCAST(EggPrimitive, (*ci));
314 nassertv(first_prim != nullptr);
315 const EggRenderState *render_state;
316 DCAST_INTO_V(render_state, first_prim->get_user_data(EggRenderState::get_class_type()));
317
318 if (render_state->_hidden && egg_suppress_hidden) {
319 // Eat this polyset.
320 return;
321 }
322
323 // Generate an optimal vertex pool (or multiple vertex pools, if we have a
324 // lot of vertex) for the polygons within just the bin. Each EggVertexPool
325 // translates directly to an optimal GeomVertexData structure.
326 EggVertexPools vertex_pools;
327 egg_bin->rebuild_vertex_pools(vertex_pools, (unsigned int)egg_max_vertices,
328 false);
329
330 if (egg_mesh) {
331 // If we're using the mesher, mesh now.
332 egg_bin->mesh_triangles(render_state->_flat_shaded ? EggGroupNode::T_flat_shaded : 0);
333
334 } else {
335 // If we're not using the mesher, at least triangulate any higher-order
336 // polygons we might have.
337 egg_bin->triangulate_polygons(EggGroupNode::T_polygon | EggGroupNode::T_convex);
338 }
339
340 // Now that we've meshed, apply the per-prim attributes onto the vertices,
341 // so we can copy them to the GeomVertexData.
342 egg_bin->apply_first_attribute(false);
343 egg_bin->post_apply_flat_attribute(false);
344
345 // egg_bin->write(cerr, 0);
346
347 PT(GeomNode) geom_node;
348
349 // Now iterate through each EggVertexPool. Normally, there's only one, but
350 // if we have a really big mesh, it might have been split into multiple
351 // vertex pools (to keep each one within the egg_max_vertices constraint).
352 EggVertexPools::iterator vpi;
353 for (vpi = vertex_pools.begin(); vpi != vertex_pools.end(); ++vpi) {
354 EggVertexPool *vertex_pool = (*vpi);
355 vertex_pool->remove_unused_vertices();
356 // vertex_pool->write(cerr, 0);
357
358 bool has_overall_color;
359 LColor overall_color;
360 vertex_pool->check_overall_color(has_overall_color, overall_color);
361 if (!egg_flat_colors) {
362 // If flat colors aren't allowed, then we don't care whether there is an
363 // overall color. In that case, treat all vertex pools as if they
364 // contain a combination of multiple colors.
365 has_overall_color = false;
366 }
367
368 PT(TransformBlendTable) blend_table;
369 if (is_dynamic) {
370 // Dynamic vertex pools will require a TransformBlendTable to indicate
371 // how the vertices are to be animated.
372 blend_table = make_blend_table(vertex_pool, egg_bin, character_maker);
373
374 // Now that we've created the blend table, we can re-order the vertices
375 // in the pool to efficiently group vertices together that will share
376 // the same transform matrix. (We have to re-order them before we
377 // create primitives, below, because this will change the vertex index
378 // numbers.)
379 vertex_pool->sort_by_external_index();
380 }
381
382 // Create a handful of GeomPrimitives corresponding to the various types
383 // of primitives that reference this vertex pool.
384 UniquePrimitives unique_primitives;
385 Primitives primitives;
386 for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) {
387 EggPrimitive *egg_prim;
388 DCAST_INTO_V(egg_prim, (*ci));
389 if (egg_prim->get_pool() == vertex_pool) {
390 make_primitive(render_state, egg_prim, unique_primitives, primitives,
391 has_overall_color, overall_color);
392 }
393 }
394
395 if (!primitives.empty()) {
396 LMatrix4d mat;
397 if (transform != nullptr) {
398 mat = (*transform);
399 } else {
400 mat = egg_bin->get_vertex_to_node();
401 }
402
403 // Now convert this vertex pool to a GeomVertexData.
404 PT(GeomVertexData) vertex_data =
405 make_vertex_data(render_state, vertex_pool, egg_bin, mat, blend_table,
406 is_dynamic, character_maker, has_overall_color);
407 nassertv(vertex_data != nullptr);
408
409 // And create a Geom to hold the primitives.
410 PT(Geom) geom = new Geom(vertex_data);
411
412 // Add each new primitive to the Geom.
413 Primitives::const_iterator pi;
414 for (pi = primitives.begin(); pi != primitives.end(); ++pi) {
415 PT(GeomPrimitive) primitive = (*pi);
416
417 if (primitive->is_indexed()) {
418 // Since we may have over-allocated while we were filling up the
419 // primitives, down-allocate now.
420 primitive->reserve_num_vertices(primitive->get_num_vertices());
421 }
422
423 geom->add_primitive(primitive);
424 }
425
426 // vertex_data->write(cerr); geom->write(cerr);
427 // render_state->_state->write(cerr, 0);
428
429 // Create a new GeomNode if we haven't already.
430 if (geom_node == nullptr) {
431 // Now, is our parent node a GeomNode, or just an ordinary PandaNode?
432 // If it's a GeomNode, we can add the new Geom directly to our parent;
433 // otherwise, we need to create a new node.
434 if (parent->is_geom_node() && !render_state->_hidden) {
435 geom_node = DCAST(GeomNode, parent);
436
437 } else {
438 geom_node = new GeomNode(egg_bin->get_name());
439 if (render_state->_hidden) {
440 parent->add_stashed(geom_node);
441 } else {
442 parent->add_child(geom_node);
443 }
444 }
445 }
446
447 CPT(RenderState) geom_state = render_state->_state;
448 if (has_overall_color) {
449 if (!overall_color.almost_equal(LColor(1.0f, 1.0f, 1.0f, 1.0f))) {
450 geom_state = geom_state->add_attrib(ColorAttrib::make_flat(overall_color), -1);
451 }
452 } else {
453 geom_state = geom_state->add_attrib(ColorAttrib::make_vertex(), -1);
454 }
455
456 geom_node->add_geom(geom, geom_state);
457 }
458 }
459
460 if (geom_node != nullptr && egg_show_normals) {
461 // Create some more geometry to visualize each normal.
462 for (vpi = vertex_pools.begin(); vpi != vertex_pools.end(); ++vpi) {
463 EggVertexPool *vertex_pool = (*vpi);
464 show_normals(vertex_pool, geom_node);
465 }
466 }
467}
468
469/**
470 * Creates a TransformState object corresponding to the indicated
471 * EggTransform.
472 */
473CPT(TransformState) EggLoader::
474make_transform(const EggTransform *egg_transform) {
475 // We'll build up the transform componentwise, so we preserve any
476 // componentwise properties of the egg transform.
477
478 CPT(TransformState) ts = TransformState::make_identity();
479 int num_components = egg_transform->get_num_components();
480 for (int i = 0; i < num_components; i++) {
481 switch (egg_transform->get_component_type(i)) {
482 case EggTransform::CT_translate2d:
483 {
484 LVecBase2 trans2d(LCAST(PN_stdfloat, egg_transform->get_component_vec2(i)));
485 LVecBase3 trans3d(trans2d[0], trans2d[1], 0.0f);
486 ts = TransformState::make_pos(trans3d)->compose(ts);
487 }
488 break;
489
490 case EggTransform::CT_translate3d:
491 {
492 LVecBase3 trans3d(LCAST(PN_stdfloat, egg_transform->get_component_vec3(i)));
493 ts = TransformState::make_pos(trans3d)->compose(ts);
494 }
495 break;
496
497 case EggTransform::CT_rotate2d:
498 {
499 LRotation rot(LVector3(0.0f, 0.0f, 1.0f),
500 (PN_stdfloat)egg_transform->get_component_number(i));
501 ts = TransformState::make_quat(rot)->compose(ts);
502 }
503 break;
504
505 case EggTransform::CT_rotx:
506 {
507 LRotation rot(LVector3(1.0f, 0.0f, 0.0f),
508 (PN_stdfloat)egg_transform->get_component_number(i));
509 ts = TransformState::make_quat(rot)->compose(ts);
510 }
511 break;
512
513 case EggTransform::CT_roty:
514 {
515 LRotation rot(LVector3(0.0f, 1.0f, 0.0f),
516 (PN_stdfloat)egg_transform->get_component_number(i));
517 ts = TransformState::make_quat(rot)->compose(ts);
518 }
519 break;
520
521 case EggTransform::CT_rotz:
522 {
523 LRotation rot(LVector3(0.0f, 0.0f, 1.0f),
524 (PN_stdfloat)egg_transform->get_component_number(i));
525 ts = TransformState::make_quat(rot)->compose(ts);
526 }
527 break;
528
529 case EggTransform::CT_rotate3d:
530 {
531 LRotation rot(LCAST(PN_stdfloat, egg_transform->get_component_vec3(i)),
532 (PN_stdfloat)egg_transform->get_component_number(i));
533 ts = TransformState::make_quat(rot)->compose(ts);
534 }
535 break;
536
537 case EggTransform::CT_scale2d:
538 {
539 LVecBase2 scale2d(LCAST(PN_stdfloat, egg_transform->get_component_vec2(i)));
540 LVecBase3 scale3d(scale2d[0], scale2d[1], 1.0f);
541 ts = TransformState::make_scale(scale3d)->compose(ts);
542 }
543 break;
544
545 case EggTransform::CT_scale3d:
546 {
547 LVecBase3 scale3d(LCAST(PN_stdfloat, egg_transform->get_component_vec3(i)));
548 ts = TransformState::make_scale(scale3d)->compose(ts);
549 }
550 break;
551
552 case EggTransform::CT_uniform_scale:
553 {
554 PN_stdfloat scale = (PN_stdfloat)egg_transform->get_component_number(i);
555 ts = TransformState::make_scale(scale)->compose(ts);
556 }
557 break;
558
559 case EggTransform::CT_matrix3:
560 {
561 LMatrix3 m(LCAST(PN_stdfloat, egg_transform->get_component_mat3(i)));
562 LMatrix4 mat4(m(0, 0), m(0, 1), 0.0, m(0, 2),
563 m(1, 0), m(1, 1), 0.0, m(1, 2),
564 0.0, 0.0, 1.0, 0.0,
565 m(2, 0), m(2, 1), 0.0, m(2, 2));
566
567 ts = TransformState::make_mat(mat4)->compose(ts);
568 }
569 break;
570
571 case EggTransform::CT_matrix4:
572 {
573 LMatrix4 mat4(LCAST(PN_stdfloat, egg_transform->get_component_mat4(i)));
574 ts = TransformState::make_mat(mat4)->compose(ts);
575 }
576 break;
577
578 case EggTransform::CT_invalid:
579 nassertr(false, ts);
580 break;
581 }
582 }
583
584 if (ts->components_given()) {
585 return ts;
586 }
587
588 // Finally, we uniquify all the matrix-based TransformStates we create by
589 // complete matrix value. The TransformState class doesn't normally go this
590 // far, because of the cost of this additional uniquification step, but this
591 // is the egg loader so we don't mind spending a little bit of extra time
592 // here to get a more optimal result.
593 TransformStates::iterator tsi = _transform_states.insert(TransformStates::value_type(ts->get_mat(), ts)).first;
594
595 return (*tsi).second;
596}
597
598/**
599 * In the presence of egg-show-normals, generate some additional geometry to
600 * represent the normals, tangents, and binormals of each vertex.
601 */
602void EggLoader::
603show_normals(EggVertexPool *vertex_pool, GeomNode *geom_node) {
604 PT(GeomPrimitive) primitive = new GeomLines(Geom::UH_static);
605 CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3cp();
606 PT(GeomVertexData) vertex_data =
607 new GeomVertexData(vertex_pool->get_name(), format, Geom::UH_static);
608
609 GeomVertexWriter vertex(vertex_data, InternalName::get_vertex());
610 GeomVertexWriter color(vertex_data, InternalName::get_color());
611
612 EggVertexPool::const_iterator vi;
613 for (vi = vertex_pool->begin(); vi != vertex_pool->end(); ++vi) {
614 EggVertex *vert = (*vi);
615 LPoint3d pos = vert->get_pos3();
616
617 if (vert->has_normal()) {
618 vertex.add_data3d(pos);
619 vertex.add_data3d(pos + vert->get_normal() * egg_normal_scale);
620 color.add_data4(1.0f, 0.0f, 0.0f, 1.0f);
621 color.add_data4(1.0f, 0.0f, 0.0f, 1.0f);
622 primitive->add_next_vertices(2);
623 primitive->close_primitive();
624 }
625
626 // Also look for tangents and binormals in each texture coordinate set.
627 EggVertex::const_uv_iterator uvi;
628 for (uvi = vert->uv_begin(); uvi != vert->uv_end(); ++uvi) {
629 EggVertexUV *uv_obj = (*uvi);
630 if (uv_obj->has_tangent()) {
631 vertex.add_data3d(pos);
632 vertex.add_data3d(pos + uv_obj->get_tangent() * egg_normal_scale);
633 color.add_data4(0.0f, 1.0f, 0.0f, 1.0f);
634 color.add_data4(0.0f, 1.0f, 0.0f, 1.0f);
635 primitive->add_next_vertices(2);
636 primitive->close_primitive();
637 }
638 if (uv_obj->has_binormal()) {
639 vertex.add_data3d(pos);
640 vertex.add_data3d(pos + uv_obj->get_binormal() * egg_normal_scale);
641 color.add_data4(0.0f, 0.0f, 1.0f, 1.0f);
642 color.add_data4(0.0f, 0.0f, 1.0f, 1.0f);
643 primitive->add_next_vertices(2);
644 primitive->close_primitive();
645 }
646 }
647 }
648
649 PT(Geom) geom = new Geom(vertex_data);
650 geom->add_primitive(primitive);
651 geom_node->add_geom(geom);
652}
653
654/**
655 *
656 */
657void EggLoader::
658make_nurbs_curve(EggNurbsCurve *egg_curve, PandaNode *parent,
659 const LMatrix4d &mat) {
660 if (egg_load_old_curves) {
661 // Make a NurbsCurve instead of a RopeNode (old interface).
662 make_old_nurbs_curve(egg_curve, parent, mat);
663 return;
664 }
665
666 assert(parent != nullptr);
667 assert(!parent->is_geom_node());
668
669 PT(NurbsCurveEvaluator) nurbs = ::make_nurbs_curve(egg_curve, mat);
670 if (nurbs == nullptr) {
671 _error = true;
672 return;
673 }
674
675 /*
676 switch (egg_curve->get_curve_type()) {
677 case EggCurve::CT_xyz:
678 curve->set_curve_type(PCT_XYZ);
679 break;
680
681 case EggCurve::CT_hpr:
682 curve->set_curve_type(PCT_HPR);
683 break;
684
685 case EggCurve::CT_t:
686 curve->set_curve_type(PCT_T);
687 break;
688
689 default:
690 break;
691 }
692 */
693
694 PT(RopeNode) rope = new RopeNode(egg_curve->get_name());
695 rope->set_curve(nurbs);
696
697 // Respect the subdivision values in the egg file, if any.
698 if (egg_curve->get_subdiv() != 0) {
699 int subdiv_per_segment =
700 (int)((egg_curve->get_subdiv() + 0.5) / nurbs->get_num_segments());
701 rope->set_num_subdiv(max(subdiv_per_segment, 1));
702 }
703
704 const EggRenderState *render_state;
705 DCAST_INTO_V(render_state, egg_curve->get_user_data(EggRenderState::get_class_type()));
706 if (render_state->_hidden && egg_suppress_hidden) {
707 // Eat this primitive.
708 return;
709 }
710
711 rope->set_state(render_state->_state);
712 rope->set_uv_mode(RopeNode::UV_parametric);
713
714 if (egg_curve->has_vertex_color()) {
715 // If the curve had individual vertex color, enable it.
716 rope->set_use_vertex_color(true);
717 } else if (egg_curve->has_color()) {
718 // Otherwise, if the curve has overall color, apply it.
719 rope->set_attrib(ColorAttrib::make_flat(egg_curve->get_color()));
720 }
721
722 parent->add_child(rope);
723}
724
725/**
726 * This deprecated interface creates a NurbsCurve object for the EggNurbsCurve
727 * entry. It will eventually be removed in favor of the above, which creates
728 * a RopeNode.
729 *
730 * @deprecated See make_nurbs_curve.
731 */
732void EggLoader::
733make_old_nurbs_curve(EggNurbsCurve *egg_curve, PandaNode *parent,
734 const LMatrix4d &mat) {
735 assert(parent != nullptr);
736 assert(!parent->is_geom_node());
737
738 PT(ParametricCurve) curve;
739 curve = new NurbsCurve;
740
741 NurbsCurveInterface *nurbs = curve->get_nurbs_interface();
742 nassertv(nurbs != nullptr);
743
744 if (egg_curve->get_order() < 1 || egg_curve->get_order() > 4) {
745 egg2pg_cat.error()
746 << "Invalid NURBSCurve order for " << egg_curve->get_name() << ": "
747 << egg_curve->get_order() << "\n";
748 _error = true;
749 return;
750 }
751
752 nurbs->set_order(egg_curve->get_order());
753
754 EggPrimitive::const_iterator pi;
755 for (pi = egg_curve->begin(); pi != egg_curve->end(); ++pi) {
756 nurbs->append_cv(LCAST(PN_stdfloat, (*pi)->get_pos4() * mat));
757 }
758
759 int num_knots = egg_curve->get_num_knots();
760 if (num_knots != nurbs->get_num_knots()) {
761 egg2pg_cat.error()
762 << "Invalid NURBSCurve number of knots for "
763 << egg_curve->get_name() << ": got " << num_knots
764 << " knots, expected " << nurbs->get_num_knots() << "\n";
765 _error = true;
766 return;
767 }
768
769 for (int i = 0; i < num_knots; i++) {
770 nurbs->set_knot(i, egg_curve->get_knot(i));
771 }
772
773 switch (egg_curve->get_curve_type()) {
774 case EggCurve::CT_xyz:
775 curve->set_curve_type(PCT_XYZ);
776 break;
777
778 case EggCurve::CT_hpr:
779 curve->set_curve_type(PCT_HPR);
780 break;
781
782 case EggCurve::CT_t:
783 curve->set_curve_type(PCT_T);
784 break;
785
786 default:
787 break;
788 }
789 curve->set_name(egg_curve->get_name());
790
791 if (!curve->recompute()) {
792 egg2pg_cat.error()
793 << "Invalid NURBSCurve " << egg_curve->get_name() << "\n";
794 _error = true;
795 return;
796 }
797
798 parent->add_child(curve);
799}
800
801/**
802 *
803 */
804void EggLoader::
805make_nurbs_surface(EggNurbsSurface *egg_surface, PandaNode *parent,
806 const LMatrix4d &mat) {
807 assert(parent != nullptr);
808 assert(!parent->is_geom_node());
809
810 PT(NurbsSurfaceEvaluator) nurbs = ::make_nurbs_surface(egg_surface, mat);
811 if (nurbs == nullptr) {
812 _error = true;
813 return;
814 }
815
816 PT(SheetNode) sheet = new SheetNode(egg_surface->get_name());
817 sheet->set_surface(nurbs);
818
819 // Respect the subdivision values in the egg file, if any.
820 if (egg_surface->get_u_subdiv() != 0) {
821 int u_subdiv_per_segment =
822 (int)((egg_surface->get_u_subdiv() + 0.5) / nurbs->get_num_u_segments());
823 sheet->set_num_u_subdiv(max(u_subdiv_per_segment, 1));
824 }
825 if (egg_surface->get_v_subdiv() != 0) {
826 int v_subdiv_per_segment =
827 (int)((egg_surface->get_v_subdiv() + 0.5) / nurbs->get_num_v_segments());
828 sheet->set_num_v_subdiv(max(v_subdiv_per_segment, 1));
829 }
830
831 const EggRenderState *render_state;
832 DCAST_INTO_V(render_state, egg_surface->get_user_data(EggRenderState::get_class_type()));
833 if (render_state->_hidden && egg_suppress_hidden) {
834 // Eat this primitive.
835 return;
836 }
837
838 sheet->set_state(render_state->_state);
839
840 if (egg_surface->has_vertex_color()) {
841 // If the surface had individual vertex color, enable it.
842 sheet->set_use_vertex_color(true);
843 } else if (egg_surface->has_color()) {
844 // Otherwise, if the surface has overall color, apply it.
845 sheet->set_attrib(ColorAttrib::make_flat(egg_surface->get_color()));
846 }
847
848 parent->add_child(sheet);
849}
850
851/**
852 *
853 */
854void EggLoader::
855load_textures() {
856 // First, collect all the textures that are referenced.
857 EggTextureCollection tc;
858 tc.find_used_textures(_data);
859
860 EggTextureCollection::iterator ti;
861 for (ti = tc.begin(); ti != tc.end(); ++ti) {
862 PT_EggTexture egg_tex = (*ti);
863
864 TextureDef def;
865 if (load_texture(def, egg_tex)) {
866 // Now associate the pointers, so we'll be able to look up the Texture
867 // pointer given an EggTexture pointer, later.
868 _textures[egg_tex] = def;
869 }
870 }
871}
872
873
874/**
875 *
876 */
877bool EggLoader::
878load_texture(TextureDef &def, EggTexture *egg_tex) {
879 if (egg_tex->get_env_type() == EggTexture::ET___occlusion) {
880 return false;
881 }
882
883 // Check to see if we should reduce the number of channels in the texture.
884 int wanted_channels = 0;
885 bool wanted_alpha = false;
886 switch (egg_tex->get_format()) {
887 case EggTexture::F_red:
888 case EggTexture::F_green:
889 case EggTexture::F_blue:
890 case EggTexture::F_alpha:
891 case EggTexture::F_luminance:
892 wanted_channels = 1;
893 wanted_alpha = false;
894 break;
895
896 case EggTexture::F_luminance_alpha:
897 case EggTexture::F_luminance_alphamask:
898 wanted_channels = 2;
899 wanted_alpha = true;
900 break;
901
902 case EggTexture::F_rgb:
903 case EggTexture::F_rgb12:
904 case EggTexture::F_rgb8:
905 case EggTexture::F_rgb5:
906 case EggTexture::F_rgb332:
907 case EggTexture::F_srgb:
908 wanted_channels = 3;
909 wanted_alpha = false;
910 break;
911
912 case EggTexture::F_rgba:
913 case EggTexture::F_rgbm:
914 case EggTexture::F_rgba12:
915 case EggTexture::F_rgba8:
916 case EggTexture::F_rgba4:
917 case EggTexture::F_rgba5:
918 case EggTexture::F_srgb_alpha:
919 wanted_channels = 4;
920 wanted_alpha = true;
921 break;
922
923 case EggTexture::F_unspecified:
924 wanted_alpha = egg_tex->has_alpha_filename();
925 }
926
927 // Since some properties of the textures are inferred from the texture files
928 // themselves (if the properties are not explicitly specified in the egg
929 // file), then we add the textures as dependents for the egg file.
930 if (_record != nullptr) {
931 _record->add_dependent_file(egg_tex->get_fullpath());
932 if (egg_tex->has_alpha_filename() && wanted_alpha) {
933 _record->add_dependent_file(egg_tex->get_alpha_fullpath());
934 }
935 }
936
937 // By convention, the egg loader will preload the simple texture images.
938 LoaderOptions options;
939 if (egg_preload_simple_textures) {
940 options.set_texture_flags(options.get_texture_flags() | LoaderOptions::TF_preload_simple);
941 }
942
943 if (!egg_ignore_filters && !egg_ignore_mipmaps) {
944 switch (egg_tex->get_minfilter()) {
945 case EggTexture::FT_nearest:
946 case EggTexture::FT_linear:
947 case EggTexture::FT_unspecified:
948 break;
949
950 case EggTexture::FT_nearest_mipmap_nearest:
951 case EggTexture::FT_linear_mipmap_nearest:
952 case EggTexture::FT_nearest_mipmap_linear:
953 case EggTexture::FT_linear_mipmap_linear:
954 options.set_texture_flags(options.get_texture_flags() | LoaderOptions::TF_generate_mipmaps);
955 }
956 }
957
958 if (egg_tex->get_multiview()) {
959 options.set_texture_flags(options.get_texture_flags() | LoaderOptions::TF_multiview);
960 if (egg_tex->has_num_views()) {
961 options.set_texture_num_views(egg_tex->get_num_views());
962 }
963 }
964
965 // Allow the texture loader to pre-compress the texture.
966 if (egg_tex->get_compression_mode() == EggTexture::CM_on) {
967 options.set_texture_flags(options.get_texture_flags() | LoaderOptions::TF_allow_compression);
968 }
969
970 PT(Texture) tex;
971 switch (egg_tex->get_texture_type()) {
972 case EggTexture::TT_unspecified:
973 case EggTexture::TT_1d_texture:
974 options.set_texture_flags(options.get_texture_flags() | LoaderOptions::TF_allow_1d);
975 // Fall through.
976
977 case EggTexture::TT_2d_texture:
978 if (egg_tex->has_alpha_filename() && wanted_alpha) {
980 egg_tex->get_alpha_fullpath(),
981 wanted_channels,
982 egg_tex->get_alpha_file_channel(),
983 egg_tex->get_read_mipmaps(), options);
984 } else {
986 wanted_channels,
987 egg_tex->get_read_mipmaps(), options);
988 }
989 break;
990
991 case EggTexture::TT_3d_texture:
993 egg_tex->get_read_mipmaps(), options);
994 break;
995
996 case EggTexture::TT_cube_map:
998 egg_tex->get_read_mipmaps(), options);
999 break;
1000 }
1001
1002 if (tex == nullptr) {
1003 return false;
1004 }
1005
1006 // Record the original filenames in the textures (as loaded from the egg
1007 // file). These filenames will be written back to the bam file if the bam
1008 // file is written out.
1009 tex->set_filename(egg_tex->get_filename());
1010 if (egg_tex->has_alpha_filename() && wanted_alpha) {
1011 tex->set_alpha_filename(egg_tex->get_alpha_filename());
1012 }
1013
1014 // See if there is some egg data hanging on the texture. In particular, the
1015 // TxaFileFilter might have left that here for us.
1016 TypedReferenceCount *aux = tex->get_aux_data("egg");
1017 if (aux != nullptr &&
1018 aux->is_of_type(EggTexture::get_class_type())) {
1019 EggTexture *aux_egg_tex = DCAST(EggTexture, aux);
1020
1021 if (aux_egg_tex->get_alpha_mode() != EggTexture::AM_unspecified) {
1022 egg_tex->set_alpha_mode(aux_egg_tex->get_alpha_mode());
1023 }
1024 if (aux_egg_tex->get_format() != EggTexture::F_unspecified) {
1025 egg_tex->set_format(aux_egg_tex->get_format());
1026 }
1027 if (aux_egg_tex->get_minfilter() != EggTexture::FT_unspecified) {
1028 egg_tex->set_minfilter(aux_egg_tex->get_minfilter());
1029 }
1030 if (aux_egg_tex->get_magfilter() != EggTexture::FT_unspecified) {
1031 egg_tex->set_magfilter(aux_egg_tex->get_magfilter());
1032 }
1033 if (aux_egg_tex->has_anisotropic_degree()) {
1034 egg_tex->set_anisotropic_degree(aux_egg_tex->get_anisotropic_degree());
1035 }
1036 }
1037
1038 apply_texture_attributes(tex, egg_tex);
1039
1040 // Make a texture stage for the texture.
1041 PT(TextureStage) stage = make_texture_stage(egg_tex);
1042 def._texture = DCAST(TextureAttrib, TextureAttrib::make())->add_on_stage(stage, tex);
1043 def._stage = stage;
1044 def._egg_tex = egg_tex;
1045
1046 return true;
1047}
1048
1049
1050/**
1051 *
1052 */
1053void EggLoader::
1054apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) {
1055 if (egg_tex->get_compression_mode() != EggTexture::CM_default) {
1056 tex->set_compression(convert_compression_mode(egg_tex->get_compression_mode()));
1057 }
1058
1059 SamplerState sampler;
1060
1061 EggTexture::WrapMode wrap_u = egg_tex->determine_wrap_u();
1062 EggTexture::WrapMode wrap_v = egg_tex->determine_wrap_v();
1063 EggTexture::WrapMode wrap_w = egg_tex->determine_wrap_w();
1064
1065 if (wrap_u != EggTexture::WM_unspecified) {
1066 sampler.set_wrap_u(convert_wrap_mode(wrap_u));
1067 }
1068 if (wrap_v != EggTexture::WM_unspecified) {
1069 sampler.set_wrap_v(convert_wrap_mode(wrap_v));
1070 }
1071 if (wrap_w != EggTexture::WM_unspecified) {
1072 sampler.set_wrap_w(convert_wrap_mode(wrap_w));
1073 }
1074
1075 if (egg_tex->has_border_color()) {
1076 sampler.set_border_color(egg_tex->get_border_color());
1077 }
1078
1079 switch (egg_tex->get_minfilter()) {
1080 case EggTexture::FT_nearest:
1081 sampler.set_minfilter(SamplerState::FT_nearest);
1082 break;
1083
1084 case EggTexture::FT_linear:
1085 if (egg_ignore_filters) {
1086 egg2pg_cat.warning()
1087 << "Ignoring minfilter request\n";
1088 sampler.set_minfilter(SamplerState::FT_nearest);
1089 } else {
1090 sampler.set_minfilter(SamplerState::FT_linear);
1091 }
1092 break;
1093
1094 case EggTexture::FT_nearest_mipmap_nearest:
1095 if (egg_ignore_filters) {
1096 egg2pg_cat.warning()
1097 << "Ignoring minfilter request\n";
1098 sampler.set_minfilter(SamplerState::FT_nearest);
1099 } else if (egg_ignore_mipmaps) {
1100 egg2pg_cat.warning()
1101 << "Ignoring mipmap request\n";
1102 sampler.set_minfilter(SamplerState::FT_nearest);
1103 } else {
1104 sampler.set_minfilter(SamplerState::FT_nearest_mipmap_nearest);
1105 }
1106 break;
1107
1108 case EggTexture::FT_linear_mipmap_nearest:
1109 if (egg_ignore_filters) {
1110 egg2pg_cat.warning()
1111 << "Ignoring minfilter request\n";
1112 sampler.set_minfilter(SamplerState::FT_nearest);
1113 } else if (egg_ignore_mipmaps) {
1114 egg2pg_cat.warning()
1115 << "Ignoring mipmap request\n";
1116 sampler.set_minfilter(SamplerState::FT_linear);
1117 } else {
1118 sampler.set_minfilter(SamplerState::FT_linear_mipmap_nearest);
1119 }
1120 break;
1121
1122 case EggTexture::FT_nearest_mipmap_linear:
1123 if (egg_ignore_filters) {
1124 egg2pg_cat.warning()
1125 << "Ignoring minfilter request\n";
1126 sampler.set_minfilter(SamplerState::FT_nearest);
1127 } else if (egg_ignore_mipmaps) {
1128 egg2pg_cat.warning()
1129 << "Ignoring mipmap request\n";
1130 sampler.set_minfilter(SamplerState::FT_nearest);
1131 } else {
1132 sampler.set_minfilter(SamplerState::FT_nearest_mipmap_linear);
1133 }
1134 break;
1135
1136 case EggTexture::FT_linear_mipmap_linear:
1137 if (egg_ignore_filters) {
1138 egg2pg_cat.warning()
1139 << "Ignoring minfilter request\n";
1140 sampler.set_minfilter(SamplerState::FT_nearest);
1141 } else if (egg_ignore_mipmaps) {
1142 egg2pg_cat.warning()
1143 << "Ignoring mipmap request\n";
1144 sampler.set_minfilter(SamplerState::FT_linear);
1145 } else {
1146 sampler.set_minfilter(SamplerState::FT_linear_mipmap_linear);
1147 }
1148 break;
1149
1150 case EggTexture::FT_unspecified:
1151 break;
1152 }
1153
1154 switch (egg_tex->get_magfilter()) {
1155 case EggTexture::FT_nearest:
1156 case EggTexture::FT_nearest_mipmap_nearest:
1157 case EggTexture::FT_nearest_mipmap_linear:
1158 sampler.set_magfilter(SamplerState::FT_nearest);
1159 break;
1160
1161 case EggTexture::FT_linear:
1162 case EggTexture::FT_linear_mipmap_nearest:
1163 case EggTexture::FT_linear_mipmap_linear:
1164 if (egg_ignore_filters) {
1165 egg2pg_cat.warning()
1166 << "Ignoring magfilter request\n";
1167 sampler.set_magfilter(SamplerState::FT_nearest);
1168 } else {
1169 sampler.set_magfilter(SamplerState::FT_linear);
1170 }
1171 break;
1172
1173 case EggTexture::FT_unspecified:
1174 break;
1175 }
1176
1177 if (egg_tex->has_anisotropic_degree()) {
1179 }
1180
1181 if (egg_tex->has_min_lod()) {
1182 sampler.set_min_lod(egg_tex->get_min_lod());
1183 }
1184
1185 if (egg_tex->has_max_lod()) {
1186 sampler.set_max_lod(egg_tex->get_max_lod());
1187 }
1188
1189 if (egg_tex->has_lod_bias()) {
1190 sampler.set_lod_bias(egg_tex->get_lod_bias());
1191 }
1192
1193 tex->set_default_sampler(sampler);
1194
1195 bool force_srgb = false;
1196 if (egg_force_srgb_textures) {
1197 switch (egg_tex->get_env_type()) {
1198 case EggTexture::ET_unspecified:
1199 case EggTexture::ET_modulate:
1200 case EggTexture::ET_decal:
1201 case EggTexture::ET_blend:
1202 case EggTexture::ET_replace:
1203 case EggTexture::ET_add:
1204 case EggTexture::ET_blend_color_scale:
1205 case EggTexture::ET_modulate_glow:
1206 case EggTexture::ET_modulate_gloss:
1207 force_srgb = true;
1208 if (egg2pg_cat.is_debug()) {
1209 egg2pg_cat.debug()
1210 << "Enabling sRGB format on texture " << egg_tex->get_name() << "\n";
1211 }
1212 break;
1213
1214 default:
1215 break;
1216 }
1217 }
1218
1219 if (tex->get_num_components() == 1) {
1220 switch (egg_tex->get_format()) {
1221 case EggTexture::F_red:
1222 tex->set_format(Texture::F_red);
1223 break;
1224 case EggTexture::F_green:
1225 tex->set_format(Texture::F_green);
1226 break;
1227 case EggTexture::F_blue:
1228 tex->set_format(Texture::F_blue);
1229 break;
1230 case EggTexture::F_alpha:
1231 tex->set_format(Texture::F_alpha);
1232 break;
1233 case EggTexture::F_luminance:
1234 tex->set_format(force_srgb ? Texture::F_sluminance : Texture::F_luminance);
1235 break;
1236
1237 default:
1238 egg2pg_cat.warning()
1239 << "Ignoring inappropriate format " << egg_tex->get_format()
1240 << " for 1-component texture " << egg_tex->get_name() << "\n";
1241
1242 case EggTexture::F_unspecified:
1243 if (force_srgb) {
1244 tex->set_format(Texture::F_sluminance);
1245 }
1246 break;
1247 }
1248
1249 } else if (tex->get_num_components() == 2) {
1250 switch (egg_tex->get_format()) {
1251 case EggTexture::F_luminance_alpha:
1252 tex->set_format(force_srgb ? Texture::F_sluminance_alpha : Texture::F_luminance_alpha);
1253 break;
1254
1255 case EggTexture::F_luminance_alphamask:
1256 tex->set_format(force_srgb ? Texture::F_sluminance_alpha : Texture::F_luminance_alphamask);
1257 break;
1258
1259 default:
1260 egg2pg_cat.warning()
1261 << "Ignoring inappropriate format " << egg_tex->get_format()
1262 << " for 2-component texture " << egg_tex->get_name() << "\n";
1263
1264 case EggTexture::F_unspecified:
1265 if (force_srgb) {
1266 tex->set_format(Texture::F_sluminance_alpha);
1267 }
1268 break;
1269 }
1270
1271 } else if (tex->get_num_components() == 3) {
1272 switch (egg_tex->get_format()) {
1273 case EggTexture::F_rgb:
1274 tex->set_format(force_srgb ? Texture::F_srgb : Texture::F_rgb);
1275 break;
1276 case EggTexture::F_rgb12:
1277 if (force_srgb) {
1278 tex->set_format(Texture::F_srgb);
1279 } else if (tex->get_component_width() >= 2) {
1280 // Only do this if the component width supports it.
1281 tex->set_format(Texture::F_rgb12);
1282 } else {
1283 egg2pg_cat.warning()
1284 << "Ignoring inappropriate format " << egg_tex->get_format()
1285 << " for 8-bit texture " << egg_tex->get_name() << "\n";
1286 }
1287 break;
1288 case EggTexture::F_rgb8:
1289 case EggTexture::F_rgba8:
1290 // We'll quietly accept RGBA8 for a 3-component texture, since flt2egg
1291 // generates these for 3-component as well as for 4-component textures.
1292 tex->set_format(force_srgb ? Texture::F_srgb : Texture::F_rgb8);
1293 break;
1294 case EggTexture::F_rgb5:
1295 tex->set_format(force_srgb ? Texture::F_srgb : Texture::F_rgb5);
1296 break;
1297 case EggTexture::F_rgb332:
1298 tex->set_format(force_srgb ? Texture::F_srgb : Texture::F_rgb332);
1299 break;
1300 case EggTexture::F_srgb:
1301 case EggTexture::F_srgb_alpha:
1302 tex->set_format(Texture::F_srgb);
1303 break;
1304
1305 default:
1306 egg2pg_cat.warning()
1307 << "Ignoring inappropriate format " << egg_tex->get_format()
1308 << " for 3-component texture " << egg_tex->get_name() << "\n";
1309
1310 case EggTexture::F_unspecified:
1311 if (force_srgb) {
1312 tex->set_format(Texture::F_srgb);
1313 }
1314 break;
1315 }
1316
1317 } else if (tex->get_num_components() == 4) {
1318 switch (egg_tex->get_format()) {
1319 case EggTexture::F_rgba:
1320 tex->set_format(force_srgb ? Texture::F_srgb_alpha : Texture::F_rgba);
1321 break;
1322 case EggTexture::F_rgbm:
1323 tex->set_format(force_srgb ? Texture::F_srgb_alpha : Texture::F_rgbm);
1324 break;
1325 case EggTexture::F_rgba12:
1326 if (force_srgb) {
1327 tex->set_format(Texture::F_srgb_alpha);
1328 } else if (tex->get_component_width() >= 2) {
1329 // Only do this if the component width supports it.
1330 tex->set_format(force_srgb ? Texture::F_srgb_alpha : Texture::F_rgba12);
1331 } else {
1332 egg2pg_cat.warning()
1333 << "Ignoring inappropriate format " << egg_tex->get_format()
1334 << " for 8-bit texture " << egg_tex->get_name() << "\n";
1335 }
1336 break;
1337 case EggTexture::F_rgba8:
1338 tex->set_format(force_srgb ? Texture::F_srgb_alpha : Texture::F_rgba8);
1339 break;
1340 case EggTexture::F_rgba4:
1341 tex->set_format(force_srgb ? Texture::F_srgb_alpha : Texture::F_rgba4);
1342 break;
1343 case EggTexture::F_rgba5:
1344 tex->set_format(force_srgb ? Texture::F_srgb_alpha : Texture::F_rgba5);
1345 break;
1346 case EggTexture::F_srgb_alpha:
1347 tex->set_format(Texture::F_srgb_alpha);
1348 break;
1349
1350 default:
1351 egg2pg_cat.warning()
1352 << "Ignoring inappropriate format " << egg_tex->get_format()
1353 << " for 4-component texture " << egg_tex->get_name() << "\n";
1354
1355 case EggTexture::F_unspecified:
1356 if (force_srgb) {
1357 tex->set_format(Texture::F_srgb_alpha);
1358 }
1359 break;
1360 }
1361 }
1362
1363 if (force_srgb && tex->get_format() != Texture::F_alpha &&
1364 !Texture::is_srgb(tex->get_format())) {
1365 egg2pg_cat.warning()
1366 << "Unable to enable sRGB format on texture " << egg_tex->get_name()
1367 << " with specified format " << egg_tex->get_format() << "\n";
1368 }
1369
1370 switch (egg_tex->get_quality_level()) {
1371 case EggTexture::QL_unspecified:
1372 case EggTexture::QL_default:
1373 tex->set_quality_level(Texture::QL_default);
1374 break;
1375
1376 case EggTexture::QL_fastest:
1377 tex->set_quality_level(Texture::QL_fastest);
1378 break;
1379
1380 case EggTexture::QL_normal:
1381 tex->set_quality_level(Texture::QL_normal);
1382 break;
1383
1384 case EggTexture::QL_best:
1385 tex->set_quality_level(Texture::QL_best);
1386 break;
1387 }
1388}
1389
1390/**
1391 * Returns the Texture::CompressionMode enum corresponding to the
1392 * EggTexture::CompressionMode. Returns CM_default if the compression mode is
1393 * unspecified.
1394 */
1395Texture::CompressionMode EggLoader::
1396convert_compression_mode(EggTexture::CompressionMode compression_mode) const {
1397 switch (compression_mode) {
1398 case EggTexture::CM_off:
1399 return Texture::CM_off;
1400
1401 case EggTexture::CM_on:
1402 return Texture::CM_on;
1403
1404 case EggTexture::CM_fxt1:
1405 return Texture::CM_fxt1;
1406
1407 case EggTexture::CM_dxt1:
1408 return Texture::CM_dxt1;
1409
1410 case EggTexture::CM_dxt2:
1411 return Texture::CM_dxt2;
1412
1413 case EggTexture::CM_dxt3:
1414 return Texture::CM_dxt3;
1415
1416 case EggTexture::CM_dxt4:
1417 return Texture::CM_dxt4;
1418
1419 case EggTexture::CM_dxt5:
1420 return Texture::CM_dxt5;
1421
1422 case EggTexture::CM_default:
1423 return Texture::CM_default;
1424 }
1425
1426 egg2pg_cat.warning()
1427 << "Unexpected texture compression flag: " << (int)compression_mode << "\n";
1428 return Texture::CM_default;
1429}
1430
1431/**
1432 * Returns the SamplerState::WrapMode enum corresponding to the
1433 * EggTexture::WrapMode. Returns WM_repeat if the wrap mode is unspecified.
1434 */
1435SamplerState::WrapMode EggLoader::
1436convert_wrap_mode(EggTexture::WrapMode wrap_mode) const {
1437 switch (wrap_mode) {
1438 case EggTexture::WM_clamp:
1439 return SamplerState::WM_clamp;
1440
1441 case EggTexture::WM_repeat:
1442 return SamplerState::WM_repeat;
1443
1444 case EggTexture::WM_mirror:
1445 return SamplerState::WM_mirror;
1446
1447 case EggTexture::WM_mirror_once:
1448 return SamplerState::WM_mirror_once;
1449
1450 case EggTexture::WM_border_color:
1451 return SamplerState::WM_border_color;
1452
1453 case EggTexture::WM_unspecified:
1454 return SamplerState::WM_repeat;
1455 }
1456
1457 egg2pg_cat.warning()
1458 << "Unexpected texture wrap flag: " << (int)wrap_mode << "\n";
1459 return SamplerState::WM_repeat;
1460}
1461
1462/**
1463 * Creates a TextureStage object suitable for rendering the indicated texture.
1464 */
1465PT(TextureStage) EggLoader::
1466make_texture_stage(const EggTexture *egg_tex) {
1467 // If the egg texture specifies any relevant TextureStage properties, or if
1468 // it is multitextured on top of anything else, it gets its own texture
1469 // stage; otherwise, it gets the default texture stage.
1470 if (!egg_tex->has_stage_name() &&
1471 !egg_tex->has_uv_name() &&
1472 !egg_tex->has_color() &&
1473 (egg_tex->get_env_type() == EggTexture::ET_unspecified ||
1474 egg_tex->get_env_type() == EggTexture::ET_modulate) &&
1475 egg_tex->get_combine_mode(EggTexture::CC_rgb) == EggTexture::CM_unspecified &&
1476 egg_tex->get_combine_mode(EggTexture::CC_alpha) == EggTexture::CM_unspecified &&
1477
1478 !egg_tex->has_priority() &&
1479 egg_tex->get_multitexture_sort() == 0 &&
1480 !egg_tex->get_saved_result()) {
1482 }
1483
1484 PT(TextureStage) stage = new TextureStage(egg_tex->get_stage_name());
1485
1486 switch (egg_tex->get_env_type()) {
1487 case EggTexture::ET_modulate:
1488 stage->set_mode(TextureStage::M_modulate);
1489 break;
1490
1491 case EggTexture::ET_decal:
1492 stage->set_mode(TextureStage::M_decal);
1493 break;
1494
1495 case EggTexture::ET_blend:
1496 stage->set_mode(TextureStage::M_blend);
1497 break;
1498
1499 case EggTexture::ET_replace:
1500 stage->set_mode(TextureStage::M_replace);
1501 break;
1502
1503 case EggTexture::ET_add:
1504 stage->set_mode(TextureStage::M_add);
1505 break;
1506
1507 case EggTexture::ET_blend_color_scale:
1508 stage->set_mode(TextureStage::M_blend_color_scale);
1509 break;
1510
1511 case EggTexture::ET_modulate_glow:
1512 stage->set_mode(TextureStage::M_modulate_glow);
1513 break;
1514
1515 case EggTexture::ET_modulate_gloss:
1516 stage->set_mode(TextureStage::M_modulate_gloss);
1517 break;
1518
1519 case EggTexture::ET_normal:
1520 stage->set_mode(TextureStage::M_normal);
1521 break;
1522
1523 case EggTexture::ET_normal_height:
1524 stage->set_mode(TextureStage::M_normal_height);
1525 break;
1526
1527 case EggTexture::ET_glow:
1528 stage->set_mode(TextureStage::M_glow);
1529 break;
1530
1531 case EggTexture::ET_gloss:
1532 stage->set_mode(TextureStage::M_gloss);
1533 break;
1534
1535 case EggTexture::ET_height:
1536 stage->set_mode(TextureStage::M_height);
1537 break;
1538
1539 case EggTexture::ET_selector:
1540 case EggTexture::ET___metallic_roughness:
1541 case EggTexture::ET___occlusion_metallic_roughness:
1542 stage->set_mode(TextureStage::M_selector);
1543 break;
1544
1545 case EggTexture::ET_normal_gloss:
1546 stage->set_mode(TextureStage::M_normal_gloss);
1547 break;
1548
1549 case EggTexture::ET_emission:
1550 stage->set_mode(TextureStage::M_emission);
1551 break;
1552
1553 case EggTexture::ET_unspecified:
1554 break;
1555 }
1556
1557 switch (egg_tex->get_combine_mode(EggTexture::CC_rgb)) {
1558 case EggTexture::CM_replace:
1559 stage->set_combine_rgb(get_combine_mode(egg_tex, EggTexture::CC_rgb),
1560 get_combine_source(egg_tex, EggTexture::CC_rgb, 0),
1561 get_combine_operand(egg_tex, EggTexture::CC_rgb, 0));
1562 break;
1563
1564 case EggTexture::CM_modulate:
1565 case EggTexture::CM_add:
1566 case EggTexture::CM_add_signed:
1567 case EggTexture::CM_subtract:
1568 case EggTexture::CM_dot3_rgb:
1569 case EggTexture::CM_dot3_rgba:
1570 stage->set_combine_rgb(get_combine_mode(egg_tex, EggTexture::CC_rgb),
1571 get_combine_source(egg_tex, EggTexture::CC_rgb, 0),
1572 get_combine_operand(egg_tex, EggTexture::CC_rgb, 0),
1573 get_combine_source(egg_tex, EggTexture::CC_rgb, 1),
1574 get_combine_operand(egg_tex, EggTexture::CC_rgb, 1));
1575 break;
1576
1577 case EggTexture::CM_interpolate:
1578 stage->set_combine_rgb(get_combine_mode(egg_tex, EggTexture::CC_rgb),
1579 get_combine_source(egg_tex, EggTexture::CC_rgb, 0),
1580 get_combine_operand(egg_tex, EggTexture::CC_rgb, 0),
1581 get_combine_source(egg_tex, EggTexture::CC_rgb, 1),
1582 get_combine_operand(egg_tex, EggTexture::CC_rgb, 1),
1583 get_combine_source(egg_tex, EggTexture::CC_rgb, 2),
1584 get_combine_operand(egg_tex, EggTexture::CC_rgb, 2));
1585 break;
1586
1587 case EggTexture::CM_unspecified:
1588 break;
1589 }
1590
1591 switch (egg_tex->get_combine_mode(EggTexture::CC_alpha)) {
1592 case EggTexture::CM_replace:
1593 stage->set_combine_alpha(get_combine_mode(egg_tex, EggTexture::CC_alpha),
1594 get_combine_source(egg_tex, EggTexture::CC_alpha, 0),
1595 get_combine_operand(egg_tex, EggTexture::CC_alpha, 0));
1596 break;
1597
1598 case EggTexture::CM_modulate:
1599 case EggTexture::CM_add:
1600 case EggTexture::CM_add_signed:
1601 case EggTexture::CM_subtract:
1602 stage->set_combine_alpha(get_combine_mode(egg_tex, EggTexture::CC_alpha),
1603 get_combine_source(egg_tex, EggTexture::CC_alpha, 0),
1604 get_combine_operand(egg_tex, EggTexture::CC_alpha, 0),
1605 get_combine_source(egg_tex, EggTexture::CC_alpha, 1),
1606 get_combine_operand(egg_tex, EggTexture::CC_alpha, 1));
1607 break;
1608
1609 case EggTexture::CM_interpolate:
1610 stage->set_combine_alpha(get_combine_mode(egg_tex, EggTexture::CC_alpha),
1611 get_combine_source(egg_tex, EggTexture::CC_alpha, 0),
1612 get_combine_operand(egg_tex, EggTexture::CC_alpha, 0),
1613 get_combine_source(egg_tex, EggTexture::CC_alpha, 1),
1614 get_combine_operand(egg_tex, EggTexture::CC_alpha, 1),
1615 get_combine_source(egg_tex, EggTexture::CC_alpha, 2),
1616 get_combine_operand(egg_tex, EggTexture::CC_alpha, 2));
1617 break;
1618
1619 case EggTexture::CM_unspecified:
1620 case EggTexture::CM_dot3_rgb:
1621 case EggTexture::CM_dot3_rgba:
1622 break;
1623 }
1624
1625
1626 if (egg_tex->has_uv_name()) {
1627 PT(InternalName) name =
1628 InternalName::get_texcoord_name(egg_tex->get_uv_name());
1629 stage->set_texcoord_name(name);
1630 }
1631
1632 if (egg_tex->has_rgb_scale()) {
1633 stage->set_rgb_scale(egg_tex->get_rgb_scale());
1634 }
1635
1636 if (egg_tex->has_alpha_scale()) {
1637 stage->set_alpha_scale(egg_tex->get_alpha_scale());
1638 }
1639
1640 stage->set_saved_result(egg_tex->get_saved_result());
1641
1642 stage->set_sort(egg_tex->get_multitexture_sort() * 10);
1643
1644 if (egg_tex->has_priority()) {
1645 stage->set_sort(egg_tex->get_priority());
1646 }
1647
1648 if (egg_tex->has_color()) {
1649 stage->set_color(egg_tex->get_color());
1650 }
1651
1652 return TextureStagePool::get_stage(stage);
1653}
1654
1655/**
1656 * Walks the tree recursively, looking for EggPrimitives that are children of
1657 * sequence or switch nodes. If any are found, they are moved within their
1658 * own group to protect them from being flattened with their neighbors.
1659 */
1660void EggLoader::
1661separate_switches(EggNode *egg_node) {
1662 bool parent_has_switch = false;
1663 if (egg_node->is_of_type(EggGroup::get_class_type())) {
1664 EggGroup *egg_group = DCAST(EggGroup, egg_node);
1665 parent_has_switch = egg_group->get_switch_flag();
1666 }
1667
1668 if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
1669 EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
1670
1671 EggGroupNode::iterator ci;
1672 ci = egg_group->begin();
1673 while (ci != egg_group->end()) {
1674 EggGroupNode::iterator cnext;
1675 cnext = ci;
1676 ++cnext;
1677
1678 PT(EggNode) child = (*ci);
1679 if (parent_has_switch &&
1680 child->is_of_type(EggPrimitive::get_class_type())) {
1681 // Move this child under a new node.
1682 PT(EggGroup) new_group = new EggGroup(child->get_name());
1683 egg_group->replace(ci, new_group.p());
1684 new_group->add_child(child);
1685 }
1686
1687 separate_switches(child);
1688
1689 ci = cnext;
1690 }
1691 }
1692}
1693
1694/**
1695 * Looks for EggPolygons with a bface flag applied to them. Any such polygons
1696 * are duplicated into a pair of back-to-back polygons, and the bface flag is
1697 * removed.
1698 */
1699void EggLoader::
1700emulate_bface(EggNode *egg_node) {
1701 if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
1702 EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
1703 PT(EggGroupNode) dup_prims = new EggGroupNode;
1704
1705 EggGroupNode::iterator ci;
1706 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
1707 PT(EggNode) child = (*ci);
1708 if (child->is_of_type(EggPolygon::get_class_type())) {
1709 EggPolygon *poly = DCAST(EggPolygon, child);
1710 if (poly->get_bface_flag()) {
1711 poly->set_bface_flag(false);
1712
1713 PT(EggPolygon) dup_poly = new EggPolygon(*poly);
1714 dup_poly->reverse_vertex_ordering();
1715 if (dup_poly->has_normal()) {
1716 dup_poly->set_normal(-dup_poly->get_normal());
1717 }
1718
1719 // Also reverse the normal on any vertices.
1720 EggPolygon::iterator vi;
1721 for (vi = dup_poly->begin(); vi != dup_poly->end(); ++vi) {
1722 EggVertex *vertex = (*vi);
1723 if (vertex->has_normal()) {
1724 EggVertex dup_vertex(*vertex);
1725 dup_vertex.set_normal(-dup_vertex.get_normal());
1726 EggVertex *new_vertex = vertex->get_pool()->create_unique_vertex(dup_vertex);
1727 if (new_vertex != vertex) {
1728 new_vertex->copy_grefs_from(*vertex);
1729 dup_poly->replace(vi, new_vertex);
1730 }
1731 }
1732 }
1733 dup_prims->add_child(dup_poly);
1734 }
1735 }
1736
1737 emulate_bface(child);
1738 }
1739
1740 // Now that we've iterated through all the children, add in any duplicated
1741 // polygons we generated.
1742 egg_group->steal_children(*dup_prims);
1743 }
1744}
1745
1746/**
1747 *
1748 */
1749PandaNode *EggLoader::
1750make_node(EggNode *egg_node, PandaNode *parent) {
1751 if (egg_node->is_of_type(EggBin::get_class_type())) {
1752 return make_node(DCAST(EggBin, egg_node), parent);
1753 } else if (egg_node->is_of_type(EggGroup::get_class_type())) {
1754 return make_node(DCAST(EggGroup, egg_node), parent);
1755 } else if (egg_node->is_of_type(EggTable::get_class_type())) {
1756 return make_node(DCAST(EggTable, egg_node), parent);
1757 } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
1758 return make_node(DCAST(EggGroupNode, egg_node), parent);
1759 }
1760
1761 return nullptr;
1762}
1763
1764/**
1765 *
1766 */
1767PandaNode *EggLoader::
1768make_node(EggBin *egg_bin, PandaNode *parent) {
1769 // An EggBin might mean an LOD node (i.e. a parent of one or more EggGroups
1770 // with LOD specifications), or it might mean a polyset node (a parent of
1771 // one or more similar EggPrimitives).
1772 switch (egg_bin->get_bin_number()) {
1773 case EggBinner::BN_polyset:
1774 case EggBinner::BN_patches:
1775 make_polyset(egg_bin, parent, nullptr, _dynamic_override, _dynamic_override_char_maker);
1776 return nullptr;
1777
1778 case EggBinner::BN_lod:
1779 return make_lod(egg_bin, parent);
1780
1781 case EggBinner::BN_nurbs_surface:
1782 {
1783 nassertr(!egg_bin->empty(), nullptr);
1784 EggNode *child = egg_bin->get_first_child();
1785 EggNurbsSurface *egg_nurbs;
1786 DCAST_INTO_R(egg_nurbs, child, nullptr);
1787 const LMatrix4d &mat = egg_nurbs->get_vertex_to_node();
1788 make_nurbs_surface(egg_nurbs, parent, mat);
1789 }
1790 return nullptr;
1791
1792 case EggBinner::BN_nurbs_curve:
1793 {
1794 nassertr(!egg_bin->empty(), nullptr);
1795 EggNode *child = egg_bin->get_first_child();
1796 EggNurbsCurve *egg_nurbs;
1797 DCAST_INTO_R(egg_nurbs, child, nullptr);
1798 const LMatrix4d &mat = egg_nurbs->get_vertex_to_node();
1799 make_nurbs_curve(egg_nurbs, parent, mat);
1800 }
1801 return nullptr;
1802
1803 case EggBinner::BN_none:
1804 break;
1805 }
1806
1807 // Shouldn't get here.
1808 return nullptr;
1809}
1810
1811/**
1812 *
1813 */
1814PandaNode *EggLoader::
1815make_lod(EggBin *egg_bin, PandaNode *parent) {
1816 PT(LODNode) lod_node = LODNode::make_default_lod(egg_bin->get_name());
1817
1818 pvector<LODInstance> instances;
1819
1820 EggGroup::const_iterator ci;
1821 for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) {
1822 LODInstance instance(*ci);
1823 instances.push_back(instance);
1824 }
1825
1826 // Now that we've created all of our children, put them in the proper order
1827 // and tell the LOD node about them.
1828 sort(instances.begin(), instances.end());
1829
1830 if (!instances.empty()) {
1831 // Set up the LOD node's center. All of the children should have the same
1832 // center, because that's how we binned them.
1833 lod_node->set_center(LCAST(PN_stdfloat, instances[0]._d->_center));
1834 }
1835
1836 for (size_t i = 0; i < instances.size(); i++) {
1837 // Create the children in the proper order within the scene graph.
1838 const LODInstance &instance = instances[i];
1839 make_node(instance._egg_node, lod_node);
1840
1841 // All of the children should have the same center, because that's how we
1842 // binned them.
1843 nassertr(lod_node->get_center().almost_equal
1844 (LCAST(PN_stdfloat, instance._d->_center), 0.01), nullptr);
1845
1846 // Tell the LOD node about this child's switching distances.
1847 lod_node->add_switch(instance._d->_switch_in, instance._d->_switch_out);
1848 }
1849
1850 _groups[egg_bin] = lod_node;
1851 return create_group_arc(egg_bin, parent, lod_node);
1852}
1853
1854/**
1855 *
1856 */
1857PandaNode *EggLoader::
1858make_node(EggGroup *egg_group, PandaNode *parent) {
1859 PT(PandaNode) node = nullptr;
1860
1861 if (egg_group->get_dart_type() != EggGroup::DT_none) {
1862 // A group with the <Dart> flag set means to create a character.
1863 bool structured = (egg_group->get_dart_type() == EggGroup::DT_structured);
1864
1865 CharacterMaker char_maker(egg_group, *this, structured);
1866
1867 node = char_maker.make_node();
1868 if(structured) {
1869 // we're going to generate the rest of the children normally except
1870 // we'll be making dynamic geometry
1871 _dynamic_override = true;
1872 _dynamic_override_char_maker = &char_maker;
1873 EggGroup::const_iterator ci;
1874 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
1875 make_node(*ci, node);
1876 }
1877 _dynamic_override_char_maker = nullptr;
1878 _dynamic_override = false;
1879 }
1880
1881 } else if (egg_group->get_cs_type() != EggGroup::CST_none) {
1882 // A collision group: create collision geometry.
1883 node = new CollisionNode(egg_group->get_name());
1884
1885 // Piggy-back the desired transform to apply onto the node, since we can't
1886 // break the ABI in 1.10.
1887 node->set_transform(TransformState::make_mat(LCAST(PN_stdfloat, egg_group->get_vertex_to_node())));
1888 make_collision_solids(egg_group, egg_group, (CollisionNode *)node.p());
1889 node->clear_transform();
1890
1891 if ((egg_group->get_collide_flags() & EggGroup::CF_keep) != 0) {
1892 // If we also specified to keep the geometry, continue the traversal.
1893 // In this case, we create a new PandaNode to be the parent of the
1894 // visible geometry and the collision geometry.
1895 PandaNode *combined = new PandaNode("");
1896 parent->add_child(combined);
1897 combined->add_child(node);
1898 node = combined;
1899
1900 EggGroup::const_iterator ci;
1901 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
1902 make_node(*ci, combined);
1903 }
1904 }
1905
1906 node = create_group_arc(egg_group, parent, node);
1907 return node;
1908
1909 } else if (egg_group->get_portal_flag()) {
1910 // Create a portal instead of a regular polyset. Scan the children of
1911 // this node looking for a polygon, similar to the collision polygon case,
1912 // above.
1913 PortalNode *pnode = new PortalNode(egg_group->get_name());
1914 node = pnode;
1915
1916 set_portal_polygon(egg_group, pnode);
1917 if (pnode->get_num_vertices() == 0) {
1918 egg2pg_cat.warning()
1919 << "Portal " << egg_group->get_name() << " has no vertices!\n";
1920 }
1921
1922 } else if (egg_group->get_occluder_flag()) {
1923 // Create an occluder instead of a regular polyset. Scan the children of
1924 // this node looking for a polygon, the same as the portal polygon case,
1925 // above.
1926 OccluderNode *pnode = new OccluderNode(egg_group->get_name());
1927 node = pnode;
1928
1929 set_occluder_polygon(egg_group, pnode);
1930 if (pnode->get_num_vertices() == 0) {
1931 egg2pg_cat.warning()
1932 << "Occluder " << egg_group->get_name() << " has no vertices!\n";
1933 }
1934
1935 } else if (egg_group->get_polylight_flag()) {
1936 // Create a polylight instead of a regular polyset. use make_sphere to
1937 // get the center, radius and color egg2pg_cat.debug() << "polylight
1938 // node\n";
1939 LPoint3 center;
1940 LColor color;
1941 PN_stdfloat radius;
1942
1943 if (!make_sphere(egg_group, EggGroup::CF_none, center, radius, color)) {
1944 egg2pg_cat.warning()
1945 << "Polylight " << egg_group->get_name() << " make_sphere failed!\n";
1946 }
1947 PolylightNode *pnode = new PolylightNode(egg_group->get_name());
1948 pnode->set_pos(center);
1949 pnode->set_color(color);
1950 pnode->set_radius(radius);
1951
1952 pnode->xform(LCAST(PN_stdfloat, egg_group->get_vertex_to_node()));
1953
1954 node = pnode;
1955
1956 } else if (egg_group->get_switch_flag()) {
1957 if (egg_group->get_switch_fps() != 0.0) {
1958 // Create a sequence node.
1959 node = new SequenceNode(egg_group->get_name());
1960 ((SequenceNode *)node.p())->set_frame_rate(egg_group->get_switch_fps());
1961 _sequences.insert(node);
1962 } else {
1963 // Create a switch node.
1964 node = new SwitchNode(egg_group->get_name());
1965 }
1966
1967 EggGroup::const_iterator ci;
1968 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
1969 make_node(*ci, node);
1970 }
1971 } else if (egg_group->has_scrolling_uvs()) {
1972 node = new UvScrollNode(egg_group->get_name(), egg_group->get_scroll_u(), egg_group->get_scroll_v(), egg_group->get_scroll_w(), egg_group->get_scroll_r());
1973
1974 EggGroup::const_iterator ci;
1975 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
1976 make_node(*ci, node);
1977 }
1978
1979 } else if (egg_group->get_model_flag() || egg_group->has_dcs_type()) {
1980 // A model or DCS flag; create a model node.
1981 node = new ModelNode(egg_group->get_name());
1982 switch (egg_group->get_dcs_type()) {
1983 case EggGroup::DC_net:
1984 DCAST(ModelNode, node)->set_preserve_transform(ModelNode::PT_net);
1985 break;
1986
1987 case EggGroup::DC_no_touch:
1988 DCAST(ModelNode, node)->set_preserve_transform(ModelNode::PT_no_touch);
1989 break;
1990
1991 case EggGroup::DC_local:
1992 case EggGroup::DC_default:
1993 DCAST(ModelNode, node)->set_preserve_transform(ModelNode::PT_local);
1994 break;
1995
1996 case EggGroup::DC_none:
1997 case EggGroup::DC_unspecified:
1998 break;
1999 }
2000
2001 EggGroup::const_iterator ci;
2002 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
2003 make_node(*ci, node);
2004 }
2005
2006 } else {
2007 // A normal group; just create a normal node, and traverse. But if all of
2008 // the children of this group are polysets, anticipate this for the
2009 // benefit of smaller grouping, and create a single GeomNode for all of
2010 // the children.
2011 bool all_polysets = false;
2012 bool any_hidden = false;
2013
2014 // We don't want to ever create a GeomNode under a "decal" flag, since
2015 // that can confuse the decal reparenting.
2016 if (!egg_group->determine_decal()) {
2017 check_for_polysets(egg_group, all_polysets, any_hidden);
2018 }
2019
2020 if (all_polysets && !any_hidden) {
2021 node = new GeomNode(egg_group->get_name());
2022 } else {
2023 node = new PandaNode(egg_group->get_name());
2024 }
2025
2026 EggGroup::const_iterator ci;
2027 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
2028 make_node(*ci, node);
2029 }
2030 }
2031
2032 if (node == nullptr) {
2033 return nullptr;
2034 }
2035
2036 // Associate any instances with this node.
2037 int num_group_refs = egg_group->get_num_group_refs();
2038 for (int gri = 0; gri < num_group_refs; ++gri) {
2039 EggGroup *group_ref = egg_group->get_group_ref(gri);
2040 Groups::const_iterator gi = _groups.find(group_ref);
2041 if (gi != _groups.end()) {
2042 PandaNode *node_ref = (*gi).second;
2043 node->add_child(node_ref);
2044 }
2045 }
2046
2047 _groups[egg_group] = node;
2048 return create_group_arc(egg_group, parent, node);
2049}
2050
2051/**
2052 * Creates the arc parenting a new group to the scene graph, and applies any
2053 * relevant attribs to the arc according to the EggGroup node that inspired
2054 * the group.
2055 */
2056PandaNode *EggLoader::
2057create_group_arc(EggGroup *egg_group, PandaNode *parent, PandaNode *node) {
2058 parent->add_child(node);
2059
2060 // If the group had a transform, apply it to the node.
2061 if (egg_group->has_transform()) {
2062 CPT(TransformState) transform = make_transform(egg_group);
2063 node->set_transform(transform);
2064 node->set_prev_transform(transform);
2065 }
2066
2067 // If the group has a billboard flag, apply that.
2068 switch (egg_group->get_billboard_type()) {
2069 case EggGroup::BT_point_camera_relative:
2070 node->set_effect(BillboardEffect::make_point_eye());
2071 break;
2072
2073 case EggGroup::BT_point_world_relative:
2074 node->set_effect(BillboardEffect::make_point_world());
2075 break;
2076
2077 case EggGroup::BT_axis:
2078 node->set_effect(BillboardEffect::make_axis());
2079 break;
2080
2081 case EggGroup::BT_none:
2082 break;
2083 }
2084
2085 if (egg_group->get_decal_flag()) {
2086 if (egg_ignore_decals) {
2087 egg2pg_cat.error()
2088 << "Ignoring decal flag on " << egg_group->get_name() << "\n";
2089 _error = true;
2090 }
2091
2092 // If the group has the "decal" flag set, it means that all of the
2093 // descendant groups will be decaled onto the geometry within this group.
2094 // This means we'll need to reparent things a bit afterward.
2095 _decals.insert(node);
2096 }
2097
2098 // Copy all the tags from the group onto the node.
2099 EggGroup::TagData::const_iterator ti;
2100 for (ti = egg_group->tag_begin(); ti != egg_group->tag_end(); ++ti) {
2101 node->set_tag((*ti).first, (*ti).second);
2102 }
2103
2104 if (egg_group->get_blend_mode() != EggGroup::BM_unspecified &&
2105 egg_group->get_blend_mode() != EggGroup::BM_none) {
2106 // Apply a ColorBlendAttrib to the group.
2107 ColorBlendAttrib::Mode mode = get_color_blend_mode(egg_group->get_blend_mode());
2108 ColorBlendAttrib::Operand a = get_color_blend_operand(egg_group->get_blend_operand_a());
2109 ColorBlendAttrib::Operand b = get_color_blend_operand(egg_group->get_blend_operand_b());
2110 LColor color = egg_group->get_blend_color();
2111 node->set_attrib(ColorBlendAttrib::make(mode, a, b, color));
2112 }
2113
2114 // If the group specified some property that should propagate down to the
2115 // leaves, we have to remember this node and apply the property later, after
2116 // we've created the actual geometry.
2117 DeferredNodeProperty def;
2118 if (egg_group->has_collide_mask()) {
2119 def._from_collide_mask = egg_group->get_collide_mask();
2120 def._into_collide_mask = egg_group->get_collide_mask();
2121 def._flags |=
2122 DeferredNodeProperty::F_has_from_collide_mask |
2123 DeferredNodeProperty::F_has_into_collide_mask;
2124 }
2125 if (egg_group->has_from_collide_mask()) {
2126 def._from_collide_mask = egg_group->get_from_collide_mask();
2127 def._flags |= DeferredNodeProperty::F_has_from_collide_mask;
2128 }
2129 if (egg_group->has_into_collide_mask()) {
2130 def._into_collide_mask = egg_group->get_into_collide_mask();
2131 def._flags |= DeferredNodeProperty::F_has_into_collide_mask;
2132 }
2133
2134 if (def._flags != 0) {
2135 _deferred_nodes[node] = def;
2136 }
2137
2138 return node;
2139}
2140
2141/**
2142 *
2143 */
2144PandaNode *EggLoader::
2145make_node(EggTable *egg_table, PandaNode *parent) {
2146 if (egg_table->get_table_type() != EggTable::TT_bundle) {
2147 // We only do anything with bundles. Isolated tables are treated as
2148 // ordinary groups.
2149 return make_node(DCAST(EggGroupNode, egg_table), parent);
2150 }
2151
2152 // It's an actual bundle, so make an AnimBundle from it and its descendants.
2153 AnimBundleMaker bundle_maker(egg_table);
2154 AnimBundleNode *node = bundle_maker.make_node();
2155 parent->add_child(node);
2156 return node;
2157}
2158
2159
2160/**
2161 *
2162 */
2163PandaNode *EggLoader::
2164make_node(EggGroupNode *egg_group, PandaNode *parent) {
2165 PandaNode *node = new PandaNode(egg_group->get_name());
2166
2167 EggGroupNode::const_iterator ci;
2168 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
2169 make_node(*ci, node);
2170 }
2171
2172 parent->add_child(node);
2173 return node;
2174}
2175
2176/**
2177 * Sets all_polysets true if all of the children of this node represent a
2178 * polyset. Sets any_hidden true if any of those polysets are flagged hidden.
2179 */
2180void EggLoader::
2181check_for_polysets(EggGroup *egg_group, bool &all_polysets, bool &any_hidden) {
2182 all_polysets = (!egg_group->empty());
2183 any_hidden = false;
2184
2185 EggGroup::const_iterator ci;
2186 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
2187 if ((*ci)->is_of_type(EggBin::get_class_type())) {
2188 EggBin *egg_bin = DCAST(EggBin, (*ci));
2189 if (egg_bin->get_bin_number() == EggBinner::BN_polyset) {
2190 // We know that all of the primitives in the bin have the same render
2191 // state, so we can get that information from the first primitive.
2192 EggGroup::const_iterator bci = egg_bin->begin();
2193 nassertv(bci != egg_bin->end());
2194 const EggPrimitive *first_prim;
2195 DCAST_INTO_V(first_prim, (*bci));
2196 const EggRenderState *render_state;
2197 DCAST_INTO_V(render_state, first_prim->get_user_data(EggRenderState::get_class_type()));
2198
2199 if (render_state->_hidden) {
2200 any_hidden = true;
2201 }
2202 } else {
2203 all_polysets = false;
2204 return;
2205 }
2206 } else if ((*ci)->is_of_type(EggGroup::get_class_type())) {
2207 // Other kinds of children, like vertex pools, comments, textures, etc.,
2208 // are ignored; but groups indicate more nodes, so if we find a nested
2209 // group it means we're not all polysets.
2210 all_polysets = false;
2211 return;
2212 }
2213 }
2214}
2215
2216/**
2217 * Creates a GeomVertexData structure from the vertex pool, for the indicated
2218 * transform space. If a GeomVertexData has already been created for this
2219 * transform, just returns it.
2220 */
2221PT(GeomVertexData) EggLoader::
2222make_vertex_data(const EggRenderState *render_state,
2223 EggVertexPool *vertex_pool, EggNode *primitive_home,
2224 const LMatrix4d &transform, TransformBlendTable *blend_table,
2225 bool is_dynamic, CharacterMaker *character_maker,
2226 bool ignore_color) {
2227 VertexPoolTransform vpt;
2228 vpt._vertex_pool = vertex_pool;
2229 vpt._bake_in_uvs = render_state->_bake_in_uvs;
2230 vpt._transform = transform;
2231
2232 VertexPoolData::iterator di;
2233 di = _vertex_pool_data.find(vpt);
2234 if (di != _vertex_pool_data.end()) {
2235 return (*di).second;
2236 }
2237
2238 PT(GeomVertexArrayFormat) array_format = new GeomVertexArrayFormat;
2239 array_format->add_column
2240 (InternalName::get_vertex(), vertex_pool->get_num_dimensions(),
2241 Geom::NT_stdfloat, Geom::C_point);
2242
2243 if (vertex_pool->has_normals()) {
2244 array_format->add_column
2245 (InternalName::get_normal(), 3,
2246 Geom::NT_stdfloat, Geom::C_normal);
2247 }
2248
2249 if (!ignore_color) {
2250 // Let's not use Direct3D-style colors on platforms where we only have
2251 // OpenGL anyway.
2252#ifdef _WIN32
2253 array_format->add_column(InternalName::get_color(), 1,
2254 Geom::NT_packed_dabc, Geom::C_color);
2255#else
2256 array_format->add_column(InternalName::get_color(), 4,
2257 Geom::NT_uint8, Geom::C_color);
2258#endif
2259 }
2260
2261 vector_string uv_names, uvw_names, tbn_names;
2262 vertex_pool->get_uv_names(uv_names, uvw_names, tbn_names);
2263 vector_string::const_iterator ni;
2264 for (ni = uv_names.begin(); ni != uv_names.end(); ++ni) {
2265 string name = (*ni);
2266
2267 PT(InternalName) iname = InternalName::get_texcoord_name(name);
2268
2269 if (find(uvw_names.begin(), uvw_names.end(), name) != uvw_names.end()) {
2270 // This one actually represents 3-d texture coordinates.
2271 array_format->add_column
2272 (iname, 3, Geom::NT_stdfloat, Geom::C_texcoord);
2273 } else {
2274 array_format->add_column
2275 (iname, 2, Geom::NT_stdfloat, Geom::C_texcoord);
2276 }
2277 }
2278 for (ni = tbn_names.begin(); ni != tbn_names.end(); ++ni) {
2279 string name = (*ni);
2280
2281 PT(InternalName) iname_t = InternalName::get_tangent_name(name);
2282 PT(InternalName) iname_b = InternalName::get_binormal_name(name);
2283
2284 array_format->add_column
2285 (iname_t, 3, Geom::NT_stdfloat, Geom::C_vector);
2286 array_format->add_column
2287 (iname_b, 3, Geom::NT_stdfloat, Geom::C_vector);
2288 }
2289
2290 vector_string aux_names;
2291 vertex_pool->get_aux_names(aux_names);
2292 for (ni = aux_names.begin(); ni != aux_names.end(); ++ni) {
2293 string name = (*ni);
2294 PT(InternalName) iname = InternalName::make(name);
2295 array_format->add_column
2296 (iname, 4, Geom::NT_stdfloat, Geom::C_other);
2297 }
2298
2299 PT(GeomVertexFormat) temp_format = new GeomVertexFormat(array_format);
2300
2301 PT(SliderTable) slider_table;
2302 string name = _data->get_egg_filename().get_basename_wo_extension();
2303
2304 if (is_dynamic) {
2305 // If it's a dynamic object, we need a TransformBlendTable and maybe a
2306 // SliderTable, and additional columns in the vertex data: one that
2307 // indexes into the blend table per vertex, and also one for each
2308 // different type of morph delta.
2309
2310 // Tell the format that we're setting it up for Panda-based animation.
2311 GeomVertexAnimationSpec animation;
2312 animation.set_panda();
2313 temp_format->set_animation(animation);
2314
2315 PT(GeomVertexArrayFormat) anim_array_format = new GeomVertexArrayFormat;
2316 anim_array_format->add_column
2317 (InternalName::get_transform_blend(), 1,
2318 Geom::NT_uint16, Geom::C_index, 0, 2);
2319 temp_format->add_array(anim_array_format);
2320
2321 pmap<string, BitArray> slider_names;
2322 EggVertexPool::const_iterator vi;
2323 for (vi = vertex_pool->begin(); vi != vertex_pool->end(); ++vi) {
2324 EggVertex *vertex = (*vi);
2325
2326 EggMorphVertexList::const_iterator mvi;
2327 for (mvi = vertex->_dxyzs.begin(); mvi != vertex->_dxyzs.end(); ++mvi) {
2328 slider_names[(*mvi).get_name()].set_bit(vertex->get_index());
2329 record_morph(anim_array_format, character_maker, (*mvi).get_name(),
2330 InternalName::get_vertex(), 3);
2331 }
2332 if (vertex->has_normal()) {
2333 EggMorphNormalList::const_iterator mni;
2334 for (mni = vertex->_dnormals.begin(); mni != vertex->_dnormals.end(); ++mni) {
2335 slider_names[(*mni).get_name()].set_bit(vertex->get_index());
2336 record_morph(anim_array_format, character_maker, (*mni).get_name(),
2337 InternalName::get_normal(), 3);
2338 }
2339 }
2340 if (!ignore_color && vertex->has_color()) {
2341 EggMorphColorList::const_iterator mci;
2342 for (mci = vertex->_drgbas.begin(); mci != vertex->_drgbas.end(); ++mci) {
2343 slider_names[(*mci).get_name()].set_bit(vertex->get_index());
2344 record_morph(anim_array_format, character_maker, (*mci).get_name(),
2345 InternalName::get_color(), 4);
2346 }
2347 }
2348 EggVertex::const_uv_iterator uvi;
2349 for (uvi = vertex->uv_begin(); uvi != vertex->uv_end(); ++uvi) {
2350 EggVertexUV *egg_uv = (*uvi);
2351 string name = egg_uv->get_name();
2352 bool has_w = (find(uvw_names.begin(), uvw_names.end(), name) != uvw_names.end());
2353 PT(InternalName) iname = InternalName::get_texcoord_name(name);
2354
2355 EggMorphTexCoordList::const_iterator mti;
2356 for (mti = egg_uv->_duvs.begin(); mti != egg_uv->_duvs.end(); ++mti) {
2357 slider_names[(*mti).get_name()].set_bit(vertex->get_index());
2358 record_morph(anim_array_format, character_maker, (*mti).get_name(),
2359 iname, has_w ? 3 : 2);
2360 }
2361 }
2362 }
2363
2364 if (!slider_names.empty()) {
2365 // If we have any sliders at all, create a table for them.
2366
2367 slider_table = new SliderTable;
2368
2369 pmap<string, BitArray>::iterator si;
2370 for (si = slider_names.begin(); si != slider_names.end(); ++si) {
2371 PT(VertexSlider) slider = character_maker->egg_to_slider((*si).first);
2372 slider_table->add_slider(slider, (*si).second);
2373 }
2374 }
2375
2376 // We'll also assign the character name to the vertex data, so it will
2377 // show up in PStats.
2378 name = character_maker->get_name();
2379 }
2380
2381 temp_format->maybe_align_columns_for_animation();
2382
2383 CPT(GeomVertexFormat) format =
2384 GeomVertexFormat::register_format(temp_format);
2385
2386 // Now create a new GeomVertexData using the indicated format. It is
2387 // actually correct to create it with UH_static even though it represents a
2388 // dynamic object, because the vertex data itself won't be changing--just
2389 // the result of applying the animation is dynamic.
2390 PT(GeomVertexData) vertex_data =
2391 new GeomVertexData(name, format, Geom::UH_static);
2392 vertex_data->reserve_num_rows(vertex_pool->size());
2393
2394 vertex_data->set_transform_blend_table(blend_table);
2395 if (slider_table != nullptr) {
2396 vertex_data->set_slider_table(SliderTable::register_table(slider_table));
2397 }
2398
2399 // And fill in the data from the vertex pool.
2400 EggVertexPool::const_iterator vi;
2401 for (vi = vertex_pool->begin(); vi != vertex_pool->end(); ++vi) {
2402 GeomVertexWriter gvw(vertex_data);
2403 EggVertex *vertex = (*vi);
2404 gvw.set_row(vertex->get_index());
2405
2406 gvw.set_column(InternalName::get_vertex());
2407 gvw.add_data4d(vertex->get_pos4() * transform);
2408
2409 if (is_dynamic) {
2410 EggMorphVertexList::const_iterator mvi;
2411 for (mvi = vertex->_dxyzs.begin(); mvi != vertex->_dxyzs.end(); ++mvi) {
2412 const EggMorphVertex &morph = (*mvi);
2413 CPT(InternalName) delta_name =
2414 InternalName::get_morph(InternalName::get_vertex(), morph.get_name());
2415 gvw.set_column(delta_name);
2416 gvw.add_data3d(morph.get_offset() * transform);
2417 }
2418 }
2419
2420 if (vertex->has_normal()) {
2421 gvw.set_column(InternalName::get_normal());
2422 LNormald orig_normal = vertex->get_normal();
2423 LNormald transformed_normal = normalize(orig_normal * transform);
2424 gvw.add_data3d(transformed_normal);
2425
2426 if (is_dynamic) {
2427 EggMorphNormalList::const_iterator mni;
2428 for (mni = vertex->_dnormals.begin(); mni != vertex->_dnormals.end(); ++mni) {
2429 const EggMorphNormal &morph = (*mni);
2430 CPT(InternalName) delta_name =
2431 InternalName::get_morph(InternalName::get_normal(), morph.get_name());
2432 gvw.set_column(delta_name);
2433 LNormald morphed_normal = orig_normal + morph.get_offset();
2434 LNormald transformed_morphed_normal = normalize(morphed_normal * transform);
2435 LVector3d delta = transformed_morphed_normal - transformed_normal;
2436 gvw.add_data3d(delta);
2437 }
2438 }
2439 }
2440
2441 if (!ignore_color && vertex->has_color()) {
2442 gvw.set_column(InternalName::get_color());
2443 gvw.add_data4(vertex->get_color());
2444
2445 if (is_dynamic) {
2446 EggMorphColorList::const_iterator mci;
2447 for (mci = vertex->_drgbas.begin(); mci != vertex->_drgbas.end(); ++mci) {
2448 const EggMorphColor &morph = (*mci);
2449 CPT(InternalName) delta_name =
2450 InternalName::get_morph(InternalName::get_color(), morph.get_name());
2451 gvw.set_column(delta_name);
2452 gvw.add_data4(morph.get_offset());
2453 }
2454 }
2455 }
2456
2457 EggVertex::const_uv_iterator uvi;
2458 for (uvi = vertex->uv_begin(); uvi != vertex->uv_end(); ++uvi) {
2459 EggVertexUV *egg_uv = (*uvi);
2460 LTexCoord3d orig_uvw = egg_uv->get_uvw();
2461 LTexCoord3d uvw = egg_uv->get_uvw();
2462
2463 string name = egg_uv->get_name();
2464 PT(InternalName) iname = InternalName::get_texcoord_name(name);
2465 gvw.set_column(iname);
2466
2467 BakeInUVs::const_iterator buv = render_state->_bake_in_uvs.find(iname);
2468 if (buv != render_state->_bake_in_uvs.end()) {
2469 // If we are to bake in a texture matrix, do so now.
2470 uvw = uvw * (*buv).second->get_transform3d();
2471 }
2472
2473 gvw.set_data3d(uvw);
2474
2475 if (is_dynamic) {
2476 EggMorphTexCoordList::const_iterator mti;
2477 for (mti = egg_uv->_duvs.begin(); mti != egg_uv->_duvs.end(); ++mti) {
2478 const EggMorphTexCoord &morph = (*mti);
2479 CPT(InternalName) delta_name =
2480 InternalName::get_morph(iname, morph.get_name());
2481 gvw.set_column(delta_name);
2482 LTexCoord3d duvw = morph.get_offset();
2483 if (buv != render_state->_bake_in_uvs.end()) {
2484 LTexCoord3d new_uvw = orig_uvw + duvw;
2485 duvw = (new_uvw * (*buv).second->get_transform3d()) - uvw;
2486 }
2487
2488 gvw.add_data3d(duvw);
2489 }
2490 }
2491
2492 // Also add the tangent and binormal, if present.
2493 if (egg_uv->has_tangent() && egg_uv->has_binormal()) {
2494 PT(InternalName) iname = InternalName::get_tangent_name(name);
2495 gvw.set_column(iname);
2496 if (gvw.has_column()) {
2497 LVector3d tangent = egg_uv->get_tangent();
2498 LVector3d binormal = egg_uv->get_binormal();
2499 gvw.add_data3d(tangent);
2500 gvw.set_column(InternalName::get_binormal_name(name));
2501 gvw.add_data3d(binormal);
2502 }
2503 }
2504 }
2505
2506 EggVertex::const_aux_iterator auxi;
2507 for (auxi = vertex->aux_begin(); auxi != vertex->aux_end(); ++auxi) {
2508 EggVertexAux *egg_aux = (*auxi);
2509 LVecBase4d aux = egg_aux->get_aux();
2510
2511 string name = egg_aux->get_name();
2512 PT(InternalName) iname = InternalName::make(name);
2513 gvw.set_column(iname);
2514
2515 gvw.set_data4d(aux);
2516 }
2517
2518 if (is_dynamic) {
2519 int table_index = vertex->get_external_index();
2520 gvw.set_column(InternalName::get_transform_blend());
2521 gvw.set_data1i(table_index);
2522 }
2523 }
2524
2525 bool inserted = _vertex_pool_data.insert
2526 (VertexPoolData::value_type(vpt, vertex_data)).second;
2527 nassertr(inserted, vertex_data);
2528
2530 return vertex_data;
2531}
2532
2533/**
2534 *
2535 */
2536PT(TransformBlendTable) EggLoader::
2537make_blend_table(EggVertexPool *vertex_pool, EggNode *primitive_home,
2538 CharacterMaker *character_maker) {
2539 PT(TransformBlendTable) blend_table;
2540 blend_table = new TransformBlendTable;
2541 blend_table->set_rows(SparseArray::lower_on(vertex_pool->size()));
2542
2543 EggVertexPool::const_iterator vi;
2544 for (vi = vertex_pool->begin(); vi != vertex_pool->end(); ++vi) {
2545 EggVertex *vertex = (*vi);
2546
2547 // Figure out the transforms affecting this particular vertex.
2548 TransformBlend blend;
2549 if (vertex->gref_size() == 0) {
2550 // If the vertex has no explicit membership, it belongs right where it
2551 // is.
2552 PT(VertexTransform) vt = character_maker->egg_to_transform(primitive_home);
2553 nassertr(vt != nullptr, nullptr);
2554 blend.add_transform(vt, 1.0f);
2555 } else {
2556 // If the vertex does have an explicit membership, ignore its parentage
2557 // and assign it where it wants to be.
2558 double quantize = egg_vertex_membership_quantize;
2559 EggVertex::GroupRef::const_iterator gri;
2560 for (gri = vertex->gref_begin(); gri != vertex->gref_end(); ++gri) {
2561 EggGroup *egg_joint = (*gri);
2562 double membership = egg_joint->get_vertex_membership(vertex);
2563 if (quantize != 0.0) {
2564 membership = cfloor(membership / quantize + 0.5) * quantize;
2565 }
2566
2567 PT(VertexTransform) vt = character_maker->egg_to_transform(egg_joint);
2568 nassertr(vt != nullptr, nullptr);
2569 blend.add_transform(vt, membership);
2570 }
2571 }
2572 if (egg_vertex_max_num_joints >= 0) {
2573 blend.limit_transforms(egg_vertex_max_num_joints);
2574 }
2575 blend.normalize_weights();
2576
2577 int table_index = blend_table->add_blend(blend);
2578
2579 // We take advantage of the "external index" field of the EggVertex to
2580 // temporarily store the transform blend index.
2581 vertex->set_external_index(table_index);
2582 }
2583
2584 return blend_table;
2585}
2586
2587/**
2588 *
2589 */
2590void EggLoader::
2591record_morph(GeomVertexArrayFormat *array_format,
2592 CharacterMaker *character_maker,
2593 const string &morph_name, InternalName *column_name,
2594 int num_components) {
2595 PT(InternalName) delta_name =
2596 InternalName::get_morph(column_name, morph_name);
2597 if (!array_format->has_column(delta_name)) {
2598 array_format->add_column
2599 (delta_name, num_components,
2600 Geom::NT_stdfloat, Geom::C_morph_delta);
2601 }
2602}
2603
2604/**
2605 * Creates a GeomPrimitive corresponding to the indicated EggPrimitive, and
2606 * adds it to the set.
2607 */
2608void EggLoader::
2609make_primitive(const EggRenderState *render_state, EggPrimitive *egg_prim,
2610 EggLoader::UniquePrimitives &unique_primitives,
2611 EggLoader::Primitives &primitives,
2612 bool has_overall_color, const LColor &overall_color) {
2613 PT(GeomPrimitive) primitive;
2614 if (egg_prim->is_of_type(EggPolygon::get_class_type())) {
2615 if (egg_prim->size() == 3) {
2616 primitive = new GeomTriangles(Geom::UH_static);
2617 }
2618
2619 } else if (egg_prim->is_of_type(EggTriangleStrip::get_class_type())) {
2620 primitive = new GeomTristrips(Geom::UH_static);
2621
2622 } else if (egg_prim->is_of_type(EggTriangleFan::get_class_type())) {
2623 primitive = new GeomTrifans(Geom::UH_static);
2624
2625 } else if (egg_prim->is_of_type(EggLine::get_class_type())) {
2626 if (egg_prim->size() == 2) {
2627 primitive = new GeomLines(Geom::UH_static);
2628 } else {
2629 primitive = new GeomLinestrips(Geom::UH_static);
2630 }
2631
2632 } else if (egg_prim->is_of_type(EggPoint::get_class_type())) {
2633 primitive = new GeomPoints(Geom::UH_static);
2634
2635 } else if (egg_prim->is_of_type(EggPatch::get_class_type())) {
2636 int num_vertices = egg_prim->size();
2637 primitive = new GeomPatches(num_vertices, Geom::UH_static);
2638 }
2639
2640 if (primitive == nullptr) {
2641 // Don't know how to make this kind of primitive.
2642 egg2pg_cat.warning()
2643 << "Ignoring " << egg_prim->get_type() << "\n";
2644 return;
2645 }
2646
2647 if (render_state->_flat_shaded) {
2648 primitive->set_shade_model(GeomPrimitive::SM_flat_first_vertex);
2649
2650 } else if (egg_prim->get_shading() == EggPrimitive::S_overall) {
2651 primitive->set_shade_model(GeomPrimitive::SM_uniform);
2652
2653 } else {
2654 primitive->set_shade_model(GeomPrimitive::SM_smooth);
2655 }
2656
2657 // Insert the primitive into the set, but if we already have a primitive of
2658 // that type, reset the pointer to that one instead.
2659 PrimitiveUnifier pu(primitive);
2660 std::pair<UniquePrimitives::iterator, bool> result =
2661 unique_primitives.insert(UniquePrimitives::value_type(pu, primitive));
2662
2663 if (result.second) {
2664 // This was the first primitive of this type. Store it.
2665 primitives.push_back(primitive);
2666
2667 if (egg2pg_cat.is_debug()) {
2668 egg2pg_cat.debug()
2669 << "First primitive of type " << primitive->get_type()
2670 << ": " << primitive << "\n";
2671 }
2672 }
2673
2674 GeomPrimitive *orig_prim = (*result.first).second;
2675
2676 // Make sure we don't try to put more than egg_max_indices into any one
2677 // GeomPrimitive.
2678 if (orig_prim->get_num_vertices() + egg_prim->size() <= (unsigned int)egg_max_indices) {
2679 primitive = orig_prim;
2680
2681 } else if (orig_prim != primitive) {
2682 // If the old primitive is full, keep the new primitive from now on.
2683 (*result.first).second = primitive;
2684
2685 if (egg2pg_cat.is_debug()) {
2686 egg2pg_cat.debug()
2687 << "Next primitive of type " << primitive->get_type()
2688 << ": " << primitive << "\n";
2689 }
2690 primitives.push_back(primitive);
2691 }
2692
2693 // Now add the vertices.
2694 EggPrimitive::const_iterator vi;
2695 for (vi = egg_prim->begin(); vi != egg_prim->end(); ++vi) {
2696 primitive->add_vertex((*vi)->get_index());
2697 }
2698 primitive->close_primitive();
2699}
2700
2701/**
2702 * Defines the PortalNode from the first polygon found within this group.
2703 */
2704void EggLoader::
2705set_portal_polygon(EggGroup *egg_group, PortalNode *pnode) {
2706 pnode->clear_vertices();
2707
2708 PT(EggPolygon) poly = find_first_polygon(egg_group);
2709 if (poly != nullptr) {
2710 LMatrix4d mat = poly->get_vertex_to_node();
2711
2712 EggPolygon::const_iterator vi;
2713 for (vi = poly->begin(); vi != poly->end(); ++vi) {
2714 LVertexd vert = (*vi)->get_pos3() * mat;
2715 pnode->add_vertex(LCAST(PN_stdfloat, vert));
2716 }
2717 }
2718}
2719
2720/**
2721 * Defines the OccluderNode from the first polygon found within this group.
2722 */
2723void EggLoader::
2724set_occluder_polygon(EggGroup *egg_group, OccluderNode *pnode) {
2725 PT(EggPolygon) poly = find_first_polygon(egg_group);
2726 if (poly != nullptr) {
2727 if (poly->size() != 4) {
2728 egg2pg_cat.error()
2729 << "Invalid number of vertices for " << egg_group->get_name() << "\n";
2730 } else {
2731 LMatrix4d mat = poly->get_vertex_to_node();
2732
2733 LPoint3d v0 = (*poly)[0]->get_pos3() * mat;
2734 LPoint3d v1 = (*poly)[1]->get_pos3() * mat;
2735 LPoint3d v2 = (*poly)[2]->get_pos3() * mat;
2736 LPoint3d v3 = (*poly)[3]->get_pos3() * mat;
2737 pnode->set_vertices(LCAST(PN_stdfloat, v0),
2738 LCAST(PN_stdfloat, v1),
2739 LCAST(PN_stdfloat, v2),
2740 LCAST(PN_stdfloat, v3));
2741
2742 if (poly->get_bface_flag()) {
2743 pnode->set_double_sided(true);
2744 }
2745 }
2746 }
2747}
2748
2749/**
2750 * Returns the first EggPolygon found at or below the indicated node.
2751 */
2752PT(EggPolygon) EggLoader::
2753find_first_polygon(EggGroup *egg_group) {
2754 // Does this group have any polygons?
2755 EggGroup::const_iterator ci;
2756 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
2757 if ((*ci)->is_of_type(EggPolygon::get_class_type())) {
2758 // Yes! Return the polygon.
2759 return DCAST(EggPolygon, (*ci));
2760 }
2761 }
2762
2763 // Well, the group had no polygons; look for a child group that does.
2764 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
2765 if ((*ci)->is_of_type(EggGroup::get_class_type())) {
2766 EggGroup *child_group = DCAST(EggGroup, *ci);
2767 PT(EggPolygon) found = find_first_polygon(child_group);
2768 if (found != nullptr) {
2769 return found;
2770 }
2771 }
2772 }
2773
2774 // We got nothing.
2775 return nullptr;
2776}
2777
2778/**
2779 * Creates a single generic Sphere corresponding to the polygons associated
2780 * with this group. This sphere is used by make_collision_sphere and
2781 * Polylight sphere. It could be used for other spheres.
2782 */
2783bool EggLoader::
2784make_sphere(EggGroup *egg_group, EggGroup::CollideFlags flags,
2785 LPoint3 &center, PN_stdfloat &radius, LColor &color) {
2786 EggGroup *geom_group = find_collision_geometry(egg_group, flags);
2787 if (geom_group != nullptr) {
2788 // Collect all of the vertices.
2789 pset<EggVertex *> vertices;
2790
2791 EggGroup::const_iterator ci;
2792 for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) {
2793 if ((*ci)->is_of_type(EggPrimitive::get_class_type())) {
2794 EggPrimitive *prim = DCAST(EggPrimitive, *ci);
2795 EggPrimitive::const_iterator pi;
2796 for (pi = prim->begin(); pi != prim->end(); ++pi) {
2797 vertices.insert(*pi);
2798 }
2799 }
2800 }
2801
2802 // Now average together all of the vertices to get a center.
2803 int num_vertices = 0;
2804 LPoint3d d_center(0.0, 0.0, 0.0);
2805 pset<EggVertex *>::const_iterator vi;
2806
2807 for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
2808 EggVertex *vtx = (*vi);
2809 d_center += vtx->get_pos3();
2810 num_vertices++;
2811 }
2812
2813 if (num_vertices > 0) {
2814 d_center /= (double)num_vertices;
2815 // egg2pg_cat.debug() << "make_sphere d_center: " << d_center << "\n";
2816
2817 // And the furthest vertex determines the radius.
2818 double radius2 = 0.0;
2819 for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
2820 EggVertex *vtx = (*vi);
2821 LPoint3d p3 = vtx->get_pos3();
2822 LVector3d v = p3 - d_center;
2823 radius2 = max(radius2, v.length_squared());
2824 }
2825
2826 center = LCAST(PN_stdfloat, d_center);
2827 radius = sqrtf(radius2);
2828
2829 // egg2pg_cat.debug() << "make_sphere radius: " << radius << "\n";
2830 vi = vertices.begin();
2831 EggVertex *clr_vtx = (*vi);
2832 color = clr_vtx->get_color();
2833 return true;
2834 }
2835 }
2836 return false;
2837}
2838
2839/**
2840 * Creates a single generic Box corresponding to the polygons associated with
2841 * this group. This box is used by make_collision_box.
2842 */
2843bool EggLoader::
2844make_box(EggGroup *egg_group, EggGroup::CollideFlags flags,
2845 const LMatrix4 &xform, LPoint3 &min_p, LPoint3 &max_p) {
2846 EggGroup *geom_group = find_collision_geometry(egg_group, flags);
2847 if (geom_group != nullptr) {
2848 // Collect all of the vertices.
2849 pset<EggVertex *> vertices;
2850
2851 EggGroup::const_iterator ci;
2852 for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) {
2853 if ((*ci)->is_of_type(EggPrimitive::get_class_type())) {
2854 EggPrimitive *prim = DCAST(EggPrimitive, *ci);
2855 EggPrimitive::const_iterator pi;
2856 for (pi = prim->begin(); pi != prim->end(); ++pi) {
2857 vertices.insert(*pi);
2858 }
2859 }
2860 }
2861
2862 // Now find the minmax points
2863 pset<EggVertex *>::const_iterator vi;
2864 vi = vertices.begin();
2865
2866 if (vi == vertices.end()) {
2867 // No vertices, no bounding box.
2868 min_p.set(0, 0, 0);
2869 max_p.set(0, 0, 0);
2870 return false;
2871 }
2872
2873 EggVertex *vertex = (*vi);
2874 LPoint3 min_pd = LCAST(PN_stdfloat, vertex->get_pos3()) * xform;
2875 LPoint3 max_pd = min_pd;
2876
2877 for (++vi; vi != vertices.end(); ++vi) {
2878 vertex = (*vi);
2879 LPoint3 pos = LCAST(PN_stdfloat, vertex->get_pos3()) * xform;
2880 min_pd.set(min(min_pd[0], pos[0]),
2881 min(min_pd[1], pos[1]),
2882 min(min_pd[2], pos[2]));
2883 max_pd.set(max(max_pd[0], pos[0]),
2884 max(max_pd[1], pos[1]),
2885 max(max_pd[2], pos[2]));
2886 }
2887
2888 min_p = min_pd;
2889 max_p = max_pd;
2890 return (min_pd != max_pd);
2891 }
2892 return false;
2893}
2894
2895/**
2896 * Creates a single generic Box corresponding to the polygons associated with
2897 * this group. This box is used by make_collision_box.
2898 */
2899bool EggLoader::
2900make_box(EggGroup *egg_group, EggGroup::CollideFlags flags,
2901 LPoint3 &min_p, LPoint3 &max_p, LColor &color) {
2902
2903 color.set(1.0, 1.0, 1.0, 1.0);
2904 return make_box(egg_group, flags, LMatrix4::ident_mat(), min_p, max_p);
2905}
2906
2907/**
2908 * Creates CollisionSolids corresponding to the collision geometry indicated
2909 * at the given node and below.
2910 */
2911void EggLoader::
2912make_collision_solids(EggGroup *start_group, EggGroup *egg_group,
2913 CollisionNode *cnode) {
2914 if (egg_group->get_cs_type() != EggGroup::CST_none) {
2915 start_group = egg_group;
2916 }
2917
2918 switch (start_group->get_cs_type()) {
2919 case EggGroup::CST_none:
2920 // No collision flags; do nothing. Don't even traverse further.
2921 return;
2922
2923 case EggGroup::CST_plane:
2924 make_collision_plane(egg_group, cnode, start_group->get_collide_flags());
2925 break;
2926
2927 case EggGroup::CST_polygon:
2928 make_collision_polygon(egg_group, cnode, start_group->get_collide_flags());
2929 break;
2930
2931 case EggGroup::CST_polyset:
2932 make_collision_polyset(egg_group, cnode, start_group->get_collide_flags());
2933 break;
2934
2935 case EggGroup::CST_sphere:
2936 make_collision_sphere(egg_group, cnode, start_group->get_collide_flags());
2937 break;
2938
2939 case EggGroup::CST_box:
2940 make_collision_box(egg_group, cnode, start_group->get_collide_flags());
2941 break;
2942
2943 case EggGroup::CST_inv_sphere:
2944 make_collision_inv_sphere(egg_group, cnode, start_group->get_collide_flags());
2945 break;
2946
2947 case EggGroup::CST_tube:
2948 make_collision_capsule(egg_group, cnode, start_group->get_collide_flags());
2949 break;
2950
2951 case EggGroup::CST_floor_mesh:
2952 make_collision_floor_mesh(egg_group, cnode, start_group->get_collide_flags());
2953 break;
2954 }
2955
2956 if ((start_group->get_collide_flags() & EggGroup::CF_descend) != 0) {
2957 // Now pick up everything below.
2958 EggGroup::const_iterator ci;
2959 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
2960 if ((*ci)->is_of_type(EggGroup::get_class_type())) {
2961 make_collision_solids(start_group, DCAST(EggGroup, *ci), cnode);
2962 }
2963 }
2964 } else {
2965 egg2pg_cat.warning()
2966 << "Using <Collide> without 'descend' is deprecated. 'descend' "
2967 << "will become the default in a future version of Panda3D.\n";
2968 }
2969}
2970
2971/**
2972 * Creates a single CollisionPlane corresponding to the first polygon
2973 * associated with this group.
2974 */
2975void EggLoader::
2976make_collision_plane(EggGroup *egg_group, CollisionNode *cnode,
2977 EggGroup::CollideFlags flags) {
2978 EggGroup *geom_group = find_collision_geometry(egg_group, flags);
2979 if (geom_group != nullptr) {
2980 EggGroup::const_iterator ci;
2981 for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) {
2982 if ((*ci)->is_of_type(EggPolygon::get_class_type())) {
2983 CollisionPlane *csplane =
2984 create_collision_plane(DCAST(EggPolygon, *ci), egg_group);
2985 if (csplane != nullptr) {
2986 apply_collision_flags(csplane, flags);
2987 csplane->xform(cnode->get_transform()->get_mat());
2988 cnode->add_solid(csplane);
2989 return;
2990 }
2991 } else if ((*ci)->is_of_type(EggCompositePrimitive::get_class_type()) &&
2992 !(*ci)->is_of_type(EggLine::get_class_type())) {
2993 EggCompositePrimitive *comp = DCAST(EggCompositePrimitive, *ci);
2994 PT(EggGroup) temp_group = new EggGroup;
2995 if (comp->triangulate_into(temp_group)) {
2996 make_collision_plane(temp_group, cnode, flags);
2997 return;
2998 }
2999 }
3000 }
3001 }
3002}
3003
3004
3005
3006/**
3007 * Creates a single CollisionPolygon corresponding to the first polygon
3008 * associated with this group.
3009 */
3010void EggLoader::
3011make_collision_floor_mesh(EggGroup *egg_group, CollisionNode *cnode,
3012 EggGroup::CollideFlags flags) {
3013
3014 EggGroup *geom_group = find_collision_geometry(egg_group, flags);
3015
3016
3017 if (geom_group != nullptr) {
3018 create_collision_floor_mesh(cnode, geom_group,flags);
3019 }
3020}
3021
3022/**
3023 * Creates a single CollisionPolygon corresponding to the first polygon
3024 * associated with this group.
3025 */
3026void EggLoader::
3027make_collision_polygon(EggGroup *egg_group, CollisionNode *cnode,
3028 EggGroup::CollideFlags flags) {
3029
3030 EggGroup *geom_group = find_collision_geometry(egg_group, flags);
3031 if (geom_group != nullptr) {
3032 EggGroup::const_iterator ci;
3033 for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) {
3034 if ((*ci)->is_of_type(EggPolygon::get_class_type())) {
3035 create_collision_polygons(cnode, DCAST(EggPolygon, *ci),
3036 egg_group, flags);
3037 } else if ((*ci)->is_of_type(EggCompositePrimitive::get_class_type())) {
3038 EggCompositePrimitive *comp = DCAST(EggCompositePrimitive, *ci);
3039 PT(EggGroup) temp_group = new EggGroup;
3040 if (comp->triangulate_into(temp_group)) {
3041 make_collision_polygon(temp_group, cnode, flags);
3042 return;
3043 }
3044 }
3045 }
3046 }
3047}
3048
3049
3050/**
3051 * Creates a series of CollisionPolygons corresponding to the polygons
3052 * associated with this group.
3053 */
3054void EggLoader::
3055make_collision_polyset(EggGroup *egg_group, CollisionNode *cnode,
3056 EggGroup::CollideFlags flags) {
3057 EggGroup *geom_group = find_collision_geometry(egg_group, flags);
3058 if (geom_group != nullptr) {
3059 EggGroup::const_iterator ci;
3060 for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) {
3061 if ((*ci)->is_of_type(EggPolygon::get_class_type())) {
3062 create_collision_polygons(cnode, DCAST(EggPolygon, *ci),
3063 egg_group, flags);
3064 } else if ((*ci)->is_of_type(EggCompositePrimitive::get_class_type())) {
3065 EggCompositePrimitive *comp = DCAST(EggCompositePrimitive, *ci);
3066 PT(EggGroup) temp_group = new EggGroup;
3067 if (comp->triangulate_into(temp_group)) {
3068 make_collision_polyset(temp_group, cnode, flags);
3069 }
3070 }
3071 }
3072 }
3073}
3074
3075/**
3076 * Creates a single CollisionSphere corresponding to the polygons associated
3077 * with this group.
3078 */
3079void EggLoader::
3080make_collision_sphere(EggGroup *egg_group, CollisionNode *cnode,
3081 EggGroup::CollideFlags flags) {
3082 LPoint3 center;
3083 PN_stdfloat radius;
3084 LColor dummycolor;
3085 if (make_sphere(egg_group, flags, center, radius, dummycolor)) {
3086 CollisionSphere *cssphere =
3087 new CollisionSphere(center, radius);
3088 apply_collision_flags(cssphere, flags);
3089 cssphere->xform(cnode->get_transform()->get_mat());
3090 cnode->add_solid(cssphere);
3091 }
3092}
3093
3094/**
3095 * Creates a single CollisionBox corresponding to the polygons associated with
3096 * this group.
3097 */
3098void EggLoader::
3099make_collision_box(EggGroup *egg_group, CollisionNode *cnode,
3100 EggGroup::CollideFlags flags) {
3101 LPoint3 min_p;
3102 LPoint3 max_p;
3103 CPT(TransformState) transform = cnode->get_transform();
3104 if (make_box(egg_group, flags, transform->get_mat(), min_p, max_p)) {
3105 CollisionBox *csbox =
3106 new CollisionBox(min_p, max_p);
3107 apply_collision_flags(csbox, flags);
3108 cnode->add_solid(csbox);
3109 }
3110}
3111
3112/**
3113 * Creates a single CollisionInvSphere corresponding to the polygons
3114 * associated with this group.
3115 */
3116void EggLoader::
3117make_collision_inv_sphere(EggGroup *egg_group, CollisionNode *cnode,
3118 EggGroup::CollideFlags flags) {
3119 LPoint3 center;
3120 PN_stdfloat radius;
3121 LColor dummycolor;
3122 if (make_sphere(egg_group, flags, center, radius, dummycolor)) {
3123 CollisionInvSphere *cssphere =
3124 new CollisionInvSphere(center, radius);
3125 apply_collision_flags(cssphere, flags);
3126 cssphere->xform(cnode->get_transform()->get_mat());
3127 cnode->add_solid(cssphere);
3128 }
3129}
3130
3131/**
3132 * Creates a single CollisionCapsule corresponding to the polygons associated
3133 * with this group.
3134 */
3135void EggLoader::
3136make_collision_capsule(EggGroup *egg_group, CollisionNode *cnode,
3137 EggGroup::CollideFlags flags) {
3138 EggGroup *geom_group = find_collision_geometry(egg_group, flags);
3139 if (geom_group != nullptr) {
3140 // Collect all of the vertices.
3141 pset<EggVertex *> vertices;
3142
3143 EggGroup::const_iterator ci;
3144 for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) {
3145 if ((*ci)->is_of_type(EggPrimitive::get_class_type())) {
3146 EggPrimitive *prim = DCAST(EggPrimitive, *ci);
3147 EggPrimitive::const_iterator pi;
3148 for (pi = prim->begin(); pi != prim->end(); ++pi) {
3149 vertices.insert(*pi);
3150 }
3151 }
3152 }
3153
3154 // Now store the 3-d values in a vector for convenient access (and also
3155 // determine the centroid). We compute this in node space.
3156 size_t num_vertices = vertices.size();
3157 if (num_vertices != 0) {
3158 pvector<LPoint3d> vpos;
3159 vpos.reserve(num_vertices);
3160
3161 LPoint3d center(0.0, 0.0, 0.0);
3162 pset<EggVertex *>::const_iterator vi;
3163 for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
3164 EggVertex *vtx = (*vi);
3165 const LPoint3d &pos = vtx->get_pos3();
3166 vpos.push_back(pos);
3167 center += pos;
3168 }
3169 center /= (double)num_vertices;
3170
3171 // Now that we have the centroid, we have to try to figure out the
3172 // cylinder's major axis. Start by finding a point farthest from the
3173 // centroid.
3174 size_t i;
3175 double radius2 = 0.0;
3176 LPoint3d far_a = center;
3177 for (i = 0; i < num_vertices; i++) {
3178 double dist2 = (vpos[i] - center).length_squared();
3179 if (dist2 > radius2) {
3180 radius2 = dist2;
3181 far_a = vpos[i];
3182 }
3183 }
3184
3185 // The point we have found above, far_a, must be one one of the endcaps.
3186 // Now find another point, far_b, that is the farthest from far_a. This
3187 // will be a point on the other endcap.
3188 radius2 = 0.0;
3189 LPoint3d far_b = center;
3190 for (i = 0; i < num_vertices; i++) {
3191 double dist2 = (vpos[i] - far_a).length_squared();
3192 if (dist2 > radius2) {
3193 radius2 = dist2;
3194 far_b = vpos[i];
3195 }
3196 }
3197
3198 // Now we have far_a and far_b, one point on each endcap. However,
3199 // these points are not necessarily centered on the endcaps, so we
3200 // haven't figured out the cylinder's axis yet (the line between far_a
3201 // and far_b will probably pass through the cylinder at an angle).
3202
3203 // So we still need to determine the full set of points in each endcap.
3204 // To do this, we pass back through the set of points, categorizing each
3205 // point into either "endcap a" or "endcap b". We also leave a hefty
3206 // chunk of points in the middle uncategorized; this helps prevent us
3207 // from getting a little bit lopsided with points near the middle that
3208 // may appear to be closer to the wrong endcap.
3209 LPoint3d cap_a_center(0.0, 0.0, 0.0);
3210 LPoint3d cap_b_center(0.0, 0.0, 0.0);
3211 int num_a = 0;
3212 int num_b = 0;
3213
3214 // This is the threshold length; points farther away from the center
3215 // than this are deemed to be in one endcap or the other.
3216 double center_length = (far_a - far_b).length() / 4.0;
3217 double center_length2 = center_length * center_length;
3218
3219 for (i = 0; i < num_vertices; i++) {
3220 double dist2 = (vpos[i] - center).length_squared();
3221 if (dist2 > center_length2) {
3222 // This point is farther away from the center than center_length;
3223 // therefore it belongs in an endcap.
3224 double dist_a2 = (vpos[i] - far_a).length_squared();
3225 double dist_b2 = (vpos[i] - far_b).length_squared();
3226 if (dist_a2 < dist_b2) {
3227 // It's in endcap a.
3228 cap_a_center += vpos[i];
3229 num_a++;
3230 } else {
3231 // It's in endcap b.
3232 cap_b_center += vpos[i];
3233 num_b++;
3234 }
3235 }
3236 }
3237
3238 if (num_a > 0 && num_b > 0) {
3239 cap_a_center /= (double)num_a;
3240 cap_b_center /= (double)num_b;
3241
3242
3243 // Now we finally have the major axis of the cylinder.
3244 LVector3d axis = cap_b_center - cap_a_center;
3245 axis.normalize();
3246
3247 // If the axis is *almost* parallel with a major axis, assume it is
3248 // meant to be exactly parallel.
3249 if (IS_THRESHOLD_ZERO(axis[0], 0.01)) {
3250 axis[0] = 0.0;
3251 }
3252 if (IS_THRESHOLD_ZERO(axis[1], 0.01)) {
3253 axis[1] = 0.0;
3254 }
3255 if (IS_THRESHOLD_ZERO(axis[2], 0.01)) {
3256 axis[2] = 0.0;
3257 }
3258 axis.normalize();
3259
3260 // Transform all of the points so that the major axis is along the Y
3261 // axis, and the origin is the center. This is very similar to the
3262 // CollisionCapsule's idea of its canonical orientation (although not
3263 // exactly the same, since it is centered on the origin instead of
3264 // having point_a on the origin). It makes it easier to determine the
3265 // length and radius of the cylinder.
3266 LMatrix4d mat;
3267 look_at(mat, axis, LVector3d(0.0, 0.0, 1.0), CS_zup_right);
3268 mat.set_row(3, center);
3269 LMatrix4d inv_mat;
3270 inv_mat.invert_from(mat);
3271
3272 for (i = 0; i < num_vertices; i++) {
3273 vpos[i] = vpos[i] * inv_mat;
3274 }
3275
3276 double max_radius2 = 0.0;
3277
3278 // Now determine the radius.
3279 for (i = 0; i < num_vertices; i++) {
3280 LVector2d v(vpos[i][0], vpos[i][2]);
3281 double radius2 = v.length_squared();
3282 if (radius2 > max_radius2) {
3283 max_radius2 = radius2;
3284 }
3285 }
3286
3287 // And with the radius, we can determine the length. We need to know
3288 // the radius first because we want the round endcaps to enclose all
3289 // points.
3290 double min_y = 0.0;
3291 double max_y = 0.0;
3292
3293 for (i = 0; i < num_vertices; i++) {
3294 LVector2d v(vpos[i][0], vpos[i][2]);
3295 double radius2 = v.length_squared();
3296
3297 if (vpos[i][1] < min_y) {
3298 // Adjust the Y pos to account for the point's distance from the
3299 // axis.
3300 double factor = sqrt(max_radius2 - radius2);
3301 min_y = min(min_y, vpos[i][1] + factor);
3302
3303 } else if (vpos[i][1] > max_y) {
3304 double factor = sqrt(max_radius2 - radius2);
3305 max_y = max(max_y, vpos[i][1] - factor);
3306 }
3307 }
3308
3309 double length = max_y - min_y;
3310 double radius = sqrt(max_radius2);
3311
3312 // Finally, we have everything we need to define the cylinder.
3313 LVector3d half = axis * (length / 2.0);
3314 LPoint3d point_a = center - half;
3315 LPoint3d point_b = center + half;
3316
3317 CollisionCapsule *cscapsule =
3318 new CollisionCapsule(LCAST(PN_stdfloat, point_a), LCAST(PN_stdfloat, point_b),
3319 radius);
3320 apply_collision_flags(cscapsule, flags);
3321 cscapsule->xform(cnode->get_transform()->get_mat());
3322 cnode->add_solid(cscapsule);
3323 }
3324 }
3325 }
3326}
3327
3328/**
3329 * Does funny stuff to the CollisionSolid as appropriate, based on the
3330 * settings of the given CollideFlags.
3331 */
3332void EggLoader::
3333apply_collision_flags(CollisionSolid *solid, EggGroup::CollideFlags flags) {
3334 if ((flags & EggGroup::CF_intangible) != 0) {
3335 solid->set_tangible(false);
3336 }
3337 if ((flags & EggGroup::CF_level) != 0) {
3338 solid->set_effective_normal(LVector3::up());
3339 }
3340}
3341
3342/**
3343 * Looks for the node, at or below the indicated node, that contains the
3344 * associated collision geometry.
3345 */
3346EggGroup *EggLoader::
3347find_collision_geometry(EggGroup *egg_group, EggGroup::CollideFlags flags) {
3348 if ((flags & EggGroup::CF_descend) != 0) {
3349 // If we have the "descend" instruction, we'll get to it when we get to
3350 // it. Don't worry about it now.
3351 return egg_group;
3352 }
3353
3354 // Does this group have any polygons?
3355 EggGroup::const_iterator ci;
3356 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
3357 if ((*ci)->is_of_type(EggPolygon::get_class_type())) {
3358 // Yes! Use this group.
3359 return egg_group;
3360 }
3361 }
3362
3363 // Well, the group had no polygons; look for a child group that has the same
3364 // collision type.
3365 for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
3366 if ((*ci)->is_of_type(EggGroup::get_class_type())) {
3367 EggGroup *child_group = DCAST(EggGroup, *ci);
3368 if (child_group->get_cs_type() == egg_group->get_cs_type()) {
3369 return child_group;
3370 }
3371 }
3372 }
3373
3374 // We got nothing.
3375 return nullptr;
3376}
3377
3378/**
3379 * Creates a single CollisionPlane from the indicated EggPolygon.
3380 */
3381CollisionPlane *EggLoader::
3382create_collision_plane(EggPolygon *egg_poly, EggGroup *parent_group) {
3383 if (!egg_poly->cleanup()) {
3384 egg2pg_cat.info()
3385 << "Ignoring degenerate collision plane in " << parent_group->get_name()
3386 << "\n";
3387 return nullptr;
3388 }
3389
3390 if (!egg_poly->is_planar()) {
3391 egg2pg_cat.warning()
3392 << "Non-planar polygon defining collision plane in "
3393 << parent_group->get_name()
3394 << "\n";
3395 }
3396
3397 pvector<LVertex> vertices;
3398 if (!egg_poly->empty()) {
3399 EggPolygon::const_iterator vi;
3400 vi = egg_poly->begin();
3401
3402 LVertexd vert = (*vi)->get_pos3();
3403 vertices.push_back(LCAST(PN_stdfloat, vert));
3404
3405 LVertexd last_vert = vert;
3406 ++vi;
3407 while (vi != egg_poly->end()) {
3408 vert = (*vi)->get_pos3();
3409 if (!vert.almost_equal(last_vert)) {
3410 vertices.push_back(LCAST(PN_stdfloat, vert));
3411 }
3412
3413 last_vert = vert;
3414 ++vi;
3415 }
3416 }
3417
3418 if (vertices.size() < 3) {
3419 return nullptr;
3420 }
3421 LPlane plane(vertices[0], vertices[1], vertices[2]);
3422 return new CollisionPlane(plane);
3423}
3424
3425/**
3426 * Creates one or more CollisionPolygons from the indicated EggPolygon, and
3427 * adds them to the indicated CollisionNode.
3428 */
3429void EggLoader::
3430create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly,
3431 EggGroup *parent_group,
3432 EggGroup::CollideFlags flags) {
3433
3434 PT(EggGroup) group = new EggGroup;
3435
3436 if (!egg_poly->triangulate_into(group, false)) {
3437 egg2pg_cat.info()
3438 << "Ignoring degenerate collision polygon in "
3439 << parent_group->get_name()
3440 << "\n";
3441 return;
3442 }
3443
3444 if (group->size() != 1) {
3445 egg2pg_cat.info()
3446 << "Triangulating concave or non-planar collision polygon in "
3447 << parent_group->get_name()
3448 << "\n";
3449 }
3450
3451 EggGroup::iterator ci;
3452 for (ci = group->begin(); ci != group->end(); ++ci) {
3453 EggPolygon *poly = DCAST(EggPolygon, *ci);
3454
3455 pvector<LVertex> vertices;
3456 if (!poly->empty()) {
3457 EggPolygon::const_iterator vi;
3458 vi = poly->begin();
3459
3460 LVertexd vert = (*vi)->get_pos3();
3461 vertices.push_back(LCAST(PN_stdfloat, vert));
3462
3463 LVertexd last_vert = vert;
3464 ++vi;
3465 while (vi != poly->end()) {
3466 vert = (*vi)->get_pos3();
3467 if (!vert.almost_equal(last_vert)) {
3468 vertices.push_back(LCAST(PN_stdfloat, vert));
3469 }
3470
3471 last_vert = vert;
3472 ++vi;
3473 }
3474 }
3475
3476 if (vertices.size() >= 3) {
3477 const LVertex *vertices_begin = &vertices[0];
3478 const LVertex *vertices_end = vertices_begin + vertices.size();
3479 PT(CollisionPolygon) cspoly =
3480 new CollisionPolygon(vertices_begin, vertices_end);
3481 if (cspoly->is_valid()) {
3482 apply_collision_flags(cspoly, flags);
3483 cspoly->xform(cnode->get_transform()->get_mat());
3484 cnode->add_solid(cspoly);
3485 }
3486 }
3487 }
3488}
3489
3490/**
3491 * Creates a CollisionFloorMesh from the indicated EggPolygons, and adds it to
3492 * the indicated CollisionNode.
3493 */
3494void EggLoader::
3495create_collision_floor_mesh(CollisionNode *cnode,
3496 EggGroup *parent_group,
3497 EggGroup::CollideFlags flags) {
3498
3499 PT(EggGroup) group = new EggGroup;
3500 EggVertexPool pool("floorMesh");
3501 pool.local_object();
3502 EggGroup::const_iterator egi;
3503 for (egi = parent_group->begin(); egi != parent_group->end(); ++egi) {
3504 if ((*egi)->is_of_type(EggPolygon::get_class_type())) {
3505 EggPolygon * poly = DCAST(EggPolygon, *egi);
3506 if (!poly->triangulate_into(group, false)) {
3507 egg2pg_cat.info()
3508 << "Ignoring degenerate collision polygon in "
3509 << parent_group->get_name()
3510 << "\n";
3511 return;
3512 }
3513
3514 }
3515 }
3516 if(group->size() == 0) {
3517 egg2pg_cat.info()
3518 << "empty collision solid\n";
3519 return;
3520 }
3521 PT(CollisionFloorMesh) cm = new CollisionFloorMesh;
3522 pvector<CollisionFloorMesh::TriangleIndices> triangles;
3523
3524 EggGroup::iterator ci;
3525 for (ci = group->begin(); ci != group->end(); ++ci) {
3526 EggPolygon *poly = DCAST(EggPolygon, *ci);
3527 if (poly->get_num_vertices() == 3) {
3528 CollisionFloorMesh::TriangleIndices tri;
3529
3530 // generate a shared vertex triangle from the vertex pool
3531 tri.p1=pool.create_unique_vertex(*poly->get_vertex(0))->get_index();
3532 tri.p2=pool.create_unique_vertex(*poly->get_vertex(1))->get_index();
3533 tri.p3=pool.create_unique_vertex(*poly->get_vertex(2))->get_index();
3534
3535 triangles.push_back(tri);
3536 } else if (poly->get_num_vertices() == 4) {
3537 // this is a case that really shouldn't happen, but appears to be
3538 // required -split up the quad int 2 tris.
3539 CollisionFloorMesh::TriangleIndices tri;
3540 CollisionFloorMesh::TriangleIndices tri2;
3541
3542 // generate a shared vertex triangle from the vertex pool
3543 tri.p1=pool.create_unique_vertex(*poly->get_vertex(0))->get_index();
3544 tri.p2=pool.create_unique_vertex(*poly->get_vertex(1))->get_index();
3545 tri.p3=pool.create_unique_vertex(*poly->get_vertex(2))->get_index();
3546
3547 triangles.push_back(tri);
3548
3549 // generate a shared vertex triangle from the vertex pool
3550 tri2.p1=tri.p1;
3551 tri2.p2=tri.p3;
3552 tri2.p3=pool.create_unique_vertex(*poly->get_vertex(3))->get_index();
3553
3554
3555 triangles.push_back(tri2);
3556 }
3557 }
3558
3559 // Now we have a set of triangles, and a pool
3560 PT(CollisionFloorMesh) csfloor = new CollisionFloorMesh;
3561
3562
3563 EggVertexPool::const_iterator vi;
3564 for (vi = pool.begin(); vi != pool.end(); vi++) {
3565 csfloor->add_vertex(LCAST(PN_stdfloat,(*vi)->get_pos3()));
3566 }
3567
3568 pvector<CollisionFloorMesh::TriangleIndices>::iterator ti;
3569
3570 for (ti = triangles.begin(); ti != triangles.end(); ti++) {
3571 CollisionFloorMesh::TriangleIndices triangle = *ti;
3572 csfloor->add_triangle(triangle.p1, triangle.p2, triangle.p3);
3573 }
3574 csfloor->xform(cnode->get_transform()->get_mat());
3575 cnode->add_solid(csfloor);
3576}
3577
3578
3579/**
3580 * Walks back over the tree and applies the DeferredNodeProperties that were
3581 * saved up along the way.
3582 */
3583void EggLoader::
3584apply_deferred_nodes(PandaNode *node, const DeferredNodeProperty &prop) {
3585 DeferredNodeProperty next_prop(prop);
3586
3587 // Do we have a DeferredNodeProperty associated with this node?
3588 DeferredNodes::const_iterator dni;
3589 dni = _deferred_nodes.find(node);
3590
3591 if (dni != _deferred_nodes.end()) {
3592 const DeferredNodeProperty &def = (*dni).second;
3593 next_prop.compose(def);
3594 }
3595
3596 // Now apply the accumulated state to the node.
3597 next_prop.apply_to_node(node);
3598
3599 int num_children = node->get_num_children();
3600 for (int i = 0; i < num_children; i++) {
3601 apply_deferred_nodes(node->get_child(i), next_prop);
3602 }
3603}
3604
3605/**
3606 * Walks the hierarchy and calls expand_object_types() on each node, to expand
3607 * all of the ObjectType definitions in the file at once. Also prunes any
3608 * nodes that are flagged "backstage".
3609 *
3610 * The return value is true if this node should be kept, false if it should be
3611 * pruned.
3612 */
3613bool EggLoader::
3614expand_all_object_types(EggNode *egg_node) {
3615 if (egg_node->is_of_type(EggGroup::get_class_type())) {
3616 EggGroup *egg_group = DCAST(EggGroup, egg_node);
3617
3618 if (egg_group->get_num_object_types() != 0) {
3619 pset<string> expanded;
3620 pvector<string> expanded_history;
3621 if (!expand_object_types(egg_group, expanded, expanded_history)) {
3622 return false;
3623 }
3624 }
3625 }
3626
3627 // Now recurse on children, and we might prune children from this list as we
3628 // go.
3629 if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
3630 EggGroupNode *egg_group_node = DCAST(EggGroupNode, egg_node);
3631 EggGroupNode::const_iterator ci;
3632 ci = egg_group_node->begin();
3633 while (ci != egg_group_node->end()) {
3634 EggGroupNode::const_iterator cnext = ci;
3635 ++cnext;
3636
3637 if (!expand_all_object_types(*ci)) {
3638 // Prune this child.
3639 egg_group_node->erase(ci);
3640 }
3641 ci = cnext;
3642 }
3643 }
3644
3645 return true;
3646}
3647
3648/**
3649 * Recursively expands the group's ObjectType string(s). It's recursive
3650 * because an ObjectType string might itself expand to another ObjectType
3651 * string, which is allowed; but we don't want to get caught in a cycle.
3652 *
3653 * The return value is true if the object type is expanded and the node is
3654 * valid, or false if the node should be ignored (e.g. ObjectType
3655 * "backstage").
3656 */
3657bool EggLoader::
3658expand_object_types(EggGroup *egg_group, const pset<string> &expanded,
3659 const pvector<string> &expanded_history) {
3660 int num_object_types = egg_group->get_num_object_types();
3661
3662 // First, copy out the object types so we can recursively modify the list.
3663 vector_string object_types;
3664 int i;
3665 for (i = 0; i < num_object_types; i++) {
3666 object_types.push_back(egg_group->get_object_type(i));
3667 }
3668 egg_group->clear_object_types();
3669
3670 for (i = 0; i < num_object_types; i++) {
3671 string object_type = object_types[i];
3672 pset<string> new_expanded(expanded);
3673
3674 // Check for a cycle.
3675 if (!new_expanded.insert(object_type).second) {
3676 egg2pg_cat.error()
3677 << "Cycle in ObjectType expansions:\n";
3678 pvector<string>::const_iterator pi;
3679 for (pi = expanded_history.begin();
3680 pi != expanded_history.end();
3681 ++pi) {
3682 egg2pg_cat.error(false)
3683 << (*pi) << " -> ";
3684 }
3685 egg2pg_cat.error(false) << object_type << "\n";
3686 _error = true;
3687
3688 } else {
3689 // No cycle; continue.
3690 pvector<string> new_expanded_history(expanded_history);
3691 new_expanded_history.push_back(object_type);
3692
3693 if (!do_expand_object_type(egg_group, new_expanded,
3694 new_expanded_history, object_type)) {
3695 // Ignorable group; stop here.
3696 return false;
3697 }
3698 }
3699 }
3700
3701 return true;
3702}
3703
3704/**
3705 * Further implementation of expand_object_types().
3706 */
3707bool EggLoader::
3708do_expand_object_type(EggGroup *egg_group, const pset<string> &expanded,
3709 const pvector<string> &expanded_history,
3710 const string &object_type) {
3711 // Try to find the egg syntax that the given objecttype is shorthand for.
3712 // First, look in the config file.
3713
3714 ConfigVariableString egg_object_type
3715 ("egg-object-type-" + downcase(object_type), "");
3716 string egg_syntax = egg_object_type;
3717
3718 if (!egg_object_type.has_value()) {
3719 // It wasn't defined in a config file. Maybe it's built in?
3720
3721 if (cmp_nocase_uh(object_type, "barrier") == 0) {
3722 egg_syntax = "<Collide> { Polyset descend }";
3723
3724 } else if (cmp_nocase_uh(object_type, "solidpoly") == 0) {
3725 egg_syntax = "<Collide> { Polyset descend solid }";
3726
3727 } else if (cmp_nocase_uh(object_type, "turnstile") == 0) {
3728 egg_syntax = "<Collide> { Polyset descend turnstile }";
3729
3730 } else if (cmp_nocase_uh(object_type, "sphere") == 0) {
3731 egg_syntax = "<Collide> { Sphere descend }";
3732
3733 } else if (cmp_nocase_uh(object_type, "tube") == 0) {
3734 egg_syntax = "<Collide> { Tube descend }";
3735
3736 } else if (cmp_nocase_uh(object_type, "trigger") == 0) {
3737 egg_syntax = "<Collide> { Polyset descend intangible }";
3738
3739 } else if (cmp_nocase_uh(object_type, "trigger_sphere") == 0) {
3740 egg_syntax = "<Collide> { Sphere descend intangible }";
3741
3742 } else if (cmp_nocase_uh(object_type, "eye_trigger") == 0) {
3743 egg_syntax = "<Collide> { Polyset descend intangible center }";
3744
3745 } else if (cmp_nocase_uh(object_type, "bubble") == 0) {
3746 egg_syntax = "<Collide> { Sphere keep descend }";
3747
3748 } else if (cmp_nocase_uh(object_type, "ghost") == 0) {
3749 egg_syntax = "<Scalar> collide-mask { 0 }";
3750
3751 } else if (cmp_nocase_uh(object_type, "dcs") == 0) {
3752 egg_syntax = "<DCS> { 1 }";
3753
3754 } else if (cmp_nocase_uh(object_type, "model") == 0) {
3755 egg_syntax = "<Model> { 1 }";
3756
3757 } else if (cmp_nocase_uh(object_type, "none") == 0) {
3758 // ObjectType "none" is a special case, meaning nothing in particular.
3759 return true;
3760
3761 } else if (cmp_nocase_uh(object_type, "backstage") == 0) {
3762 // Ignore "backstage" geometry.
3763 return false;
3764
3765 } else {
3766 egg2pg_cat.error()
3767 << "Unknown ObjectType " << object_type << "\n";
3768 _error = true;
3769 egg2pg_cat.debug() << "returning true\n";
3770 return true;
3771 }
3772 }
3773
3774 if (!egg_syntax.empty()) {
3775 if (!egg_group->parse_egg(egg_syntax)) {
3776 egg2pg_cat.error()
3777 << "Error while parsing definition for ObjectType "
3778 << object_type << "\n";
3779 _error = true;
3780
3781 } else {
3782 // Now we've parsed the object type syntax, which might have added more
3783 // object types. Recurse if necessary.
3784 if (egg_group->get_num_object_types() != 0) {
3785 if (!expand_object_types(egg_group, expanded, expanded_history)) {
3786 return false;
3787 }
3788 }
3789 }
3790 }
3791
3792 return true;
3793}
3794
3795/**
3796 * Extracts the combine_mode from the given egg texture, and returns its
3797 * corresponding TextureStage value.
3798 */
3799TextureStage::CombineMode EggLoader::
3800get_combine_mode(const EggTexture *egg_tex,
3801 EggTexture::CombineChannel channel) {
3802 switch (egg_tex->get_combine_mode(channel)) {
3803 case EggTexture::CM_unspecified:
3804 // fall through
3805
3806 case EggTexture::CM_modulate:
3807 return TextureStage::CM_modulate;
3808
3809 case EggTexture::CM_replace:
3810 return TextureStage::CM_replace;
3811
3812 case EggTexture::CM_add:
3813 return TextureStage::CM_add;
3814
3815 case EggTexture::CM_add_signed:
3816 return TextureStage::CM_add_signed;
3817
3818 case EggTexture::CM_interpolate:
3819 return TextureStage::CM_interpolate;
3820
3821 case EggTexture::CM_subtract:
3822 return TextureStage::CM_subtract;
3823
3824 case EggTexture::CM_dot3_rgb:
3825 return TextureStage::CM_dot3_rgb;
3826
3827 case EggTexture::CM_dot3_rgba:
3828 return TextureStage::CM_dot3_rgba;
3829 };
3830
3831 return TextureStage::CM_undefined;
3832}
3833
3834/**
3835 * Extracts the combine_source from the given egg texture, and returns its
3836 * corresponding TextureStage value.
3837 */
3838TextureStage::CombineSource EggLoader::
3839get_combine_source(const EggTexture *egg_tex,
3840 EggTexture::CombineChannel channel, int n) {
3841 switch (egg_tex->get_combine_source(channel, n)) {
3842 case EggTexture::CS_unspecified:
3843 // The default source if it is unspecified is based on the parameter
3844 // index.
3845 switch (n) {
3846 case 0:
3847 return TextureStage::CS_previous;
3848 case 1:
3849 return TextureStage::CS_texture;
3850 case 2:
3851 return TextureStage::CS_constant;
3852 }
3853 // Otherwise, fall through
3854
3855 case EggTexture::CS_texture:
3856 return TextureStage::CS_texture;
3857
3858 case EggTexture::CS_constant:
3859 return TextureStage::CS_constant;
3860
3861 case EggTexture::CS_primary_color:
3862 return TextureStage::CS_primary_color;
3863
3864 case EggTexture::CS_previous:
3865 return TextureStage::CS_previous;
3866
3867 case EggTexture::CS_constant_color_scale:
3868 return TextureStage::CS_constant_color_scale;
3869
3870 case EggTexture::CS_last_saved_result:
3871 return TextureStage::CS_last_saved_result;
3872 };
3873
3874 return TextureStage::CS_undefined;
3875}
3876
3877/**
3878 * Extracts the combine_operand from the given egg texture, and returns its
3879 * corresponding TextureStage value.
3880 */
3881TextureStage::CombineOperand EggLoader::
3882get_combine_operand(const EggTexture *egg_tex,
3883 EggTexture::CombineChannel channel, int n) {
3884 switch (egg_tex->get_combine_operand(channel, n)) {
3885 case EggTexture::CO_unspecified:
3886 if (channel == EggTexture::CC_rgb) {
3887 // The default operand for RGB is src_color, except for the third
3888 // parameter, which defaults to src_alpha.
3889 return n < 2 ? TextureStage::CO_src_color : TextureStage::CO_src_alpha;
3890 } else {
3891 // The default operand for alpha is always src_alpha.
3892 return TextureStage::CO_src_alpha;
3893 }
3894
3895 case EggTexture::CO_src_color:
3896 return TextureStage::CO_src_color;
3897
3898 case EggTexture::CO_one_minus_src_color:
3899 return TextureStage::CO_one_minus_src_color;
3900
3901 case EggTexture::CO_src_alpha:
3902 return TextureStage::CO_src_alpha;
3903
3904 case EggTexture::CO_one_minus_src_alpha:
3905 return TextureStage::CO_one_minus_src_alpha;
3906 };
3907
3908 return TextureStage::CO_undefined;
3909}
3910
3911/**
3912 * Converts the EggGroup's BlendMode to the corresponding
3913 * ColorBlendAttrib::Mode value.
3914 */
3915ColorBlendAttrib::Mode EggLoader::
3916get_color_blend_mode(EggGroup::BlendMode mode) {
3917 switch (mode) {
3918 case EggGroup::BM_unspecified:
3919 case EggGroup::BM_none:
3920 return ColorBlendAttrib::M_none;
3921 case EggGroup::BM_add:
3922 return ColorBlendAttrib::M_add;
3923 case EggGroup::BM_subtract:
3924 return ColorBlendAttrib::M_subtract;
3925 case EggGroup::BM_inv_subtract:
3926 return ColorBlendAttrib::M_inv_subtract;
3927 case EggGroup::BM_min:
3928 return ColorBlendAttrib::M_min;
3929 case EggGroup::BM_max:
3930 return ColorBlendAttrib::M_max;
3931 }
3932
3933 return ColorBlendAttrib::M_none;
3934}
3935
3936/**
3937 * Converts the EggGroup's BlendOperand to the corresponding
3938 * ColorBlendAttrib::Operand value.
3939 */
3940ColorBlendAttrib::Operand EggLoader::
3941get_color_blend_operand(EggGroup::BlendOperand operand) {
3942 switch (operand) {
3943 case EggGroup::BO_zero:
3944 return ColorBlendAttrib::O_zero;
3945 case EggGroup::BO_unspecified:
3946 case EggGroup::BO_one:
3947 return ColorBlendAttrib::O_one;
3948 case EggGroup::BO_incoming_color:
3949 return ColorBlendAttrib::O_incoming_color;
3950 case EggGroup::BO_one_minus_incoming_color:
3951 return ColorBlendAttrib::O_one_minus_incoming_color;
3952 case EggGroup::BO_fbuffer_color:
3953 return ColorBlendAttrib::O_fbuffer_color;
3954 case EggGroup::BO_one_minus_fbuffer_color:
3955 return ColorBlendAttrib::O_one_minus_fbuffer_color;
3956 case EggGroup::BO_incoming_alpha:
3957 return ColorBlendAttrib::O_incoming_alpha;
3958 case EggGroup::BO_one_minus_incoming_alpha:
3959 return ColorBlendAttrib::O_one_minus_incoming_alpha;
3960 case EggGroup::BO_fbuffer_alpha:
3961 return ColorBlendAttrib::O_fbuffer_alpha;
3962 case EggGroup::BO_one_minus_fbuffer_alpha:
3963 return ColorBlendAttrib::O_one_minus_fbuffer_alpha;
3964 case EggGroup::BO_constant_color:
3965 return ColorBlendAttrib::O_constant_color;
3966 case EggGroup::BO_one_minus_constant_color:
3967 return ColorBlendAttrib::O_one_minus_constant_color;
3968 case EggGroup::BO_constant_alpha:
3969 return ColorBlendAttrib::O_constant_alpha;
3970 case EggGroup::BO_one_minus_constant_alpha:
3971 return ColorBlendAttrib::O_one_minus_constant_alpha;
3972 case EggGroup::BO_incoming_color_saturate:
3973 return ColorBlendAttrib::O_incoming_color_saturate;
3974 case EggGroup::BO_color_scale:
3975 return ColorBlendAttrib::O_color_scale;
3976 case EggGroup::BO_one_minus_color_scale:
3977 return ColorBlendAttrib::O_one_minus_color_scale;
3978 case EggGroup::BO_alpha_scale:
3979 return ColorBlendAttrib::O_alpha_scale;
3980 case EggGroup::BO_one_minus_alpha_scale:
3981 return ColorBlendAttrib::O_one_minus_alpha_scale;
3982 }
3983
3984 return ColorBlendAttrib::O_zero;
3985}
3986
3987/**
3988 *
3989 */
3990bool EggLoader::VertexPoolTransform::
3991operator < (const EggLoader::VertexPoolTransform &other) const {
3992 if (_vertex_pool != other._vertex_pool) {
3993 return _vertex_pool < other._vertex_pool;
3994 }
3995 int compare = _transform.compare_to(other._transform, 0.001);
3996 if (compare != 0) {
3997 return compare < 0;
3998 }
3999
4000 if (_bake_in_uvs.size() != other._bake_in_uvs.size()) {
4001 return _bake_in_uvs.size() < other._bake_in_uvs.size();
4002 }
4003
4004 BakeInUVs::const_iterator ai, bi;
4005 ai = _bake_in_uvs.begin();
4006 bi = other._bake_in_uvs.begin();
4007 while (ai != _bake_in_uvs.end()) {
4008 nassertr(bi != other._bake_in_uvs.end(), false);
4009 if ((*ai) != (*bi)) {
4010 return (*ai) < (*bi);
4011 }
4012 ++ai;
4013 ++bi;
4014 }
4015 nassertr(bi == other._bake_in_uvs.end(), false);
4016
4017 return false;
4018}
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.
void loop(bool restart)
Starts the entire animation looping.
Converts an EggGroup hierarchy, beginning with a group with <Dart> set, to a character node with join...
virtual void xform(const LMatrix4 &mat)
Transforms the solid by the indicated matrix.
A node in the scene graph that can hold any number of CollisionSolids.
size_t add_solid(const CollisionSolid *solid)
Adds the indicated solid to the node.
virtual void xform(const LMatrix4 &mat)
Transforms the solid by the indicated matrix.
The abstract base class for all things that can collide with other things in the world,...
set_tangible
Sets the current state of the 'tangible' flag.
void set_effective_normal(const LVector3 &effective_normal)
Records a false normal for this CollisionSolid that will be reported by the collision system with all...
virtual void xform(const LMatrix4 &mat)
Transforms the solid by the indicated matrix.
static ConstPointerTo< RenderAttrib > make_vertex()
Constructs a new ColorAttrib object that indicates geometry should be rendered according to its own v...
static ConstPointerTo< RenderAttrib > make_flat(const LColor &color)
Constructs a new ColorAttrib object that indicates geometry should be rendered in the indicated color...
static ConstPointerTo< RenderAttrib > make(Mode mode)
Constructs a new ColorBlendAttrib object.
static ConstPointerTo< RenderEffect > make()
Constructs a new DecalEffect object.
This class keeps track of all the state we must make note of during the graph traversal,...
void compose(const DeferredNodeProperty &other)
Composes this state with the next one encountered on a lower node during the apply traversal.
LColor get_color() const
Returns the color set on this particular attribute.
A type of group node that holds related subnodes.
Definition eggBin.h:26
bool triangulate_into(EggGroupNode *container) const
Subdivides the composite primitive into triangles and adds those triangles to the indicated container...
int get_subdiv() const
Returns the requested number of subdivisions, or 0 if no particular subdivisions have been requested.
Definition eggCurve.I:62
CurveType get_curve_type() const
Returns the indicated type of the curve.
Definition eggCurve.I:80
This is the primary interface into all the egg data, and the root of the egg file structure.
Definition eggData.h:37
const Filename & get_filename() const
Returns a nonmodifiable reference to the filename.
const Filename & get_fullpath() const
Returns the full pathname to the file, if it is known; otherwise, returns the same thing as get_filen...
A base class for nodes in the hierarchy that are not leaf nodes.
int triangulate_polygons(int flags)
Replace all higher-order polygons at this point in the scene graph and below with triangles.
void apply_first_attribute(bool recurse)
Sets the first vertex of the triangle (or each component) to the primitive normal and/or color,...
void post_apply_flat_attribute(bool recurse)
Intended as a followup to apply_last_attribute(), this also sets an attribute on the first vertices o...
void mesh_triangles(int flags)
Combine triangles together into triangle strips, at this group and below.
void rebuild_vertex_pools(EggVertexPools &vertex_pools, unsigned int max_vertices, bool recurse)
Copies vertices used by the primitives at this group node (and below, if recurse is true) into one or...
EggNode * get_first_child()
Returns the first child in the group's list of children, or NULL if the list of children is empty.
void replace(iterator position, PT(EggNode) x)
Replaces the node at the indicated position with the indicated node.
void steal_children(EggGroupNode &other)
Moves all the children from the other node to this one.
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition eggGroup.h:34
get_blend_color
Returns the blend color if one has been specified, or (0, 0, 0, 0) if one has not.
Definition eggGroup.h:318
TagData::const_iterator tag_begin() const
Returns an iterator that can, in conjunction with tag_end(), be used to traverse the entire set of ta...
Definition eggGroup.I:811
get_group_ref
Returns the nth <Ref> entry within this group.
Definition eggGroup.h:354
TagData::const_iterator tag_end() const
Returns an iterator that can, in conjunction with tag_begin(), be used to traverse the entire set of ...
Definition eggGroup.I:823
get_num_group_refs
Returns the number of <Ref> entries within this group.
Definition eggGroup.h:354
bool has_dcs_type() const
Returns true if the specified DCS type is not DC_none and not DC_unspecified.
Definition eggGroup.I:199
double get_vertex_membership(const EggVertex *vert) const
Returns the amount of membership of the indicated vertex in this group.
Definition eggGroup.cxx:677
virtual bool determine_decal()
Walks back up the hierarchy, looking for an EggGroup at this level or above that has the "decal" flag...
Definition eggGroup.cxx:595
void reparent_decals()
For each node representing a decal base geometry (i.e.
void make_polyset(EggBin *egg_bin, PandaNode *parent, const LMatrix4d *transform, bool is_dynamic, CharacterMaker *character_maker)
Creates a polyset–that is, a Geom–from the primitives that have already been grouped into a bin.
void start_sequences()
Starts all of the SequenceNodes we created looping.
A base class for things that may be directly added into the egg hierarchy.
Definition eggNode.h:36
const LMatrix4d & get_vertex_to_node() const
Returns the transformation matrix suitable for converting the vertices as read from the egg file into...
Definition eggNode.I:166
bool parse_egg(const std::string &egg_syntax)
Parses the egg syntax given in the indicate string as if it had been read from the egg file within th...
Definition eggNode.cxx:224
A parametric NURBS curve.
get_order
Returns the order of the curve.
get_num_knots
Returns the number of knots.
get_knot
Returns the nth knot value defined.
A parametric NURBS surface.
EggUserData * get_user_data() const
Returns the user data pointer most recently stored on this object, or NULL if nothing was previously ...
Definition eggObject.cxx:84
A single polygon.
Definition eggPolygon.h:24
bool is_planar() const
Returns true if all of the polygon's vertices lie within the same plane, false otherwise.
bool triangulate_into(EggGroupNode *container, bool convex_also) const
Subdivides the polygon into triangles and adds each one to the indicated container.
Definition eggPolygon.I:67
virtual bool cleanup() override
Cleans up modeling errors in whatever context this makes sense.
A base class for any of a number of kinds of geometry primitives: polygons, point lights,...
set_bface_flag
Sets the backfacing flag of the polygon.
get_pool
Returns the vertex pool associated with the vertices of the primitive, or NULL if the primitive has n...
get_shading
Returns the shading properties apparent on this particular primitive.
get_vertex
Returns a particular index based on its index number.
get_bface_flag
Retrieves the backfacing flag of the polygon.
bool has_vertex_color() const
Returns true if any vertex on the primitive has a specific color set, false otherwise.
void set_alpha_mode(AlphaMode mode)
Specifies precisely how the transparency for this geometry should be achieved, or if it should be use...
AlphaMode get_alpha_mode() const
Returns the alpha mode that was set, or AM_unspecified if nothing was set.
This class is used within this package only to record the render state that should be assigned to eac...
int get_v_subdiv() const
Returns the requested number of subdivisions in the U direction, or 0 if no particular subdivisions h...
Definition eggSurface.I:82
int get_u_subdiv() const
Returns the requested number of subdivisions in the U direction, or 0 if no particular subdivisions h...
Definition eggSurface.I:62
This corresponds to a a <Bundle> entry.
Definition eggTable.h:27
int find_used_textures(EggNode *node)
Walks the egg hierarchy beginning at the indicated node, looking for textures that are referenced by ...
Defines a texture map that may be applied to geometry.
Definition eggTexture.h:30
get_stage_name
Returns the stage name that has been specified for this texture, or the tref name if no texture stage...
Definition eggTexture.h:338
get_read_mipmaps
Returns the current setting of the read_mipmaps flag.
Definition eggTexture.h:359
WrapMode determine_wrap_u() const
Determines the appropriate wrap in the U direction.
Definition eggTexture.I:107
has_uv_name
Returns true if a texcoord name has been explicitly specified for this texture, false otherwise.
Definition eggTexture.h:346
get_alpha_scale
Returns the alpha_scale value that has been specified for the texture, or 1 if no alpha_scale value h...
Definition eggTexture.h:350
get_uv_name
Returns the texcoord name that has been specified for this texture, or the empty string if no texcoor...
Definition eggTexture.h:346
get_multiview
Returns the current setting of the multiview flag.
Definition eggTexture.h:356
get_multitexture_sort
Returns an integer that represents the depth to which this texture is layered on all other textures i...
Definition eggTexture.h:367
has_num_views
Returns true if the number of views has been specified for the 3-D multiview texture,...
Definition eggTexture.h:358
get_lod_bias
Returns the maximum mipmap level that has been specified for this texture.
Definition eggTexture.h:365
get_alpha_file_channel
Returns the particular channel that has been specified for the alpha-file image, or 0 if no channel h...
Definition eggTexture.h:355
has_anisotropic_degree
Returns true if a value for the anisotropic filtering degree has been specified for this texture,...
Definition eggTexture.h:332
get_border_color
Returns the border color if one has been specified, or (0, 0, 0, 1) otherwise.
Definition eggTexture.h:344
get_min_lod
Returns the minimum mipmap level that has been specified for this texture.
Definition eggTexture.h:361
WrapMode determine_wrap_w() const
Determines the appropriate wrap in the W direction.
Definition eggTexture.I:161
set_anisotropic_degree
Sets the degree of anisotropic filtering for this texture.
Definition eggTexture.h:332
has_min_lod
Returns true if a value for the minimum mipmap level has been specified for this texture,...
Definition eggTexture.h:361
get_max_lod
Returns the maximum mipmap level that has been specified for this texture.
Definition eggTexture.h:363
WrapMode determine_wrap_v() const
Determines the appropriate wrap in the V direction.
Definition eggTexture.I:134
get_anisotropic_degree
Returns the anisotropic filtering degree that has been specified for this texture,...
Definition eggTexture.h:332
has_color
Returns true if a blend color has been specified for the texture.
Definition eggTexture.h:342
has_lod_bias
Returns true if a value for the maximum mipmap level has been specified for this texture,...
Definition eggTexture.h:365
get_alpha_filename
Returns the separate file assigned for the alpha channel.
Definition eggTexture.h:352
has_priority
Returns true if a priority value for multitexture importance has been specified for the texture,...
Definition eggTexture.h:340
get_alpha_fullpath
Returns the full pathname to the alpha file, if it is known; otherwise, returns the same thing as get...
Definition eggTexture.h:353
has_rgb_scale
Returns true if an rgb_scale has been specified for the texture, false otherwise.
Definition eggTexture.h:348
has_alpha_scale
Returns true if an alpha_scale has been specified for the texture, false otherwise.
Definition eggTexture.h:350
get_color
Returns the blend color if one has been specified, or (0, 0, 0, 1) otherwise.
Definition eggTexture.h:342
has_max_lod
Returns true if a value for the maximum mipmap level has been specified for this texture,...
Definition eggTexture.h:363
get_rgb_scale
Returns the rgb_scale value that has been specified for the texture, or 1 if no rgb_scale value has b...
Definition eggTexture.h:348
has_border_color
Returns true if a border color has been specified for the texture.
Definition eggTexture.h:344
get_priority
Returns the multitexture importance value that has been specified for the texture,...
Definition eggTexture.h:340
has_stage_name
Returns true if a stage name has been explicitly specified for this texture, false otherwise.
Definition eggTexture.h:338
has_alpha_filename
Returns true if a separate file for the alpha component has been applied, false otherwise.
Definition eggTexture.h:352
get_num_views
Returns the specified number of views specified for the 3-D multiview texture.
Definition eggTexture.h:358
get_saved_result
Returns the current setting of the saved_result flag.
Definition eggTexture.h:334
This represents the <Transform> entry of a group or texture node: a list of component transform opera...
bool has_transform() const
Returns true if the transform is nonempty, false if it is empty (no transform components have been ad...
const LVecBase3d & get_component_vec3(int n) const
Returns the 3-component vector associated with the nth component.
ComponentType get_component_type(int n) const
Returns the type of the nth component.
const LVecBase2d & get_component_vec2(int n) const
Returns the 2-component vector associated with the nth component.
int get_num_components() const
Returns the number of components that make up the transform.
const LMatrix3d & get_component_mat3(int n) const
Returns the 3x3 matrix associated with the nth component.
const LMatrix4d & get_component_mat4(int n) const
Returns the 4x4 matrix associated with the nth component.
double get_component_number(int n) const
Returns the solitary number associated with the nth component.
const LVecBase4d & get_aux() const
Returns the auxiliary data quadruple.
A collection of vertices.
int remove_unused_vertices()
Removes all vertices from the pool that are not referenced by at least one primitive.
EggVertex * create_unique_vertex(const EggVertex &copy)
Creates a new vertex in the pool that is a copy of the indicated one and returns it.
const LTexCoord3d & get_uvw() const
Returns the texture coordinate triple, if get_num_dimensions() is 3.
Definition eggVertexUV.I:68
int get_index() const
Returns the index number of the vertex within its pool.
Definition eggVertex.I:277
int get_external_index() const
Returns the number set by set_external_index().
Definition eggVertex.I:300
EggVertexPool * get_pool() const
Returns the vertex pool this vertex belongs in.
Definition eggVertex.I:19
void set_external_index(int external_index)
Sets a special index number that is associated with the EggVertex (but is not written to the egg file...
Definition eggVertex.I:292
GroupRef::const_iterator gref_end() const
Returns an iterator that can, in conjunction with gref_begin(), be used to traverse the entire set of...
GroupRef::size_type gref_size() const
Returns the number of elements between gref_begin() and gref_end().
const_aux_iterator aux_begin() const
Returns an iterator that allows walking through the complete set of auxiliary data on the vertex.
Definition eggVertex.I:231
const_uv_iterator uv_end() const
Returns an iterator that allows walking through the complete set of named UV's on the vertex.
Definition eggVertex.I:242
void copy_grefs_from(const EggVertex &other)
Copies all the group references from the other vertex onto this one.
const_uv_iterator uv_begin() const
Returns an iterator that allows walking through the complete set of named UV's on the vertex.
Definition eggVertex.I:220
GroupRef::const_iterator gref_begin() const
Returns an iterator that can, in conjunction with gref_end(), be used to traverse the entire set of g...
LVertexd get_pos3() const
Valid if get_num_dimensions() returns 3 or 4.
Definition eggVertex.I:131
LPoint4d get_pos4() const
This is always valid, regardless of the value of get_num_dimensions.
Definition eggVertex.I:145
const_aux_iterator aux_end() const
Returns an iterator that allows walking through the complete set of auxiliary data on the vertex.
Definition eggVertex.I:253
A node that holds Geom objects, renderable pieces of geometry.
Definition geomNode.h:34
void add_geom(Geom *geom, const RenderState *state=RenderState::make_empty())
Adds a new Geom to the node.
Definition geomNode.cxx:612
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
get_num_vertices
Returns the number of indices used by all the primitives in this object.
void set_panda()
Specifies that vertex animation is to be performed by Panda.
This describes the structure of a single array within a Geom data.
bool has_column(const InternalName *name) const
Returns true if the array has the named column, false otherwise.
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...
static const GeomVertexFormat * get_v3cp()
Returns a standard vertex format with a packed color and a 3-component vertex position.
A container for geometry primitives.
Definition geom.h:54
Encodes a string name in a hash table, mapping it to a pointer.
set_texture_num_views
Specifies the expected number of views to load for the texture.
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition nodePath.h:159
int get_num_children(Thread *current_thread=Thread::get_current_thread()) const
Returns the number of children of the referenced node.
Definition nodePath.I:328
bool is_empty() const
Returns true if the NodePath contains no nodes.
Definition nodePath.I:188
PandaNode * node() const
Returns the referenced node of the path.
Definition nodePath.I:227
void reparent_to(const NodePath &other, int sort=0, Thread *current_thread=Thread::get_current_thread())
Removes the referenced node of the NodePath from its current parent and attaches it to the referenced...
Definition nodePath.cxx:394
NodePath get_child(int n, Thread *current_thread=Thread::get_current_thread()) const
Returns a NodePath representing the nth child of the referenced node.
Definition nodePath.I:337
A node in the scene graph that can hold an occluder polygon, which must be a rectangle.
set_double_sided
If true, the back-face will also be used to occlude.
get_num_vertices
Returns the number of vertices in the occluder polygon.
void set_vertices(const LPoint3 &v0, const LPoint3 &v1, const LPoint3 &v2, const LPoint3 &v3)
Replaces the four vertices of the occluder polygon.
A basic node of the scene graph or data graph.
Definition pandaNode.h:65
virtual bool is_geom_node() const
A simple downcast check.
set_tag
Associates a user-defined value with a user-defined key which is stored on the node.
Definition pandaNode.h:207
get_child
Returns the nth child node of this node.
Definition pandaNode.h:124
set_transform
Sets the transform that will be applied to this node and below.
Definition pandaNode.h:183
void set_effect(const RenderEffect *effect)
Adds the indicated render effect to the scene graph on this node.
void add_stashed(PandaNode *child_node, int sort=0, Thread *current_thread=Thread::get_current_thread())
Adds a new child to the node, directly as a stashed child.
void add_child(PandaNode *child_node, int sort=0, Thread *current_thread=Thread::get_current_thread())
Adds a new child to the node.
void set_prev_transform(const TransformState *transform, Thread *current_thread=Thread::get_current_thread())
Sets the transform that represents this node's "previous" position, one frame ago,...
void set_attrib(const RenderAttrib *attrib, int override=0)
Adds the indicated render attribute to the scene graph on this node.
get_num_children
Returns the number of child nodes this node has.
Definition pandaNode.h:124
void set_pos(const LPoint3 &position)
Set this light's position.
void set_radius(PN_stdfloat r)
Set radius of the spherical light volume.
void set_color(const LColor &color)
Set the light's color...
virtual void xform(const LMatrix4 &mat)
Transforms the contents of this node by the indicated matrix, if it means anything to do so.
A node in the scene graph that can hold a Portal Polygon, which is a rectangle.
Definition portalNode.h:30
void add_vertex(const LPoint3 &vertex)
Adds a new vertex to the portal polygon.
Definition portalNode.I:109
get_num_vertices
Returns the number of vertices in the portal polygon.
Definition portalNode.h:67
void clear_vertices()
Resets the vertices of the portal to the empty list.
Definition portalNode.I:100
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition renderState.h:47
set_wrap_v
This setting determines what happens when the SamplerState is sampled with a V value outside the rang...
set_max_lod
Sets the maximum level of detail that will be used when sampling this texture.
set_anisotropic_degree
Specifies the level of anisotropic filtering to apply to the SamplerState.
set_border_color
Specifies the solid color of the SamplerState's border.
set_wrap_w
The W wrap direction is only used for 3-d SamplerStates.
set_min_lod
Sets the minimum level of detail that will be used when sampling this texture.
set_minfilter
Sets the filtering method that should be used when viewing the SamplerState from a distance.
set_wrap_u
This setting determines what happens when the SamplerState is sampled with a U value outside the rang...
set_magfilter
Sets the filtering method that should be used when viewing the SamplerState up close.
set_lod_bias
Sets the value that will be added to the level of detail when sampling the texture.
A node that automatically cycles through rendering each one of its children according to its frame ra...
static SparseArray lower_on(int on_bits)
Returns a SparseArray whose lower on_bits bits are on.
Definition sparseArray.I:43
static ConstPointerTo< RenderAttrib > make()
Constructs a new TextureAttrib object that does nothing.
static Texture * load_texture(const Filename &filename, int primary_file_num_channels=0, bool read_mipmaps=false, const LoaderOptions &options=LoaderOptions())
Loads the given filename up into a texture, if it has not already been loaded, and returns the new te...
Definition texturePool.I:70
static Texture * load_cube_map(const Filename &filename_pattern, bool read_mipmaps=false, const LoaderOptions &options=LoaderOptions())
Loads a cube map texture that is specified with a series of 6 pages, numbered 0 through 5.
static Texture * load_3d_texture(const Filename &filename_pattern, bool read_mipmaps=false, const LoaderOptions &options=LoaderOptions())
Loads a 3-D texture that is specified with a series of n pages, all numbered in sequence,...
static TextureStage * get_stage(TextureStage *temp)
Returns a TextureStage pointer that represents the same TextureStage described by temp,...
Defines the properties of a named stage of the multitexture pipeline.
get_default
Returns the default TextureStage that will be used for all texturing that does not name a particular ...
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition texture.h:72
set_compression
Requests that this particular Texture be compressed when it is loaded into texture memory.
Definition texture.h:414
get_format
Returns the format of the texture, which represents both the semantic meaning of the texels and,...
Definition texture.h:371
static bool is_srgb(Format format)
Returns true if the indicated format is in the sRGB color space, false otherwise.
Definition texture.cxx:2678
set_format
Changes the format value for the texture components.
Definition texture.h:371
get_component_width
Returns the number of bytes stored for each color component of a texel.
Definition texture.h:365
set_quality_level
Sets a hint to the renderer about the desired performance / quality tradeoff for this particular text...
Definition texture.h:428
get_num_components
Returns the number of color components for each texel of the texture image.
Definition texture.h:364
set_default_sampler
This sets the default sampler state for this texture, containing the wrap and filter properties speci...
Definition texture.h:422
static void consider_yield()
Possibly suspends the current thread for the rest of the current epoch, if it has run for enough this...
Definition thread.I:212
This structure collects together the different combinations of transforms and blend amounts used by a...
void limit_transforms(int max_transforms)
If the total number of transforms in the blend exceeds max_transforms, removes the n least-important ...
void normalize_weights()
Rescales all of the weights on the various transforms so that they sum to 1.0.
void add_transform(const VertexTransform *transform, PN_stdfloat weight)
Adds a new transform to the blend.
Indicates a coordinate-system transform on vertices.
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition typedObject.I:28
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.
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.
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.
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.
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.
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.
STTransform::operator CPT TransformState() const
This is used internally to convert an STTransform into a TransformState pointer.
Definition stTransform.I:98
string downcase(const string &s)
Returns the input string with all uppercase letters converted to lowercase.
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.