// Filename: eggLoader.cxx // Created by: drose (26Feb02) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) Carnegie Mellon University. All rights reserved. // // All use of this software is subject to the terms of the revised BSD // license. You should have received a copy of this license along // with this source code in a file named "LICENSE." // //////////////////////////////////////////////////////////////////// #include "pandabase.h" #include "eggLoader.h" #include "eggRenderState.h" #include "egg_parametrics.h" #include "config_egg2pg.h" #include "config_egg.h" #include "nodePath.h" #include "renderState.h" #include "transformState.h" #include "texturePool.h" #include "billboardEffect.h" #include "decalEffect.h" #include "colorAttrib.h" #include "textureAttrib.h" #include "materialPool.h" #include "geomNode.h" #include "geomVertexFormat.h" #include "geomVertexArrayFormat.h" #include "geomVertexData.h" #include "geomVertexWriter.h" #include "geom.h" #include "geomTriangles.h" #include "geomTristrips.h" #include "geomTrifans.h" #include "geomLines.h" #include "geomLinestrips.h" #include "geomPoints.h" #include "sequenceNode.h" #include "switchNode.h" #include "portalNode.h" #include "occluderNode.h" #include "polylightNode.h" #include "lodNode.h" #include "modelNode.h" #include "modelRoot.h" #include "string_utils.h" #include "eggPrimitive.h" #include "eggPoint.h" #include "eggLine.h" #include "eggTextureCollection.h" #include "eggNurbsCurve.h" #include "eggNurbsSurface.h" #include "eggGroupNode.h" #include "eggGroup.h" #include "eggPolygon.h" #include "eggTriangleStrip.h" #include "eggTriangleFan.h" #include "eggBin.h" #include "eggTable.h" #include "eggBinner.h" #include "eggVertexPool.h" #include "pt_EggTexture.h" #include "characterMaker.h" #include "character.h" #include "animBundleMaker.h" #include "animBundleNode.h" #include "selectiveChildNode.h" #include "collisionNode.h" #include "collisionSphere.h" #include "collisionInvSphere.h" #include "collisionTube.h" #include "collisionPlane.h" #include "collisionPolygon.h" #include "collisionFloorMesh.h" #include "parametricCurve.h" #include "nurbsCurve.h" #include "nurbsCurveInterface.h" #include "nurbsCurveEvaluator.h" #include "nurbsSurfaceEvaluator.h" #include "ropeNode.h" #include "sheetNode.h" #include "look_at.h" #include "configVariableString.h" #include "transformBlendTable.h" #include "transformBlend.h" #include "sparseArray.h" #include "bitArray.h" #include "thread.h" #include "uvScrollNode.h" #include "textureStagePool.h" #include #include // This class is used in make_node(EggBin *) to sort LOD instances in // order by switching distance. class LODInstance { public: LODInstance(EggNode *egg_node); bool operator < (const LODInstance &other) const { return _d->_switch_in < other._d->_switch_in; } EggNode *_egg_node; const EggSwitchConditionDistance *_d; }; LODInstance:: LODInstance(EggNode *egg_node) { nassertv(egg_node != NULL); _egg_node = egg_node; // We expect this egg node to be an EggGroup with an LOD // specification. That's what the EggBinner collected together, // after all. EggGroup *egg_group = DCAST(EggGroup, egg_node); nassertv(egg_group->has_lod()); const EggSwitchCondition &sw = egg_group->get_lod(); // For now, this is the only kind of switch condition there is. _d = DCAST(EggSwitchConditionDistance, &sw); } //////////////////////////////////////////////////////////////////// // Function: EggLoader::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// EggLoader:: EggLoader() { // We need to enforce whatever coordinate system the user asked for. _data = new EggData; _data->set_coordinate_system(egg_coordinate_system); _error = false; _dynamic_override = false; _dynamic_override_char_maker = NULL; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::Constructor // Access: Public // Description: The EggLoader constructor makes a copy of the EggData // passed in. //////////////////////////////////////////////////////////////////// EggLoader:: EggLoader(const EggData *data) : _data(new EggData(*data)) { _error = false; _dynamic_override = false; _dynamic_override_char_maker = NULL; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::build_graph // Access: Public // Description: //////////////////////////////////////////////////////////////////// void EggLoader:: build_graph() { _deferred_nodes.clear(); // Expand all of the ObjectType flags before we do anything else; // that might prune out large portions of the scene. if (!expand_all_object_types(_data)) { return; } // Now, load up all of the textures. load_textures(); // Clean up the vertices. _data->clear_connected_shading(); _data->remove_unused_vertices(true); _data->get_connected_shading(); _data->unify_attributes(true, egg_flat_shading, true); // Now we need to get the connected shading again, since in unifying // the attributes we may have made vertices suddenly become // identical to each other, thereby connecting more primitives than // before. _data->clear_connected_shading(); _data->remove_unused_vertices(true); _data->get_connected_shading(); // Sequences and switches have special needs. Make sure that // primitives parented directly to a sequence or switch are sorted // into sub-groups first, to prevent them being unified into a // single polyset. separate_switches(_data); if (egg_emulate_bface) { emulate_bface(_data); } // Then bin up the polysets and LOD nodes. _data->remove_invalid_primitives(true); EggBinner binner(*this); binner.make_bins(_data); // ((EggGroupNode *)_data)->write(cerr, 0); // Now build up the scene graph. _root = new ModelRoot(_data->get_egg_filename().get_basename()); make_node(_data, _root); reparent_decals(); start_sequences(); apply_deferred_nodes(_root, DeferredNodeProperty()); } //////////////////////////////////////////////////////////////////// // Function: EggLoader::reparent_decals // Access: Public // Description: For each node representing a decal base geometry // (i.e. a node corresponding to an EggGroup with the // decal flag set), move all of its nested geometry // directly below the GeomNode representing the group. //////////////////////////////////////////////////////////////////// void EggLoader:: reparent_decals() { ExtraNodes::const_iterator di; for (di = _decals.begin(); di != _decals.end(); ++di) { PandaNode *node = (*di); nassertv(node != (PandaNode *)NULL); // The NodePath interface is best for this. NodePath parent(node); // First, search for the GeomNode. NodePath geom_parent; int num_children = parent.get_num_children(); for (int i = 0; i < num_children; i++) { NodePath child = parent.get_child(i); if (child.node()->is_of_type(GeomNode::get_class_type())) { if (!geom_parent.is_empty()) { // Oops, too many GeomNodes. egg2pg_cat.error() << "Decal onto " << parent.node()->get_name() << " uses base geometry with multiple GeomNodes.\n"; _error = true; } geom_parent = child; } } if (geom_parent.is_empty()) { // No children were GeomNodes. egg2pg_cat.error() << "Ignoring decal onto " << parent.node()->get_name() << "; no geometry within group.\n"; _error = true; } else { // Now reparent all of the non-GeomNodes to this node. We have // to be careful so we don't get lost as we self-modify this // list. int i = 0; while (i < num_children) { NodePath child = parent.get_child(i); if (child.node()->is_of_type(GeomNode::get_class_type())) { i++; } else { child.reparent_to(geom_parent); num_children--; } } // Finally, set the DecalEffect on the base geometry. geom_parent.node()->set_effect(DecalEffect::make()); } } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::start_sequences // Access: Public // Description: Starts all of the SequenceNodes we created looping. // We have to wait until the entire graph is built up to // do this, because the SequenceNode needs its full set // of children before it can know how many frames to // loop. //////////////////////////////////////////////////////////////////// void EggLoader:: start_sequences() { ExtraNodes::const_iterator ni; for (ni = _sequences.begin(); ni != _sequences.end(); ++ni) { SequenceNode *node = DCAST(SequenceNode, (*ni)); node->loop(true); } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_polyset // Access: Public // Description: Creates a polyset--that is, a Geom--from the // primitives that have already been grouped into a bin. // If transform is non-NULL, it represents the transform // to apply to the vertices (instead of the default // transform based on the bin's position within the // hierarchy). //////////////////////////////////////////////////////////////////// void EggLoader:: make_polyset(EggBin *egg_bin, PandaNode *parent, const LMatrix4d *transform, bool is_dynamic, CharacterMaker *character_maker) { if (egg_bin->empty()) { // If there are no children--no primitives--never mind. return; } // We know that all of the primitives in the bin have the same // render state, so we can get that information from the first // primitive. EggGroupNode::const_iterator ci = egg_bin->begin(); nassertv(ci != egg_bin->end()); CPT(EggPrimitive) first_prim = DCAST(EggPrimitive, (*ci)); nassertv(first_prim != (EggPrimitive *)NULL); const EggRenderState *render_state; DCAST_INTO_V(render_state, first_prim->get_user_data(EggRenderState::get_class_type())); if (render_state->_hidden && egg_suppress_hidden) { // Eat this polyset. return; } // Generate an optimal vertex pool (or multiple vertex pools, if we // have a lot of vertex) for the polygons within just the bin. Each // EggVertexPool translates directly to an optimal GeomVertexData // structure. EggVertexPools vertex_pools; egg_bin->rebuild_vertex_pools(vertex_pools, (unsigned int)egg_max_vertices, false); if (egg_mesh) { // If we're using the mesher, mesh now. egg_bin->mesh_triangles(render_state->_flat_shaded ? EggGroupNode::T_flat_shaded : 0); } else { // If we're not using the mesher, at least triangulate any // higher-order polygons we might have. egg_bin->triangulate_polygons(EggGroupNode::T_polygon | EggGroupNode::T_convex); } // Now that we've meshed, apply the per-prim attributes onto the // vertices, so we can copy them to the GeomVertexData. egg_bin->apply_first_attribute(false); egg_bin->post_apply_flat_attribute(false); // egg_bin->write(cerr, 0); PT(GeomNode) geom_node; // Now iterate through each EggVertexPool. Normally, there's only // one, but if we have a really big mesh, it might have been split // into multiple vertex pools (to keep each one within the // egg_max_vertices constraint). EggVertexPools::iterator vpi; for (vpi = vertex_pools.begin(); vpi != vertex_pools.end(); ++vpi) { EggVertexPool *vertex_pool = (*vpi); vertex_pool->remove_unused_vertices(); // vertex_pool->write(cerr, 0); bool has_overall_color; Colorf overall_color; vertex_pool->check_overall_color(has_overall_color, overall_color); if (!egg_flat_colors) { // If flat colors aren't allowed, then we don't care whether // there is an overall color. In that case, treat all vertex // pools as if they contain a combination of multiple colors. has_overall_color = false; } // Create a handful of GeomPrimitives corresponding to the various // types of primitives that reference this vertex pool. UniquePrimitives unique_primitives; Primitives primitives; for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) { EggPrimitive *egg_prim; DCAST_INTO_V(egg_prim, (*ci)); if (egg_prim->get_pool() == vertex_pool) { make_primitive(render_state, egg_prim, unique_primitives, primitives, has_overall_color, overall_color); } } if (!primitives.empty()) { LMatrix4d mat; if (transform != NULL) { mat = (*transform); } else { mat = egg_bin->get_vertex_to_node(); } // Now convert this vertex pool to a GeomVertexData. PT(GeomVertexData) vertex_data = make_vertex_data(render_state, vertex_pool, egg_bin, mat, is_dynamic, character_maker, has_overall_color); nassertv(vertex_data != (GeomVertexData *)NULL); // And create a Geom to hold the primitives. PT(Geom) geom = new Geom(vertex_data); // Add each new primitive to the Geom. Primitives::const_iterator pi; for (pi = primitives.begin(); pi != primitives.end(); ++pi) { PT(GeomPrimitive) primitive = (*pi); if (primitive->is_indexed()) { // Since we may have over-allocated while we were filling up // the primitives, down-allocate now. primitive->reserve_num_vertices(primitive->get_num_vertices()); } geom->add_primitive(primitive); } // vertex_data->write(cerr); // geom->write(cerr); // render_state->_state->write(cerr, 0); // Create a new GeomNode if we haven't already. if (geom_node == (GeomNode *)NULL) { // Now, is our parent node a GeomNode, or just an ordinary // PandaNode? If it's a GeomNode, we can add the new Geom directly // to our parent; otherwise, we need to create a new node. if (parent->is_geom_node() && !render_state->_hidden) { geom_node = DCAST(GeomNode, parent); } else { geom_node = new GeomNode(egg_bin->get_name()); if (render_state->_hidden) { parent->add_stashed(geom_node); } else { parent->add_child(geom_node); } } } CPT(RenderState) geom_state = render_state->_state; if (has_overall_color) { if (!overall_color.almost_equal(Colorf(1.0f, 1.0f, 1.0f, 1.0f))) { geom_state = geom_state->add_attrib(ColorAttrib::make_flat(overall_color), -1); } } else { geom_state = geom_state->add_attrib(ColorAttrib::make_vertex(), -1); } geom_node->add_geom(geom, geom_state); } } if (geom_node != (GeomNode *)NULL && egg_show_normals) { // Create some more geometry to visualize each normal. for (vpi = vertex_pools.begin(); vpi != vertex_pools.end(); ++vpi) { EggVertexPool *vertex_pool = (*vpi); show_normals(vertex_pool, geom_node); } } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_transform // Access: Public // Description: Creates a TransformState object corresponding to the // indicated EggTransform. //////////////////////////////////////////////////////////////////// CPT(TransformState) EggLoader:: make_transform(const EggTransform *egg_transform) { // We'll build up the transform componentwise, so we preserve any // componentwise properties of the egg transform. CPT(TransformState) ts = TransformState::make_identity(); int num_components = egg_transform->get_num_components(); for (int i = 0; i < num_components; i++) { switch (egg_transform->get_component_type(i)) { case EggTransform::CT_translate2d: { LVecBase2f trans2d(LCAST(float, egg_transform->get_component_vec2(i))); LVecBase3f trans3d(trans2d[0], trans2d[1], 0.0f); ts = TransformState::make_pos(trans3d)->compose(ts); } break; case EggTransform::CT_translate3d: { LVecBase3f trans3d(LCAST(float, egg_transform->get_component_vec3(i))); ts = TransformState::make_pos(trans3d)->compose(ts); } break; case EggTransform::CT_rotate2d: { LRotationf rot(LVector3f(0.0f, 0.0f, 1.0f), (float)egg_transform->get_component_number(i)); ts = TransformState::make_quat(rot)->compose(ts); } break; case EggTransform::CT_rotx: { LRotationf rot(LVector3f(1.0f, 0.0f, 0.0f), (float)egg_transform->get_component_number(i)); ts = TransformState::make_quat(rot)->compose(ts); } break; case EggTransform::CT_roty: { LRotationf rot(LVector3f(0.0f, 1.0f, 0.0f), (float)egg_transform->get_component_number(i)); ts = TransformState::make_quat(rot)->compose(ts); } break; case EggTransform::CT_rotz: { LRotationf rot(LVector3f(0.0f, 0.0f, 1.0f), (float)egg_transform->get_component_number(i)); ts = TransformState::make_quat(rot)->compose(ts); } break; case EggTransform::CT_rotate3d: { LRotationf rot(LCAST(float, egg_transform->get_component_vec3(i)), (float)egg_transform->get_component_number(i)); ts = TransformState::make_quat(rot)->compose(ts); } break; case EggTransform::CT_scale2d: { LVecBase2f scale2d(LCAST(float, egg_transform->get_component_vec2(i))); LVecBase3f scale3d(scale2d[0], scale2d[1], 1.0f); ts = TransformState::make_scale(scale3d)->compose(ts); } break; case EggTransform::CT_scale3d: { LVecBase3f scale3d(LCAST(float, egg_transform->get_component_vec3(i))); ts = TransformState::make_scale(scale3d)->compose(ts); } break; case EggTransform::CT_uniform_scale: { float scale = (float)egg_transform->get_component_number(i); ts = TransformState::make_scale(scale)->compose(ts); } break; case EggTransform::CT_matrix3: { LMatrix3f m(LCAST(float, egg_transform->get_component_mat3(i))); LMatrix4f mat4(m(0, 0), m(0, 1), 0.0, m(0, 2), m(1, 0), m(1, 1), 0.0, m(1, 2), 0.0, 0.0, 1.0, 0.0, m(2, 0), m(2, 1), 0.0, m(2, 2)); ts = TransformState::make_mat(mat4)->compose(ts); } break; case EggTransform::CT_matrix4: { LMatrix4f mat4(LCAST(float, egg_transform->get_component_mat4(i))); ts = TransformState::make_mat(mat4)->compose(ts); } break; case EggTransform::CT_invalid: nassertr(false, ts); break; } } if (ts->components_given()) { return ts; } // Finally, we uniquify all the matrix-based TransformStates we // create by complete matrix value. The TransformState class // doesn't normally go this far, because of the cost of this // additional uniquification step, but this is the egg loader so we // don't mind spending a little bit of extra time here to get a more // optimal result. TransformStates::iterator tsi = _transform_states.insert(TransformStates::value_type(ts->get_mat(), ts)).first; return (*tsi).second; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::show_normals // Access: Private // Description: In the presence of egg-show-normals, generate some // additional geometry to represent the normals, // tangents, and binormals of each vertex. //////////////////////////////////////////////////////////////////// void EggLoader:: show_normals(EggVertexPool *vertex_pool, GeomNode *geom_node) { PT(GeomPrimitive) primitive = new GeomLines(Geom::UH_static); CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3cp(); PT(GeomVertexData) vertex_data = new GeomVertexData(vertex_pool->get_name(), format, Geom::UH_static); GeomVertexWriter vertex(vertex_data, InternalName::get_vertex()); GeomVertexWriter color(vertex_data, InternalName::get_color()); EggVertexPool::const_iterator vi; for (vi = vertex_pool->begin(); vi != vertex_pool->end(); ++vi) { EggVertex *vert = (*vi); LPoint3d pos = vert->get_pos3(); if (vert->has_normal()) { vertex.add_data3f(LCAST(float, pos)); vertex.add_data3f(LCAST(float, pos + vert->get_normal() * egg_normal_scale)); color.add_data4f(1.0f, 0.0f, 0.0f, 1.0f); color.add_data4f(1.0f, 0.0f, 0.0f, 1.0f); primitive->add_next_vertices(2); primitive->close_primitive(); } // Also look for tangents and binormals in each texture coordinate // set. EggVertex::const_uv_iterator uvi; for (uvi = vert->uv_begin(); uvi != vert->uv_end(); ++uvi) { EggVertexUV *uv_obj = (*uvi); if (uv_obj->has_tangent()) { vertex.add_data3f(LCAST(float, pos)); vertex.add_data3f(LCAST(float, pos + uv_obj->get_tangent() * egg_normal_scale)); color.add_data4f(0.0f, 1.0f, 0.0f, 1.0f); color.add_data4f(0.0f, 1.0f, 0.0f, 1.0f); primitive->add_next_vertices(2); primitive->close_primitive(); } if (uv_obj->has_binormal()) { vertex.add_data3f(LCAST(float, pos)); vertex.add_data3f(LCAST(float, pos + uv_obj->get_binormal() * egg_normal_scale)); color.add_data4f(0.0f, 0.0f, 1.0f, 1.0f); color.add_data4f(0.0f, 0.0f, 1.0f, 1.0f); primitive->add_next_vertices(2); primitive->close_primitive(); } } } PT(Geom) geom = new Geom(vertex_data); geom->add_primitive(primitive); geom_node->add_geom(geom); } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_nurbs_curve // Access: Private // Description: //////////////////////////////////////////////////////////////////// void EggLoader:: make_nurbs_curve(EggNurbsCurve *egg_curve, PandaNode *parent, const LMatrix4d &mat) { if (egg_load_old_curves) { // Make a NurbsCurve instead of a RopeNode (old interface). make_old_nurbs_curve(egg_curve, parent, mat); return; } assert(parent != NULL); assert(!parent->is_geom_node()); PT(NurbsCurveEvaluator) nurbs = ::make_nurbs_curve(egg_curve, mat); if (nurbs == (NurbsCurveEvaluator *)NULL) { _error = true; return; } /* switch (egg_curve->get_curve_type()) { case EggCurve::CT_xyz: curve->set_curve_type(PCT_XYZ); break; case EggCurve::CT_hpr: curve->set_curve_type(PCT_HPR); break; case EggCurve::CT_t: curve->set_curve_type(PCT_T); break; default: break; } */ PT(RopeNode) rope = new RopeNode(egg_curve->get_name()); rope->set_curve(nurbs); // Respect the subdivision values in the egg file, if any. if (egg_curve->get_subdiv() != 0) { int subdiv_per_segment = (int)((egg_curve->get_subdiv() + 0.5) / nurbs->get_num_segments()); rope->set_num_subdiv(max(subdiv_per_segment, 1)); } const EggRenderState *render_state; DCAST_INTO_V(render_state, egg_curve->get_user_data(EggRenderState::get_class_type())); if (render_state->_hidden && egg_suppress_hidden) { // Eat this primitive. return; } rope->set_state(render_state->_state); rope->set_uv_mode(RopeNode::UV_parametric); if (egg_curve->has_vertex_color()) { // If the curve had individual vertex color, enable it. rope->set_use_vertex_color(true); } else if (egg_curve->has_color()) { // Otherwise, if the curve has overall color, apply it. rope->set_attrib(ColorAttrib::make_flat(egg_curve->get_color())); } parent->add_child(rope); } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_old_nurbs_curve // Access: Private // Description: This deprecated interface creates a NurbsCurve object // for the EggNurbsCurve entry. It will eventually be // removed in favor of the above, which creates a // RopeNode. //////////////////////////////////////////////////////////////////// void EggLoader:: make_old_nurbs_curve(EggNurbsCurve *egg_curve, PandaNode *parent, const LMatrix4d &mat) { assert(parent != NULL); assert(!parent->is_geom_node()); PT(ParametricCurve) curve; curve = new NurbsCurve; NurbsCurveInterface *nurbs = curve->get_nurbs_interface(); nassertv(nurbs != (NurbsCurveInterface *)NULL); if (egg_curve->get_order() < 1 || egg_curve->get_order() > 4) { egg2pg_cat.error() << "Invalid NURBSCurve order for " << egg_curve->get_name() << ": " << egg_curve->get_order() << "\n"; _error = true; return; } nurbs->set_order(egg_curve->get_order()); EggPrimitive::const_iterator pi; for (pi = egg_curve->begin(); pi != egg_curve->end(); ++pi) { nurbs->append_cv(LCAST(float, (*pi)->get_pos4() * mat)); } int num_knots = egg_curve->get_num_knots(); if (num_knots != nurbs->get_num_knots()) { egg2pg_cat.error() << "Invalid NURBSCurve number of knots for " << egg_curve->get_name() << ": got " << num_knots << " knots, expected " << nurbs->get_num_knots() << "\n"; _error = true; return; } for (int i = 0; i < num_knots; i++) { nurbs->set_knot(i, egg_curve->get_knot(i)); } switch (egg_curve->get_curve_type()) { case EggCurve::CT_xyz: curve->set_curve_type(PCT_XYZ); break; case EggCurve::CT_hpr: curve->set_curve_type(PCT_HPR); break; case EggCurve::CT_t: curve->set_curve_type(PCT_T); break; default: break; } curve->set_name(egg_curve->get_name()); if (!curve->recompute()) { egg2pg_cat.error() << "Invalid NURBSCurve " << egg_curve->get_name() << "\n"; _error = true; return; } parent->add_child(curve); } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_nurbs_surface // Access: Private // Description: //////////////////////////////////////////////////////////////////// void EggLoader:: make_nurbs_surface(EggNurbsSurface *egg_surface, PandaNode *parent, const LMatrix4d &mat) { assert(parent != NULL); assert(!parent->is_geom_node()); PT(NurbsSurfaceEvaluator) nurbs = ::make_nurbs_surface(egg_surface, mat); if (nurbs == (NurbsSurfaceEvaluator *)NULL) { _error = true; return; } PT(SheetNode) sheet = new SheetNode(egg_surface->get_name()); sheet->set_surface(nurbs); // Respect the subdivision values in the egg file, if any. if (egg_surface->get_u_subdiv() != 0) { int u_subdiv_per_segment = (int)((egg_surface->get_u_subdiv() + 0.5) / nurbs->get_num_u_segments()); sheet->set_num_u_subdiv(max(u_subdiv_per_segment, 1)); } if (egg_surface->get_v_subdiv() != 0) { int v_subdiv_per_segment = (int)((egg_surface->get_v_subdiv() + 0.5) / nurbs->get_num_v_segments()); sheet->set_num_v_subdiv(max(v_subdiv_per_segment, 1)); } const EggRenderState *render_state; DCAST_INTO_V(render_state, egg_surface->get_user_data(EggRenderState::get_class_type())); if (render_state->_hidden && egg_suppress_hidden) { // Eat this primitive. return; } sheet->set_state(render_state->_state); if (egg_surface->has_vertex_color()) { // If the surface had individual vertex color, enable it. sheet->set_use_vertex_color(true); } else if (egg_surface->has_color()) { // Otherwise, if the surface has overall color, apply it. sheet->set_attrib(ColorAttrib::make_flat(egg_surface->get_color())); } parent->add_child(sheet); } //////////////////////////////////////////////////////////////////// // Function: EggLoader::load_textures // Access: Private // Description: //////////////////////////////////////////////////////////////////// void EggLoader:: load_textures() { // First, collect all the textures that are referenced. EggTextureCollection tc; tc.find_used_textures(_data); EggTextureCollection::iterator ti; for (ti = tc.begin(); ti != tc.end(); ++ti) { PT_EggTexture egg_tex = (*ti); TextureDef def; if (load_texture(def, egg_tex)) { // Now associate the pointers, so we'll be able to look up the // Texture pointer given an EggTexture pointer, later. _textures[egg_tex] = def; } } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::load_texture // Access: Private // Description: //////////////////////////////////////////////////////////////////// bool EggLoader:: load_texture(TextureDef &def, EggTexture *egg_tex) { // Check to see if we should reduce the number of channels in // the texture. int wanted_channels = 0; bool wanted_alpha = false; switch (egg_tex->get_format()) { case EggTexture::F_red: case EggTexture::F_green: case EggTexture::F_blue: case EggTexture::F_alpha: case EggTexture::F_luminance: wanted_channels = 1; wanted_alpha = false; break; case EggTexture::F_luminance_alpha: case EggTexture::F_luminance_alphamask: wanted_channels = 2; wanted_alpha = true; break; case EggTexture::F_rgb: case EggTexture::F_rgb12: case EggTexture::F_rgb8: case EggTexture::F_rgb5: case EggTexture::F_rgb332: wanted_channels = 3; wanted_alpha = false; break; case EggTexture::F_rgba: case EggTexture::F_rgbm: case EggTexture::F_rgba12: case EggTexture::F_rgba8: case EggTexture::F_rgba4: case EggTexture::F_rgba5: wanted_channels = 4; wanted_alpha = true; break; case EggTexture::F_unspecified: wanted_alpha = egg_tex->has_alpha_filename(); } // Since some properties of the textures are inferred from the // texture files themselves (if the properties are not explicitly // specified in the egg file), then we add the textures as // dependents for the egg file. if (_record != (BamCacheRecord *)NULL) { _record->add_dependent_file(egg_tex->get_fullpath()); if (egg_tex->has_alpha_filename() && wanted_alpha) { _record->add_dependent_file(egg_tex->get_alpha_fullpath()); } } // By convention, the egg loader will preload the simple texture images. LoaderOptions options; if (egg_preload_simple_textures) { options.set_texture_flags(options.get_texture_flags() | LoaderOptions::TF_preload_simple); } if (!egg_ignore_filters && !egg_ignore_mipmaps) { switch (egg_tex->get_minfilter()) { case EggTexture::FT_nearest: case EggTexture::FT_linear: case EggTexture::FT_unspecified: break; case EggTexture::FT_nearest_mipmap_nearest: case EggTexture::FT_linear_mipmap_nearest: case EggTexture::FT_nearest_mipmap_linear: case EggTexture::FT_linear_mipmap_linear: options.set_texture_flags(options.get_texture_flags() | LoaderOptions::TF_generate_mipmaps); } } PT(Texture) tex; switch (egg_tex->get_texture_type()) { case EggTexture::TT_unspecified: case EggTexture::TT_1d_texture: options.set_texture_flags(options.get_texture_flags() | LoaderOptions::TF_allow_1d); // Fall through. case EggTexture::TT_2d_texture: if (egg_tex->has_alpha_filename() && wanted_alpha) { tex = TexturePool::load_texture(egg_tex->get_fullpath(), egg_tex->get_alpha_fullpath(), wanted_channels, egg_tex->get_alpha_file_channel(), egg_tex->get_read_mipmaps(), options); } else { tex = TexturePool::load_texture(egg_tex->get_fullpath(), wanted_channels, egg_tex->get_read_mipmaps(), options); } break; case EggTexture::TT_3d_texture: tex = TexturePool::load_3d_texture(egg_tex->get_fullpath(), egg_tex->get_read_mipmaps(), options); break; case EggTexture::TT_cube_map: tex = TexturePool::load_cube_map(egg_tex->get_fullpath(), egg_tex->get_read_mipmaps(), options); break; } if (tex == (Texture *)NULL) { return false; } // Record the original filenames in the textures (as loaded from the // egg file). These filenames will be written back to the bam file // if the bam file is written out. tex->set_filename(egg_tex->get_filename()); if (egg_tex->has_alpha_filename() && wanted_alpha) { tex->set_alpha_filename(egg_tex->get_alpha_filename()); } // See if there is some egg data hanging on the texture. In // particular, the TxaFileFilter might have left that here for us. TypedReferenceCount *aux = tex->get_aux_data("egg"); if (aux != (TypedReferenceCount *)NULL && aux->is_of_type(EggTexture::get_class_type())) { EggTexture *aux_egg_tex = DCAST(EggTexture, aux); if (aux_egg_tex->get_alpha_mode() != EggTexture::AM_unspecified) { egg_tex->set_alpha_mode(aux_egg_tex->get_alpha_mode()); } if (aux_egg_tex->get_format() != EggTexture::F_unspecified) { egg_tex->set_format(aux_egg_tex->get_format()); } if (aux_egg_tex->get_minfilter() != EggTexture::FT_unspecified) { egg_tex->set_minfilter(aux_egg_tex->get_minfilter()); } if (aux_egg_tex->get_magfilter() != EggTexture::FT_unspecified) { egg_tex->set_magfilter(aux_egg_tex->get_magfilter()); } if (aux_egg_tex->has_anisotropic_degree()) { egg_tex->set_anisotropic_degree(aux_egg_tex->get_anisotropic_degree()); } } apply_texture_attributes(tex, egg_tex); // Make a texture stage for the texture. PT(TextureStage) stage = make_texture_stage(egg_tex); def._texture = DCAST(TextureAttrib, TextureAttrib::make())->add_on_stage(stage, tex); def._stage = stage; def._egg_tex = egg_tex; return true; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::apply_texture_attributes // Access: Private // Description: //////////////////////////////////////////////////////////////////// void EggLoader:: apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) { if (egg_tex->get_compression_mode() != EggTexture::CM_default) { tex->set_compression(convert_compression_mode(egg_tex->get_compression_mode())); } EggTexture::WrapMode wrap_u = egg_tex->determine_wrap_u(); EggTexture::WrapMode wrap_v = egg_tex->determine_wrap_v(); EggTexture::WrapMode wrap_w = egg_tex->determine_wrap_w(); if (wrap_u != EggTexture::WM_unspecified) { tex->set_wrap_u(convert_wrap_mode(wrap_u)); } if (wrap_v != EggTexture::WM_unspecified) { tex->set_wrap_v(convert_wrap_mode(wrap_v)); } if (wrap_w != EggTexture::WM_unspecified) { tex->set_wrap_w(convert_wrap_mode(wrap_w)); } if (egg_tex->has_border_color()) { tex->set_border_color(egg_tex->get_border_color()); } switch (egg_tex->get_minfilter()) { case EggTexture::FT_nearest: tex->set_minfilter(Texture::FT_nearest); break; case EggTexture::FT_linear: if (egg_ignore_filters) { egg2pg_cat.warning() << "Ignoring minfilter request\n"; tex->set_minfilter(Texture::FT_nearest); } else { tex->set_minfilter(Texture::FT_linear); } break; case EggTexture::FT_nearest_mipmap_nearest: if (egg_ignore_filters) { egg2pg_cat.warning() << "Ignoring minfilter request\n"; tex->set_minfilter(Texture::FT_nearest); } else if (egg_ignore_mipmaps) { egg2pg_cat.warning() << "Ignoring mipmap request\n"; tex->set_minfilter(Texture::FT_nearest); } else { tex->set_minfilter(Texture::FT_nearest_mipmap_nearest); } break; case EggTexture::FT_linear_mipmap_nearest: if (egg_ignore_filters) { egg2pg_cat.warning() << "Ignoring minfilter request\n"; tex->set_minfilter(Texture::FT_nearest); } else if (egg_ignore_mipmaps) { egg2pg_cat.warning() << "Ignoring mipmap request\n"; tex->set_minfilter(Texture::FT_linear); } else { tex->set_minfilter(Texture::FT_linear_mipmap_nearest); } break; case EggTexture::FT_nearest_mipmap_linear: if (egg_ignore_filters) { egg2pg_cat.warning() << "Ignoring minfilter request\n"; tex->set_minfilter(Texture::FT_nearest); } else if (egg_ignore_mipmaps) { egg2pg_cat.warning() << "Ignoring mipmap request\n"; tex->set_minfilter(Texture::FT_nearest); } else { tex->set_minfilter(Texture::FT_nearest_mipmap_linear); } break; case EggTexture::FT_linear_mipmap_linear: if (egg_ignore_filters) { egg2pg_cat.warning() << "Ignoring minfilter request\n"; tex->set_minfilter(Texture::FT_nearest); } else if (egg_ignore_mipmaps) { egg2pg_cat.warning() << "Ignoring mipmap request\n"; tex->set_minfilter(Texture::FT_linear); } else { tex->set_minfilter(Texture::FT_linear_mipmap_linear); } break; case EggTexture::FT_unspecified: break; } switch (egg_tex->get_magfilter()) { case EggTexture::FT_nearest: case EggTexture::FT_nearest_mipmap_nearest: case EggTexture::FT_nearest_mipmap_linear: tex->set_magfilter(Texture::FT_nearest); break; case EggTexture::FT_linear: case EggTexture::FT_linear_mipmap_nearest: case EggTexture::FT_linear_mipmap_linear: if (egg_ignore_filters) { egg2pg_cat.warning() << "Ignoring magfilter request\n"; tex->set_magfilter(Texture::FT_nearest); } else { tex->set_magfilter(Texture::FT_linear); } break; case EggTexture::FT_unspecified: break; } if (egg_tex->has_anisotropic_degree()) { tex->set_anisotropic_degree(egg_tex->get_anisotropic_degree()); } if (tex->get_num_components() == 1) { switch (egg_tex->get_format()) { case EggTexture::F_red: tex->set_format(Texture::F_red); break; case EggTexture::F_green: tex->set_format(Texture::F_green); break; case EggTexture::F_blue: tex->set_format(Texture::F_blue); break; case EggTexture::F_alpha: tex->set_format(Texture::F_alpha); break; case EggTexture::F_luminance: tex->set_format(Texture::F_luminance); break; case EggTexture::F_unspecified: break; default: egg2pg_cat.warning() << "Ignoring inappropriate format " << egg_tex->get_format() << " for 1-component texture " << egg_tex->get_name() << "\n"; } } else if (tex->get_num_components() == 2) { switch (egg_tex->get_format()) { case EggTexture::F_luminance_alpha: tex->set_format(Texture::F_luminance_alpha); break; case EggTexture::F_luminance_alphamask: tex->set_format(Texture::F_luminance_alphamask); break; case EggTexture::F_unspecified: break; default: egg2pg_cat.warning() << "Ignoring inappropriate format " << egg_tex->get_format() << " for 2-component texture " << egg_tex->get_name() << "\n"; } } else if (tex->get_num_components() == 3) { switch (egg_tex->get_format()) { case EggTexture::F_rgb: tex->set_format(Texture::F_rgb); break; case EggTexture::F_rgb12: if (tex->get_component_width() >= 2) { // Only do this if the component width supports it. tex->set_format(Texture::F_rgb12); } else { egg2pg_cat.warning() << "Ignoring inappropriate format " << egg_tex->get_format() << " for 8-bit texture " << egg_tex->get_name() << "\n"; } break; case EggTexture::F_rgb8: case EggTexture::F_rgba8: // We'll quietly accept RGBA8 for a 3-component texture, since // flt2egg generates these for 3-component as well as for // 4-component textures. tex->set_format(Texture::F_rgb8); break; case EggTexture::F_rgb5: tex->set_format(Texture::F_rgb5); break; case EggTexture::F_rgb332: tex->set_format(Texture::F_rgb332); break; case EggTexture::F_unspecified: break; default: egg2pg_cat.warning() << "Ignoring inappropriate format " << egg_tex->get_format() << " for 3-component texture " << egg_tex->get_name() << "\n"; } } else if (tex->get_num_components() == 4) { switch (egg_tex->get_format()) { case EggTexture::F_rgba: tex->set_format(Texture::F_rgba); break; case EggTexture::F_rgbm: tex->set_format(Texture::F_rgbm); break; case EggTexture::F_rgba12: if (tex->get_component_width() >= 2) { // Only do this if the component width supports it. tex->set_format(Texture::F_rgba12); } else { egg2pg_cat.warning() << "Ignoring inappropriate format " << egg_tex->get_format() << " for 8-bit texture " << egg_tex->get_name() << "\n"; } break; case EggTexture::F_rgba8: tex->set_format(Texture::F_rgba8); break; case EggTexture::F_rgba4: tex->set_format(Texture::F_rgba4); break; case EggTexture::F_rgba5: tex->set_format(Texture::F_rgba5); break; case EggTexture::F_unspecified: break; default: egg2pg_cat.warning() << "Ignoring inappropriate format " << egg_tex->get_format() << " for 4-component texture " << egg_tex->get_name() << "\n"; } } switch (egg_tex->get_quality_level()) { case EggTexture::QL_unspecified: case EggTexture::QL_default: tex->set_quality_level(Texture::QL_default); break; case EggTexture::QL_fastest: tex->set_quality_level(Texture::QL_fastest); break; case EggTexture::QL_normal: tex->set_quality_level(Texture::QL_normal); break; case EggTexture::QL_best: tex->set_quality_level(Texture::QL_best); break; } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::convert_compression_mode // Access: Private // Description: Returns the Texture::CompressionMode enum // corresponding to the EggTexture::CompressionMode. // Returns CM_default if the compression mode is // unspecified. //////////////////////////////////////////////////////////////////// Texture::CompressionMode EggLoader:: convert_compression_mode(EggTexture::CompressionMode compression_mode) const { switch (compression_mode) { case EggTexture::CM_off: return Texture::CM_off; case EggTexture::CM_on: return Texture::CM_on; case EggTexture::CM_fxt1: return Texture::CM_fxt1; case EggTexture::CM_dxt1: return Texture::CM_dxt1; case EggTexture::CM_dxt2: return Texture::CM_dxt2; case EggTexture::CM_dxt3: return Texture::CM_dxt3; case EggTexture::CM_dxt4: return Texture::CM_dxt4; case EggTexture::CM_dxt5: return Texture::CM_dxt5; case EggTexture::CM_default: return Texture::CM_default; } egg2pg_cat.warning() << "Unexpected texture compression flag: " << (int)compression_mode << "\n"; return Texture::CM_default; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::convert_wrap_mode // Access: Private // Description: Returns the Texture::WrapMode enum corresponding to // the EggTexture::WrapMode. Returns WM_repeat if the // wrap mode is unspecified. //////////////////////////////////////////////////////////////////// Texture::WrapMode EggLoader:: convert_wrap_mode(EggTexture::WrapMode wrap_mode) const { switch (wrap_mode) { case EggTexture::WM_clamp: return Texture::WM_clamp; case EggTexture::WM_repeat: return Texture::WM_repeat; case EggTexture::WM_mirror: return Texture::WM_mirror; case EggTexture::WM_mirror_once: return Texture::WM_mirror_once; case EggTexture::WM_border_color: return Texture::WM_border_color; case EggTexture::WM_unspecified: return Texture::WM_repeat; } egg2pg_cat.warning() << "Unexpected texture wrap flag: " << (int)wrap_mode << "\n"; return Texture::WM_repeat; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_texture_stage // Access: Private // Description: Creates a TextureStage object suitable for rendering // the indicated texture. //////////////////////////////////////////////////////////////////// PT(TextureStage) EggLoader:: make_texture_stage(const EggTexture *egg_tex) { // If the egg texture specifies any relevant TextureStage // properties, or if it is multitextured on top of anything else, it // gets its own texture stage; otherwise, it gets the default // texture stage. if (!egg_tex->has_stage_name() && !egg_tex->has_uv_name() && !egg_tex->has_color() && (egg_tex->get_env_type() == EggTexture::ET_unspecified || egg_tex->get_env_type() == EggTexture::ET_modulate) && egg_tex->get_combine_mode(EggTexture::CC_rgb) == EggTexture::CM_unspecified && egg_tex->get_combine_mode(EggTexture::CC_alpha) == EggTexture::CM_unspecified && !egg_tex->has_priority() && egg_tex->get_multitexture_sort() == 0 && !egg_tex->get_saved_result()) { return TextureStage::get_default(); } PT(TextureStage) stage = new TextureStage(egg_tex->get_stage_name()); switch (egg_tex->get_env_type()) { case EggTexture::ET_modulate: stage->set_mode(TextureStage::M_modulate); break; case EggTexture::ET_decal: stage->set_mode(TextureStage::M_decal); break; case EggTexture::ET_blend: stage->set_mode(TextureStage::M_blend); break; case EggTexture::ET_replace: stage->set_mode(TextureStage::M_replace); break; case EggTexture::ET_add: stage->set_mode(TextureStage::M_add); break; case EggTexture::ET_blend_color_scale: stage->set_mode(TextureStage::M_blend_color_scale); break; case EggTexture::ET_modulate_glow: stage->set_mode(TextureStage::M_modulate_glow); break; case EggTexture::ET_modulate_gloss: stage->set_mode(TextureStage::M_modulate_gloss); break; case EggTexture::ET_normal: stage->set_mode(TextureStage::M_normal); break; case EggTexture::ET_normal_height: stage->set_mode(TextureStage::M_normal_height); break; case EggTexture::ET_glow: stage->set_mode(TextureStage::M_glow); break; case EggTexture::ET_gloss: stage->set_mode(TextureStage::M_gloss); break; case EggTexture::ET_height: stage->set_mode(TextureStage::M_height); break; case EggTexture::ET_selector: stage->set_mode(TextureStage::M_selector); break; case EggTexture::ET_unspecified: break; } switch (egg_tex->get_combine_mode(EggTexture::CC_rgb)) { case EggTexture::CM_replace: stage->set_combine_rgb(get_combine_mode(egg_tex, EggTexture::CC_rgb), get_combine_source(egg_tex, EggTexture::CC_rgb, 0), get_combine_operand(egg_tex, EggTexture::CC_rgb, 0)); break; case EggTexture::CM_modulate: case EggTexture::CM_add: case EggTexture::CM_add_signed: case EggTexture::CM_subtract: case EggTexture::CM_dot3_rgb: case EggTexture::CM_dot3_rgba: stage->set_combine_rgb(get_combine_mode(egg_tex, EggTexture::CC_rgb), get_combine_source(egg_tex, EggTexture::CC_rgb, 0), get_combine_operand(egg_tex, EggTexture::CC_rgb, 0), get_combine_source(egg_tex, EggTexture::CC_rgb, 1), get_combine_operand(egg_tex, EggTexture::CC_rgb, 1)); break; case EggTexture::CM_interpolate: stage->set_combine_rgb(get_combine_mode(egg_tex, EggTexture::CC_rgb), get_combine_source(egg_tex, EggTexture::CC_rgb, 0), get_combine_operand(egg_tex, EggTexture::CC_rgb, 0), get_combine_source(egg_tex, EggTexture::CC_rgb, 1), get_combine_operand(egg_tex, EggTexture::CC_rgb, 1), get_combine_source(egg_tex, EggTexture::CC_rgb, 2), get_combine_operand(egg_tex, EggTexture::CC_rgb, 2)); break; case EggTexture::CM_unspecified: break; } switch (egg_tex->get_combine_mode(EggTexture::CC_alpha)) { case EggTexture::CM_replace: stage->set_combine_alpha(get_combine_mode(egg_tex, EggTexture::CC_alpha), get_combine_source(egg_tex, EggTexture::CC_alpha, 0), get_combine_operand(egg_tex, EggTexture::CC_alpha, 0)); break; case EggTexture::CM_modulate: case EggTexture::CM_add: case EggTexture::CM_add_signed: case EggTexture::CM_subtract: stage->set_combine_alpha(get_combine_mode(egg_tex, EggTexture::CC_alpha), get_combine_source(egg_tex, EggTexture::CC_alpha, 0), get_combine_operand(egg_tex, EggTexture::CC_alpha, 0), get_combine_source(egg_tex, EggTexture::CC_alpha, 1), get_combine_operand(egg_tex, EggTexture::CC_alpha, 1)); break; case EggTexture::CM_interpolate: stage->set_combine_alpha(get_combine_mode(egg_tex, EggTexture::CC_alpha), get_combine_source(egg_tex, EggTexture::CC_alpha, 0), get_combine_operand(egg_tex, EggTexture::CC_alpha, 0), get_combine_source(egg_tex, EggTexture::CC_alpha, 1), get_combine_operand(egg_tex, EggTexture::CC_alpha, 1), get_combine_source(egg_tex, EggTexture::CC_alpha, 2), get_combine_operand(egg_tex, EggTexture::CC_alpha, 2)); break; case EggTexture::CM_unspecified: case EggTexture::CM_dot3_rgb: case EggTexture::CM_dot3_rgba: break; } if (egg_tex->has_uv_name()) { PT(InternalName) name = InternalName::get_texcoord_name(egg_tex->get_uv_name()); stage->set_texcoord_name(name); } if (egg_tex->has_rgb_scale()) { stage->set_rgb_scale(egg_tex->get_rgb_scale()); } if (egg_tex->has_alpha_scale()) { stage->set_alpha_scale(egg_tex->get_alpha_scale()); } stage->set_saved_result(egg_tex->get_saved_result()); stage->set_sort(egg_tex->get_multitexture_sort() * 10); if (egg_tex->has_priority()) { stage->set_sort(egg_tex->get_priority()); } if (egg_tex->has_color()) { stage->set_color(egg_tex->get_color()); } return TextureStagePool::get_stage(stage); } //////////////////////////////////////////////////////////////////// // Function: EggLoader::separate_switches // Access: Private // Description: Walks the tree recursively, looking for EggPrimitives // that are children of sequence or switch nodes. If // any are found, they are moved within their own group // to protect them from being flattened with their // neighbors. //////////////////////////////////////////////////////////////////// void EggLoader:: separate_switches(EggNode *egg_node) { bool parent_has_switch = false; if (egg_node->is_of_type(EggGroup::get_class_type())) { EggGroup *egg_group = DCAST(EggGroup, egg_node); parent_has_switch = egg_group->get_switch_flag(); } if (egg_node->is_of_type(EggGroupNode::get_class_type())) { EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node); EggGroupNode::iterator ci; ci = egg_group->begin(); while (ci != egg_group->end()) { EggGroupNode::iterator cnext; cnext = ci; ++cnext; PT(EggNode) child = (*ci); if (parent_has_switch && child->is_of_type(EggPrimitive::get_class_type())) { // Move this child under a new node. PT(EggGroup) new_group = new EggGroup(child->get_name()); egg_group->replace(ci, new_group.p()); new_group->add_child(child); } separate_switches(child); ci = cnext; } } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::emulate_bface // Access: Private // Description: Looks for EggPolygons with a bface flag applied to // them. Any such polygons are duplicated into a pair // of back-to-back polygons, and the bface flag is // removed. //////////////////////////////////////////////////////////////////// void EggLoader:: emulate_bface(EggNode *egg_node) { if (egg_node->is_of_type(EggGroupNode::get_class_type())) { EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node); PT(EggGroupNode) dup_prims = new EggGroupNode; EggGroupNode::iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { PT(EggNode) child = (*ci); if (child->is_of_type(EggPolygon::get_class_type())) { EggPolygon *poly = DCAST(EggPolygon, child); if (poly->get_bface_flag()) { poly->set_bface_flag(false); PT(EggPolygon) dup_poly = new EggPolygon(*poly); dup_poly->reverse_vertex_ordering(); if (dup_poly->has_normal()) { dup_poly->set_normal(-dup_poly->get_normal()); } // Also reverse the normal on any vertices. EggPolygon::iterator vi; for (vi = dup_poly->begin(); vi != dup_poly->end(); ++vi) { EggVertex *vertex = (*vi); if (vertex->has_normal()) { EggVertex dup_vertex(*vertex); dup_vertex.set_normal(-dup_vertex.get_normal()); EggVertex *new_vertex = vertex->get_pool()->create_unique_vertex(dup_vertex); if (new_vertex != vertex) { new_vertex->copy_grefs_from(*vertex); dup_poly->replace(vi, new_vertex); } } } dup_prims->add_child(dup_poly); } } emulate_bface(child); } // Now that we've iterated through all the children, add in any // duplicated polygons we generated. egg_group->steal_children(*dup_prims); } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_node // Access: Private // Description: //////////////////////////////////////////////////////////////////// PandaNode *EggLoader:: make_node(EggNode *egg_node, PandaNode *parent) { if (egg_node->is_of_type(EggBin::get_class_type())) { return make_node(DCAST(EggBin, egg_node), parent); } else if (egg_node->is_of_type(EggGroup::get_class_type())) { return make_node(DCAST(EggGroup, egg_node), parent); } else if (egg_node->is_of_type(EggTable::get_class_type())) { return make_node(DCAST(EggTable, egg_node), parent); } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) { return make_node(DCAST(EggGroupNode, egg_node), parent); } return (PandaNode *)NULL; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_node (EggBin) // Access: Private // Description: //////////////////////////////////////////////////////////////////// PandaNode *EggLoader:: make_node(EggBin *egg_bin, PandaNode *parent) { // An EggBin might mean an LOD node (i.e. a parent of one or more // EggGroups with LOD specifications), or it might mean a polyset // node (a parent of one or more similar EggPrimitives). switch (egg_bin->get_bin_number()) { case EggBinner::BN_polyset: make_polyset(egg_bin, parent, NULL, _dynamic_override, _dynamic_override_char_maker); return NULL; case EggBinner::BN_lod: return make_lod(egg_bin, parent); case EggBinner::BN_nurbs_surface: { nassertr(!egg_bin->empty(), NULL); EggNode *child = egg_bin->get_first_child(); EggNurbsSurface *egg_nurbs; DCAST_INTO_R(egg_nurbs, child, NULL); const LMatrix4d &mat = egg_nurbs->get_vertex_to_node(); make_nurbs_surface(egg_nurbs, parent, mat); } return NULL; case EggBinner::BN_nurbs_curve: { nassertr(!egg_bin->empty(), NULL); EggNode *child = egg_bin->get_first_child(); EggNurbsCurve *egg_nurbs; DCAST_INTO_R(egg_nurbs, child, NULL); const LMatrix4d &mat = egg_nurbs->get_vertex_to_node(); make_nurbs_curve(egg_nurbs, parent, mat); } return NULL; case EggBinner::BN_none: break; } // Shouldn't get here. return (PandaNode *)NULL; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_lod // Access: Private // Description: //////////////////////////////////////////////////////////////////// PandaNode *EggLoader:: make_lod(EggBin *egg_bin, PandaNode *parent) { PT(LODNode) lod_node = LODNode::make_default_lod(egg_bin->get_name()); pvector instances; EggGroup::const_iterator ci; for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) { LODInstance instance(*ci); instances.push_back(instance); } // Now that we've created all of our children, put them in the // proper order and tell the LOD node about them. sort(instances.begin(), instances.end()); if (!instances.empty()) { // Set up the LOD node's center. All of the children should have // the same center, because that's how we binned them. lod_node->set_center(LCAST(float, instances[0]._d->_center)); } for (size_t i = 0; i < instances.size(); i++) { // Create the children in the proper order within the scene graph. const LODInstance &instance = instances[i]; make_node(instance._egg_node, lod_node); // All of the children should have the same center, because that's // how we binned them. nassertr(lod_node->get_center().almost_equal (LCAST(float, instance._d->_center), 0.01), NULL); // Tell the LOD node about this child's switching distances. lod_node->add_switch(instance._d->_switch_in, instance._d->_switch_out); } _groups[egg_bin] = lod_node; return create_group_arc(egg_bin, parent, lod_node); } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_node (EggGroup) // Access: Private // Description: //////////////////////////////////////////////////////////////////// PandaNode *EggLoader:: make_node(EggGroup *egg_group, PandaNode *parent) { PT(PandaNode) node = NULL; if (egg_group->get_dart_type() != EggGroup::DT_none) { // A group with the flag set means to create a character. bool structured = (egg_group->get_dart_type() == EggGroup::DT_structured); CharacterMaker char_maker(egg_group, *this, structured); node = char_maker.make_node(); if(structured) { //we're going to generate the rest of the children normally //except we'll be making dynamic geometry _dynamic_override = true; _dynamic_override_char_maker = &char_maker; EggGroup::const_iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { make_node(*ci, node); } _dynamic_override_char_maker = NULL; _dynamic_override = false; } } else if (egg_group->get_cs_type() != EggGroup::CST_none) { // A collision group: create collision geometry. node = new CollisionNode(egg_group->get_name()); make_collision_solids(egg_group, egg_group, (CollisionNode *)node.p()); if ((egg_group->get_collide_flags() & EggGroup::CF_keep) != 0) { // If we also specified to keep the geometry, continue the // traversal. In this case, we create a new PandaNode to be the // parent of the visible geometry and the collision geometry. PandaNode *combined = new PandaNode(""); parent->add_child(combined); combined->add_child(node); node = combined; EggGroup::const_iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { make_node(*ci, combined); } } node = create_group_arc(egg_group, parent, node); return node; } else if (egg_group->get_portal_flag()) { // Create a portal instead of a regular polyset. Scan the // children of this node looking for a polygon, similar to the // collision polygon case, above. PortalNode *pnode = new PortalNode(egg_group->get_name()); node = pnode; set_portal_polygon(egg_group, pnode); if (pnode->get_num_vertices() == 0) { egg2pg_cat.warning() << "Portal " << egg_group->get_name() << " has no vertices!\n"; } } else if (egg_group->get_occluder_flag()) { // Create an occluder instead of a regular polyset. Scan the // children of this node looking for a polygon, the same as the // portal polygon case, above. OccluderNode *pnode = new OccluderNode(egg_group->get_name()); node = pnode; set_occluder_polygon(egg_group, pnode); if (pnode->get_num_vertices() == 0) { egg2pg_cat.warning() << "Occluder " << egg_group->get_name() << " has no vertices!\n"; } } else if (egg_group->get_polylight_flag()) { // Create a polylight instead of a regular polyset. // use make_sphere to get the center, radius and color //egg2pg_cat.debug() << "polylight node\n"; LPoint3f center; Colorf color; float radius; if (!make_sphere(egg_group, EggGroup::CF_none, center, radius, color)) { egg2pg_cat.warning() << "Polylight " << egg_group->get_name() << " make_sphere failed!\n"; } PolylightNode *pnode = new PolylightNode(egg_group->get_name()); pnode->set_pos(center); pnode->set_color(color); pnode->set_radius(radius); node = pnode; } else if (egg_group->get_switch_flag()) { if (egg_group->get_switch_fps() != 0.0) { // Create a sequence node. node = new SequenceNode(egg_group->get_name()); ((SequenceNode *)node.p())->set_frame_rate(egg_group->get_switch_fps()); _sequences.insert(node); } else { // Create a switch node. node = new SwitchNode(egg_group->get_name()); } EggGroup::const_iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { make_node(*ci, node); } } else if (egg_group->has_scrolling_uvs()) { node = new UvScrollNode(egg_group->get_name(), egg_group->get_scroll_u(), egg_group->get_scroll_v(), egg_group->get_scroll_r()); EggGroup::const_iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { make_node(*ci, node); } } else if (egg_group->get_model_flag() || egg_group->has_dcs_type()) { // A model or DCS flag; create a model node. node = new ModelNode(egg_group->get_name()); switch (egg_group->get_dcs_type()) { case EggGroup::DC_net: DCAST(ModelNode, node)->set_preserve_transform(ModelNode::PT_net); break; case EggGroup::DC_no_touch: DCAST(ModelNode, node)->set_preserve_transform(ModelNode::PT_no_touch); break; case EggGroup::DC_local: case EggGroup::DC_default: DCAST(ModelNode, node)->set_preserve_transform(ModelNode::PT_local); break; case EggGroup::DC_none: case EggGroup::DC_unspecified: break; } EggGroup::const_iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { make_node(*ci, node); } } else { // A normal group; just create a normal node, and traverse. But // if all of the children of this group are polysets, anticipate // this for the benefit of smaller grouping, and create a single // GeomNode for all of the children. bool all_polysets = false; bool any_hidden = false; // We don't want to ever create a GeomNode under a "decal" flag, // since that can confuse the decal reparenting. if (!egg_group->determine_decal()) { check_for_polysets(egg_group, all_polysets, any_hidden); } if (all_polysets && !any_hidden) { node = new GeomNode(egg_group->get_name()); } else { node = new PandaNode(egg_group->get_name()); } EggGroup::const_iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { make_node(*ci, node); } } if (node == (PandaNode *)NULL) { return NULL; } // Associate any instances with this node. int num_group_refs = egg_group->get_num_group_refs(); for (int gri = 0; gri < num_group_refs; ++gri) { EggGroup *group_ref = egg_group->get_group_ref(gri); Groups::const_iterator gi = _groups.find(group_ref); if (gi != _groups.end()) { PandaNode *node_ref = (*gi).second; node->add_child(node_ref); } } _groups[egg_group] = node; return create_group_arc(egg_group, parent, node); } //////////////////////////////////////////////////////////////////// // Function: EggLoader::create_group_arc // Access: Private // Description: Creates the arc parenting a new group to the scene // graph, and applies any relevant attribs to the // arc according to the EggGroup node that inspired the // group. //////////////////////////////////////////////////////////////////// PandaNode *EggLoader:: create_group_arc(EggGroup *egg_group, PandaNode *parent, PandaNode *node) { parent->add_child(node); // If the group had a transform, apply it to the node. if (egg_group->has_transform()) { CPT(TransformState) transform = make_transform(egg_group); node->set_transform(transform); node->set_prev_transform(transform); } // If the group has a billboard flag, apply that. switch (egg_group->get_billboard_type()) { case EggGroup::BT_point_camera_relative: node->set_effect(BillboardEffect::make_point_eye()); break; case EggGroup::BT_point_world_relative: node->set_effect(BillboardEffect::make_point_world()); break; case EggGroup::BT_axis: node->set_effect(BillboardEffect::make_axis()); break; case EggGroup::BT_none: break; } if (egg_group->get_decal_flag()) { if (egg_ignore_decals) { egg2pg_cat.error() << "Ignoring decal flag on " << egg_group->get_name() << "\n"; _error = true; } // If the group has the "decal" flag set, it means that all of the // descendant groups will be decaled onto the geometry within // this group. This means we'll need to reparent things a bit // afterward. _decals.insert(node); } // Copy all the tags from the group onto the node. EggGroup::TagData::const_iterator ti; for (ti = egg_group->tag_begin(); ti != egg_group->tag_end(); ++ti) { node->set_tag((*ti).first, (*ti).second); } if (egg_group->get_blend_mode() != EggGroup::BM_unspecified && egg_group->get_blend_mode() != EggGroup::BM_none) { // Apply a ColorBlendAttrib to the group. ColorBlendAttrib::Mode mode = get_color_blend_mode(egg_group->get_blend_mode()); ColorBlendAttrib::Operand a = get_color_blend_operand(egg_group->get_blend_operand_a()); ColorBlendAttrib::Operand b = get_color_blend_operand(egg_group->get_blend_operand_b()); Colorf color = egg_group->get_blend_color(); node->set_attrib(ColorBlendAttrib::make(mode, a, b, color)); } // If the group specified some property that should propagate down // to the leaves, we have to remember this node and apply the // property later, after we've created the actual geometry. DeferredNodeProperty def; if (egg_group->has_collide_mask()) { def._from_collide_mask = egg_group->get_collide_mask(); def._into_collide_mask = egg_group->get_collide_mask(); def._flags |= DeferredNodeProperty::F_has_from_collide_mask | DeferredNodeProperty::F_has_into_collide_mask; } if (egg_group->has_from_collide_mask()) { def._from_collide_mask = egg_group->get_from_collide_mask(); def._flags |= DeferredNodeProperty::F_has_from_collide_mask; } if (egg_group->has_into_collide_mask()) { def._into_collide_mask = egg_group->get_into_collide_mask(); def._flags |= DeferredNodeProperty::F_has_into_collide_mask; } if (def._flags != 0) { _deferred_nodes[node] = def; } return node; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_node (EggTable) // Access: Private // Description: //////////////////////////////////////////////////////////////////// PandaNode *EggLoader:: make_node(EggTable *egg_table, PandaNode *parent) { if (egg_table->get_table_type() != EggTable::TT_bundle) { // We only do anything with bundles. Isolated tables are treated // as ordinary groups. return make_node(DCAST(EggGroupNode, egg_table), parent); } // It's an actual bundle, so make an AnimBundle from it and its // descendants. AnimBundleMaker bundle_maker(egg_table); AnimBundleNode *node = bundle_maker.make_node(); parent->add_child(node); return node; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_node (EggGroupNode) // Access: Private // Description: //////////////////////////////////////////////////////////////////// PandaNode *EggLoader:: make_node(EggGroupNode *egg_group, PandaNode *parent) { PandaNode *node = new PandaNode(egg_group->get_name()); EggGroupNode::const_iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { make_node(*ci, node); } parent->add_child(node); return node; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::check_for_polysets // Access: Private // Description: Sets all_polysets true if all of the children of this // node represent a polyset. Sets any_hidden true if // any of those polysets are flagged hidden. //////////////////////////////////////////////////////////////////// void EggLoader:: check_for_polysets(EggGroup *egg_group, bool &all_polysets, bool &any_hidden) { all_polysets = (!egg_group->empty()); any_hidden = false; EggGroup::const_iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { if ((*ci)->is_of_type(EggBin::get_class_type())) { EggBin *egg_bin = DCAST(EggBin, (*ci)); if (egg_bin->get_bin_number() == EggBinner::BN_polyset) { // We know that all of the primitives in the bin have the same // render state, so we can get that information from the first // primitive. EggGroup::const_iterator bci = egg_bin->begin(); nassertv(bci != egg_bin->end()); const EggPrimitive *first_prim; DCAST_INTO_V(first_prim, (*bci)); const EggRenderState *render_state; DCAST_INTO_V(render_state, first_prim->get_user_data(EggRenderState::get_class_type())); if (render_state->_hidden) { any_hidden = true; } } else { all_polysets = false; return; } } else if ((*ci)->is_of_type(EggGroup::get_class_type())) { // Other kinds of children, like vertex pools, comments, // textures, etc., are ignored; but groups indicate more nodes, // so if we find a nested group it means we're not all polysets. all_polysets = false; return; } } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_vertex_data // Access: Private // Description: Creates a GeomVertexData structure from the vertex // pool, for the indicated transform space. If a // GeomVertexData has already been created for this // transform, just returns it. //////////////////////////////////////////////////////////////////// PT(GeomVertexData) EggLoader:: make_vertex_data(const EggRenderState *render_state, EggVertexPool *vertex_pool, EggNode *primitive_home, const LMatrix4d &transform, bool is_dynamic, CharacterMaker *character_maker, bool ignore_color) { VertexPoolTransform vpt; vpt._vertex_pool = vertex_pool; vpt._bake_in_uvs = render_state->_bake_in_uvs; vpt._transform = transform; VertexPoolData::iterator di; di = _vertex_pool_data.find(vpt); if (di != _vertex_pool_data.end()) { return (*di).second; } // Decide on the format for the vertices. PT(GeomVertexArrayFormat) array_format = new GeomVertexArrayFormat; array_format->add_column (InternalName::get_vertex(), vertex_pool->get_num_dimensions(), Geom::NT_float32, Geom::C_point); if (vertex_pool->has_normals()) { array_format->add_column (InternalName::get_normal(), 3, Geom::NT_float32, Geom::C_vector); } if (!ignore_color) { array_format->add_column (InternalName::get_color(), 1, Geom::NT_packed_dabc, Geom::C_color); } vector_string uv_names, uvw_names, tbn_names; vertex_pool->get_uv_names(uv_names, uvw_names, tbn_names); vector_string::const_iterator ni; for (ni = uv_names.begin(); ni != uv_names.end(); ++ni) { string name = (*ni); PT(InternalName) iname = InternalName::get_texcoord_name(name); if (find(uvw_names.begin(), uvw_names.end(), name) != uvw_names.end()) { // This one actually represents 3-d texture coordinates. array_format->add_column (iname, 3, Geom::NT_float32, Geom::C_texcoord); } else { array_format->add_column (iname, 2, Geom::NT_float32, Geom::C_texcoord); } } for (ni = tbn_names.begin(); ni != tbn_names.end(); ++ni) { string name = (*ni); PT(InternalName) iname = InternalName::get_tangent_name(name); array_format->add_column (iname, 3, Geom::NT_float32, Geom::C_vector); iname = InternalName::get_binormal_name(name); array_format->add_column (iname, 3, Geom::NT_float32, Geom::C_vector); } PT(GeomVertexFormat) temp_format = new GeomVertexFormat(array_format); PT(TransformBlendTable) blend_table; PT(SliderTable) slider_table; string name = _data->get_egg_filename().get_basename_wo_extension(); if (is_dynamic) { // If it's a dynamic object, we need a TransformBlendTable and // maybe a SliderTable, and additional columns in the vertex data: // one that indexes into the blend table per vertex, and also // one for each different type of morph delta. // Tell the format that we're setting it up for Panda-based // animation. GeomVertexAnimationSpec animation; animation.set_panda(); temp_format->set_animation(animation); blend_table = new TransformBlendTable; blend_table->set_rows(SparseArray::lower_on(vertex_pool->size())); PT(GeomVertexArrayFormat) anim_array_format = new GeomVertexArrayFormat; anim_array_format->add_column (InternalName::get_transform_blend(), 1, Geom::NT_uint16, Geom::C_index); temp_format->add_array(anim_array_format); pmap slider_names; EggVertexPool::const_iterator vi; for (vi = vertex_pool->begin(); vi != vertex_pool->end(); ++vi) { EggVertex *vertex = (*vi); EggMorphVertexList::const_iterator mvi; for (mvi = vertex->_dxyzs.begin(); mvi != vertex->_dxyzs.end(); ++mvi) { slider_names[(*mvi).get_name()].set_bit(vertex->get_index()); record_morph(anim_array_format, character_maker, (*mvi).get_name(), InternalName::get_vertex(), 3); } if (vertex->has_normal()) { EggMorphNormalList::const_iterator mni; for (mni = vertex->_dnormals.begin(); mni != vertex->_dnormals.end(); ++mni) { slider_names[(*mni).get_name()].set_bit(vertex->get_index()); record_morph(anim_array_format, character_maker, (*mni).get_name(), InternalName::get_normal(), 3); } } if (!ignore_color && vertex->has_color()) { EggMorphColorList::const_iterator mci; for (mci = vertex->_drgbas.begin(); mci != vertex->_drgbas.end(); ++mci) { slider_names[(*mci).get_name()].set_bit(vertex->get_index()); record_morph(anim_array_format, character_maker, (*mci).get_name(), InternalName::get_color(), 4); } } EggVertex::const_uv_iterator uvi; for (uvi = vertex->uv_begin(); uvi != vertex->uv_end(); ++uvi) { EggVertexUV *egg_uv = (*uvi); string name = egg_uv->get_name(); bool has_w = (find(uvw_names.begin(), uvw_names.end(), name) != uvw_names.end()); PT(InternalName) iname = InternalName::get_texcoord_name(name); EggMorphTexCoordList::const_iterator mti; for (mti = egg_uv->_duvs.begin(); mti != egg_uv->_duvs.end(); ++mti) { slider_names[(*mti).get_name()].set_bit(vertex->get_index()); record_morph(anim_array_format, character_maker, (*mti).get_name(), iname, has_w ? 3 : 2); } } } if (!slider_names.empty()) { // If we have any sliders at all, create a table for them. slider_table = new SliderTable; pmap::iterator si; for (si = slider_names.begin(); si != slider_names.end(); ++si) { PT(VertexSlider) slider = character_maker->egg_to_slider((*si).first); slider_table->add_slider(slider, (*si).second); } } // We'll also assign the character name to the vertex data, so it // will show up in PStats. name = character_maker->get_name(); } CPT(GeomVertexFormat) format = GeomVertexFormat::register_format(temp_format); // Now create a new GeomVertexData using the indicated format. It // is actually correct to create it with UH_static even it // represents a dynamic object, because the vertex data itself won't // be changing--just the result of applying the animation is // dynamic. PT(GeomVertexData) vertex_data = new GeomVertexData(name, format, Geom::UH_static); vertex_data->reserve_num_rows(vertex_pool->size()); vertex_data->set_transform_blend_table(blend_table); if (slider_table != (SliderTable *)NULL) { vertex_data->set_slider_table(SliderTable::register_table(slider_table)); } // And fill the data from the vertex pool. EggVertexPool::const_iterator vi; for (vi = vertex_pool->begin(); vi != vertex_pool->end(); ++vi) { GeomVertexWriter gvw(vertex_data); EggVertex *vertex = (*vi); gvw.set_row(vertex->get_index()); gvw.set_column(InternalName::get_vertex()); gvw.add_data4f(LCAST(float, vertex->get_pos4() * transform)); if (is_dynamic) { EggMorphVertexList::const_iterator mvi; for (mvi = vertex->_dxyzs.begin(); mvi != vertex->_dxyzs.end(); ++mvi) { const EggMorphVertex &morph = (*mvi); CPT(InternalName) delta_name = InternalName::get_morph(InternalName::get_vertex(), morph.get_name()); gvw.set_column(delta_name); gvw.add_data3f(LCAST(float, morph.get_offset() * transform)); } } if (vertex->has_normal()) { gvw.set_column(InternalName::get_normal()); Normald orig_normal = vertex->get_normal(); Normald transformed_normal = normalize(orig_normal * transform); gvw.add_data3f(LCAST(float, transformed_normal)); if (is_dynamic) { EggMorphNormalList::const_iterator mni; for (mni = vertex->_dnormals.begin(); mni != vertex->_dnormals.end(); ++mni) { const EggMorphNormal &morph = (*mni); CPT(InternalName) delta_name = InternalName::get_morph(InternalName::get_normal(), morph.get_name()); gvw.set_column(delta_name); Normald morphed_normal = orig_normal + morph.get_offset(); Normald transformed_morphed_normal = normalize(morphed_normal * transform); LVector3d delta = transformed_morphed_normal - transformed_normal; gvw.add_data3f(LCAST(float, delta)); } } } if (!ignore_color && vertex->has_color()) { gvw.set_column(InternalName::get_color()); gvw.add_data4f(vertex->get_color()); if (is_dynamic) { EggMorphColorList::const_iterator mci; for (mci = vertex->_drgbas.begin(); mci != vertex->_drgbas.end(); ++mci) { const EggMorphColor &morph = (*mci); CPT(InternalName) delta_name = InternalName::get_morph(InternalName::get_color(), morph.get_name()); gvw.set_column(delta_name); gvw.add_data4f(morph.get_offset()); } } } EggVertex::const_uv_iterator uvi; for (uvi = vertex->uv_begin(); uvi != vertex->uv_end(); ++uvi) { EggVertexUV *egg_uv = (*uvi); TexCoord3d orig_uvw = egg_uv->get_uvw(); TexCoord3d uvw = egg_uv->get_uvw(); string name = egg_uv->get_name(); PT(InternalName) iname = InternalName::get_texcoord_name(name); gvw.set_column(iname); BakeInUVs::const_iterator buv = render_state->_bake_in_uvs.find(iname); if (buv != render_state->_bake_in_uvs.end()) { // If we are to bake in a texture matrix, do so now. uvw = uvw * (*buv).second->get_transform3d(); } gvw.set_data3f(LCAST(float, uvw)); if (is_dynamic) { EggMorphTexCoordList::const_iterator mti; for (mti = egg_uv->_duvs.begin(); mti != egg_uv->_duvs.end(); ++mti) { const EggMorphTexCoord &morph = (*mti); CPT(InternalName) delta_name = InternalName::get_morph(iname, morph.get_name()); gvw.set_column(delta_name); TexCoord3d duvw = morph.get_offset(); if (buv != render_state->_bake_in_uvs.end()) { TexCoord3d new_uvw = orig_uvw + duvw; duvw = (new_uvw * (*buv).second->get_transform3d()) - uvw; } gvw.add_data3f(LCAST(float, duvw)); } } // Also add the tangent and binormal, if present. if (egg_uv->has_tangent() && egg_uv->has_binormal()) { PT(InternalName) iname = InternalName::get_tangent_name(name); gvw.set_column(iname); if (gvw.has_column()) { LVector3d tangent = egg_uv->get_tangent(); LVector3d binormal = egg_uv->get_binormal(); gvw.add_data3f(LCAST(float, tangent)); gvw.set_column(InternalName::get_binormal_name(name)); gvw.add_data3f(LCAST(float, binormal)); } } } if (is_dynamic) { // Figure out the transforms affecting this particular vertex. TransformBlend blend; if (vertex->gref_size() == 0) { // If the vertex has no explicit membership, it belongs right // where it is. PT(VertexTransform) vt = character_maker->egg_to_transform(primitive_home); nassertr(vt != (VertexTransform *)NULL, vertex_data); blend.add_transform(vt, 1.0f); } else { // If the vertex does have an explicit membership, ignore its // parentage and assign it where it wants to be. EggVertex::GroupRef::const_iterator gri; for (gri = vertex->gref_begin(); gri != vertex->gref_end(); ++gri) { EggGroup *egg_joint = (*gri); double membership = egg_joint->get_vertex_membership(vertex); PT(VertexTransform) vt = character_maker->egg_to_transform(egg_joint); nassertr(vt != (VertexTransform *)NULL, vertex_data); blend.add_transform(vt, membership); } } blend.normalize_weights(); int table_index = blend_table->add_blend(blend); gvw.set_column(InternalName::get_transform_blend()); gvw.set_data1i(table_index); } } bool inserted = _vertex_pool_data.insert (VertexPoolData::value_type(vpt, vertex_data)).second; nassertr(inserted, vertex_data); Thread::consider_yield(); return vertex_data; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::record_morph // Access: Private // Description: //////////////////////////////////////////////////////////////////// void EggLoader:: record_morph(GeomVertexArrayFormat *array_format, CharacterMaker *character_maker, const string &morph_name, InternalName *column_name, int num_components) { PT(InternalName) delta_name = InternalName::get_morph(column_name, morph_name); if (!array_format->has_column(delta_name)) { array_format->add_column (delta_name, num_components, Geom::NT_float32, Geom::C_morph_delta); } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_primitive // Access: Private // Description: Creates a GeomPrimitive corresponding to the // indicated EggPrimitive, and adds it to the set. //////////////////////////////////////////////////////////////////// void EggLoader:: make_primitive(const EggRenderState *render_state, EggPrimitive *egg_prim, EggLoader::UniquePrimitives &unique_primitives, EggLoader::Primitives &primitives, bool has_overall_color, const Colorf &overall_color) { PT(GeomPrimitive) primitive; if (egg_prim->is_of_type(EggPolygon::get_class_type())) { if (egg_prim->size() == 3) { primitive = new GeomTriangles(Geom::UH_static); } } else if (egg_prim->is_of_type(EggTriangleStrip::get_class_type())) { primitive = new GeomTristrips(Geom::UH_static); } else if (egg_prim->is_of_type(EggTriangleFan::get_class_type())) { primitive = new GeomTrifans(Geom::UH_static); } else if (egg_prim->is_of_type(EggLine::get_class_type())) { if (egg_prim->size() == 2) { primitive = new GeomLines(Geom::UH_static); } else { primitive = new GeomLinestrips(Geom::UH_static); } } else if (egg_prim->is_of_type(EggPoint::get_class_type())) { primitive = new GeomPoints(Geom::UH_static); } if (primitive == (GeomPrimitive *)NULL) { // Don't know how to make this kind of primitive. egg2pg_cat.warning() << "Ignoring " << egg_prim->get_type() << "\n"; return; } if (render_state->_flat_shaded) { primitive->set_shade_model(GeomPrimitive::SM_flat_first_vertex); } else if (egg_prim->get_shading() == EggPrimitive::S_overall) { primitive->set_shade_model(GeomPrimitive::SM_uniform); } else { primitive->set_shade_model(GeomPrimitive::SM_smooth); } // Insert the primitive into the set, but if we already have a // primitive of that type, reset the pointer to that one instead. PrimitiveUnifier pu(primitive); pair result = unique_primitives.insert(UniquePrimitives::value_type(pu, primitive)); if (result.second) { // This was the first primitive of this type. Store it. primitives.push_back(primitive); if (egg2pg_cat.is_debug()) { egg2pg_cat.debug() << "First primitive of type " << primitive->get_type() << ": " << primitive << "\n"; } } GeomPrimitive *orig_prim = (*result.first).second; // Make sure we don't try to put more than egg_max_indices into any // one GeomPrimitive. if (orig_prim->get_num_vertices() + egg_prim->size() <= (unsigned int)egg_max_indices) { primitive = orig_prim; } else if (orig_prim != primitive) { // If the old primitive is full, keep the new primitive from now // on. (*result.first).second = primitive; if (egg2pg_cat.is_debug()) { egg2pg_cat.debug() << "Next primitive of type " << primitive->get_type() << ": " << primitive << "\n"; } primitives.push_back(primitive); } // Now add the vertices. EggPrimitive::const_iterator vi; for (vi = egg_prim->begin(); vi != egg_prim->end(); ++vi) { primitive->add_vertex((*vi)->get_index()); } primitive->close_primitive(); } //////////////////////////////////////////////////////////////////// // Function: EggLoader::set_portal_polygon // Access: Private // Description: Defines the PortalNode from the first polygon found // within this group. //////////////////////////////////////////////////////////////////// void EggLoader:: set_portal_polygon(EggGroup *egg_group, PortalNode *pnode) { pnode->clear_vertices(); PT(EggPolygon) poly = find_first_polygon(egg_group); if (poly != (EggPolygon *)NULL) { LMatrix4d mat = poly->get_vertex_to_node(); EggPolygon::const_iterator vi; for (vi = poly->begin(); vi != poly->end(); ++vi) { Vertexd vert = (*vi)->get_pos3() * mat; pnode->add_vertex(LCAST(float, vert)); } } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::set_occluder_polygon // Access: Private // Description: Defines the OccluderNode from the first polygon found // within this group. //////////////////////////////////////////////////////////////////// void EggLoader:: set_occluder_polygon(EggGroup *egg_group, OccluderNode *pnode) { PT(EggPolygon) poly = find_first_polygon(egg_group); if (poly != (EggPolygon *)NULL) { if (poly->size() != 4) { egg2pg_cat.error() << "Invalid number of vertices for " << egg_group->get_name() << "\n"; } else { LMatrix4d mat = poly->get_vertex_to_node(); EggPolygon::const_iterator vi; LPoint3d v0 = (*poly)[0]->get_pos3() * mat; LPoint3d v1 = (*poly)[1]->get_pos3() * mat; LPoint3d v2 = (*poly)[2]->get_pos3() * mat; LPoint3d v3 = (*poly)[3]->get_pos3() * mat; pnode->set_vertices(LCAST(float, v0), LCAST(float, v1), LCAST(float, v2), LCAST(float, v3)); if (poly->get_bface_flag()) { pnode->set_double_sided(true); } } } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::find_first_polygon // Access: Private // Description: Returns the first EggPolygon found at or below the // indicated node. //////////////////////////////////////////////////////////////////// PT(EggPolygon) EggLoader:: find_first_polygon(EggGroup *egg_group) { // Does this group have any polygons? EggGroup::const_iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { if ((*ci)->is_of_type(EggPolygon::get_class_type())) { // Yes! Return the polygon. return DCAST(EggPolygon, (*ci)); } } // Well, the group had no polygons; look for a child group that // does. for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { if ((*ci)->is_of_type(EggGroup::get_class_type())) { EggGroup *child_group = DCAST(EggGroup, *ci); PT(EggPolygon) found = find_first_polygon(child_group); if (found != (EggPolygon *)NULL) { return found; } } } // We got nothing. return NULL; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_sphere // Access: Private // Description: Creates a single generic Sphere corresponding // to the polygons associated with this group. // This sphere is used by make_collision_sphere and // Polylight sphere. It could be used for other spheres. //////////////////////////////////////////////////////////////////// bool EggLoader:: make_sphere(EggGroup *egg_group, EggGroup::CollideFlags flags, LPoint3f ¢er, float &radius, Colorf &color) { bool success=false; EggGroup *geom_group = find_collision_geometry(egg_group, flags); if (geom_group != (EggGroup *)NULL) { // Collect all of the vertices. pset vertices; EggGroup::const_iterator ci; for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) { if ((*ci)->is_of_type(EggPrimitive::get_class_type())) { EggPrimitive *prim = DCAST(EggPrimitive, *ci); EggPrimitive::const_iterator pi; for (pi = prim->begin(); pi != prim->end(); ++pi) { vertices.insert(*pi); } } } // Now average together all of the vertices to get a center. int num_vertices = 0; LPoint3d d_center(0.0, 0.0, 0.0); pset::const_iterator vi; for (vi = vertices.begin(); vi != vertices.end(); ++vi) { EggVertex *vtx = (*vi); d_center += vtx->get_pos3(); num_vertices++; } if (num_vertices > 0) { d_center /= (double)num_vertices; //egg2pg_cat.debug() << "make_sphere d_center: " << d_center << "\n"; LMatrix4d mat = egg_group->get_vertex_to_node(); d_center = d_center * mat; // And the furthest vertex determines the radius. double radius2 = 0.0; for (vi = vertices.begin(); vi != vertices.end(); ++vi) { EggVertex *vtx = (*vi); LPoint3d p3 = vtx->get_pos3(); LVector3d v = p3 * mat - d_center; radius2 = max(radius2, v.length_squared()); } center = LCAST(float,d_center); radius = sqrtf(radius2); //egg2pg_cat.debug() << "make_sphere radius: " << radius << "\n"; vi = vertices.begin(); EggVertex *clr_vtx = (*vi); color = clr_vtx->get_color(); success = true; } } return success; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_collision_solids // Access: Private // Description: Creates CollisionSolids corresponding to the // collision geometry indicated at the given node and // below. //////////////////////////////////////////////////////////////////// void EggLoader:: make_collision_solids(EggGroup *start_group, EggGroup *egg_group, CollisionNode *cnode) { if (egg_group->get_cs_type() != EggGroup::CST_none) { start_group = egg_group; } switch (start_group->get_cs_type()) { case EggGroup::CST_none: // No collision flags; do nothing. Don't even traverse further. return; case EggGroup::CST_plane: make_collision_plane(egg_group, cnode, start_group->get_collide_flags()); break; case EggGroup::CST_polygon: make_collision_polygon(egg_group, cnode, start_group->get_collide_flags()); break; case EggGroup::CST_polyset: make_collision_polyset(egg_group, cnode, start_group->get_collide_flags()); break; case EggGroup::CST_sphere: make_collision_sphere(egg_group, cnode, start_group->get_collide_flags()); break; case EggGroup::CST_inv_sphere: make_collision_inv_sphere(egg_group, cnode, start_group->get_collide_flags()); break; case EggGroup::CST_tube: make_collision_tube(egg_group, cnode, start_group->get_collide_flags()); break; case EggGroup::CST_floor_mesh: make_collision_floor_mesh(egg_group, cnode, start_group->get_collide_flags()); break; } if ((start_group->get_collide_flags() & EggGroup::CF_descend) != 0) { // Now pick up everything below. EggGroup::const_iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { if ((*ci)->is_of_type(EggGroup::get_class_type())) { make_collision_solids(start_group, DCAST(EggGroup, *ci), cnode); } } } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_collision_plane // Access: Private // Description: Creates a single CollisionPlane corresponding // to the first polygon associated with this group. //////////////////////////////////////////////////////////////////// void EggLoader:: make_collision_plane(EggGroup *egg_group, CollisionNode *cnode, EggGroup::CollideFlags flags) { EggGroup *geom_group = find_collision_geometry(egg_group, flags); if (geom_group != (EggGroup *)NULL) { EggGroup::const_iterator ci; for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) { if ((*ci)->is_of_type(EggPolygon::get_class_type())) { CollisionPlane *csplane = create_collision_plane(DCAST(EggPolygon, *ci), egg_group); if (csplane != (CollisionPlane *)NULL) { apply_collision_flags(csplane, flags); cnode->add_solid(csplane); return; } } else if ((*ci)->is_of_type(EggCompositePrimitive::get_class_type())) { EggCompositePrimitive *comp = DCAST(EggCompositePrimitive, *ci); PT(EggGroup) temp_group = new EggGroup; if (comp->triangulate_into(temp_group)) { make_collision_plane(temp_group, cnode, flags); return; } } } } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_collision_floor_mesh // Access: Private // Description: Creates a single CollisionPolygon corresponding // to the first polygon associated with this group. //////////////////////////////////////////////////////////////////// void EggLoader:: make_collision_floor_mesh(EggGroup *egg_group, CollisionNode *cnode, EggGroup::CollideFlags flags) { EggGroup *geom_group = find_collision_geometry(egg_group, flags); if (geom_group != (EggGroup *)NULL) { create_collision_floor_mesh(cnode, geom_group,flags); } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_collision_polygon // Access: Private // Description: Creates a single CollisionPolygon corresponding // to the first polygon associated with this group. //////////////////////////////////////////////////////////////////// void EggLoader:: make_collision_polygon(EggGroup *egg_group, CollisionNode *cnode, EggGroup::CollideFlags flags) { EggGroup *geom_group = find_collision_geometry(egg_group, flags); if (geom_group != (EggGroup *)NULL) { EggGroup::const_iterator ci; for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) { if ((*ci)->is_of_type(EggPolygon::get_class_type())) { create_collision_polygons(cnode, DCAST(EggPolygon, *ci), egg_group, flags); } else if ((*ci)->is_of_type(EggCompositePrimitive::get_class_type())) { EggCompositePrimitive *comp = DCAST(EggCompositePrimitive, *ci); PT(EggGroup) temp_group = new EggGroup; if (comp->triangulate_into(temp_group)) { make_collision_polygon(temp_group, cnode, flags); return; } } } } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_collision_polyset // Access: Private // Description: Creates a series of CollisionPolygons corresponding // to the polygons associated with this group. //////////////////////////////////////////////////////////////////// void EggLoader:: make_collision_polyset(EggGroup *egg_group, CollisionNode *cnode, EggGroup::CollideFlags flags) { EggGroup *geom_group = find_collision_geometry(egg_group, flags); if (geom_group != (EggGroup *)NULL) { EggGroup::const_iterator ci; for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) { if ((*ci)->is_of_type(EggPolygon::get_class_type())) { create_collision_polygons(cnode, DCAST(EggPolygon, *ci), egg_group, flags); } else if ((*ci)->is_of_type(EggCompositePrimitive::get_class_type())) { EggCompositePrimitive *comp = DCAST(EggCompositePrimitive, *ci); PT(EggGroup) temp_group = new EggGroup; if (comp->triangulate_into(temp_group)) { make_collision_polyset(temp_group, cnode, flags); } } } } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_collision_sphere // Access: Private // Description: Creates a single CollisionSphere corresponding // to the polygons associated with this group. //////////////////////////////////////////////////////////////////// void EggLoader:: make_collision_sphere(EggGroup *egg_group, CollisionNode *cnode, EggGroup::CollideFlags flags) { LPoint3f center; float radius; Colorf dummycolor; if (make_sphere(egg_group, flags, center, radius, dummycolor)) { CollisionSphere *cssphere = new CollisionSphere(center, radius); apply_collision_flags(cssphere, flags); cnode->add_solid(cssphere); } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_collision_inv_sphere // Access: Private // Description: Creates a single CollisionInvSphere corresponding // to the polygons associated with this group. //////////////////////////////////////////////////////////////////// void EggLoader:: make_collision_inv_sphere(EggGroup *egg_group, CollisionNode *cnode, EggGroup::CollideFlags flags) { LPoint3f center; float radius; Colorf dummycolor; if (make_sphere(egg_group, flags, center, radius, dummycolor)) { CollisionInvSphere *cssphere = new CollisionInvSphere(center, radius); apply_collision_flags(cssphere, flags); cnode->add_solid(cssphere); } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::make_collision_tube // Access: Private // Description: Creates a single CollisionTube corresponding // to the polygons associated with this group. //////////////////////////////////////////////////////////////////// void EggLoader:: make_collision_tube(EggGroup *egg_group, CollisionNode *cnode, EggGroup::CollideFlags flags) { EggGroup *geom_group = find_collision_geometry(egg_group, flags); if (geom_group != (EggGroup *)NULL) { // Collect all of the vertices. pset vertices; EggGroup::const_iterator ci; for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) { if ((*ci)->is_of_type(EggPrimitive::get_class_type())) { EggPrimitive *prim = DCAST(EggPrimitive, *ci); EggPrimitive::const_iterator pi; for (pi = prim->begin(); pi != prim->end(); ++pi) { vertices.insert(*pi); } } } // Now store the 3-d values in a vector for convenient access (and // also determine the centroid). We compute this in node space. size_t num_vertices = vertices.size(); if (num_vertices != 0) { LMatrix4d mat = egg_group->get_vertex_to_node(); pvector vpos; vpos.reserve(num_vertices); LPoint3d center(0.0, 0.0, 0.0); pset::const_iterator vi; for (vi = vertices.begin(); vi != vertices.end(); ++vi) { EggVertex *vtx = (*vi); LPoint3d pos = vtx->get_pos3() * mat; vpos.push_back(pos); center += pos; } center /= (double)num_vertices; // Now that we have the centroid, we have to try to figure out // the cylinder's major axis. Start by finding a point farthest // from the centroid. size_t i; double radius2 = 0.0; LPoint3d far_a = center; for (i = 0; i < num_vertices; i++) { double dist2 = (vpos[i] - center).length_squared(); if (dist2 > radius2) { radius2 = dist2; far_a = vpos[i]; } } // The point we have found above, far_a, must be one one of the // endcaps. Now find another point, far_b, that is the farthest // from far_a. This will be a point on the other endcap. radius2 = 0.0; LPoint3d far_b = center; for (i = 0; i < num_vertices; i++) { double dist2 = (vpos[i] - far_a).length_squared(); if (dist2 > radius2) { radius2 = dist2; far_b = vpos[i]; } } // Now we have far_a and far_b, one point on each endcap. // However, these points are not necessarily centered on the // endcaps, so we haven't figured out the cylinder's axis yet // (the line between far_a and far_b will probably pass through // the cylinder at an angle). // So we still need to determine the full set of points in each // endcap. To do this, we pass back through the set of points, // categorizing each point into either "endcap a" or "endcap b". // We also leave a hefty chunk of points in the middle // uncategorized; this helps prevent us from getting a little // bit lopsided with points near the middle that may appear to // be closer to the wrong endcap. LPoint3d cap_a_center(0.0, 0.0, 0.0); LPoint3d cap_b_center(0.0, 0.0, 0.0); int num_a = 0; int num_b = 0; // This is the threshold length; points farther away from the // center than this are deemed to be in one endcap or the other. double center_length = (far_a - far_b).length() / 4.0; double center_length2 = center_length * center_length; for (i = 0; i < num_vertices; i++) { double dist2 = (vpos[i] - center).length_squared(); if (dist2 > center_length2) { // This point is farther away from the center than // center_length; therefore it belongs in an endcap. double dist_a2 = (vpos[i] - far_a).length_squared(); double dist_b2 = (vpos[i] - far_b).length_squared(); if (dist_a2 < dist_b2) { // It's in endcap a. cap_a_center += vpos[i]; num_a++; } else { // It's in endcap b. cap_b_center += vpos[i]; num_b++; } } } if (num_a > 0 && num_b > 0) { cap_a_center /= (double)num_a; cap_b_center /= (double)num_b; // Now we finally have the major axis of the cylinder. LVector3d axis = cap_b_center - cap_a_center; axis.normalize(); // If the axis is *almost* parallel with a major axis, assume // it is meant to be exactly parallel. if (IS_THRESHOLD_ZERO(axis[0], 0.01)) { axis[0] = 0.0; } if (IS_THRESHOLD_ZERO(axis[1], 0.01)) { axis[1] = 0.0; } if (IS_THRESHOLD_ZERO(axis[2], 0.01)) { axis[2] = 0.0; } axis.normalize(); // Transform all of the points so that the major axis is along // the Y axis, and the origin is the center. This is very // similar to the CollisionTube's idea of its canonical // orientation (although not exactly the same, since it is // centered on the origin instead of having point_a on the // origin). It makes it easier to determine the length and // radius of the cylinder. LMatrix4d mat; look_at(mat, axis, LVector3d(0.0, 0.0, 1.0), CS_zup_right); mat.set_row(3, center); LMatrix4d inv_mat; inv_mat.invert_from(mat); for (i = 0; i < num_vertices; i++) { vpos[i] = vpos[i] * inv_mat; } double max_radius2 = 0.0; // Now determine the radius. for (i = 0; i < num_vertices; i++) { LVector2d v(vpos[i][0], vpos[i][2]); double radius2 = v.length_squared(); if (radius2 > max_radius2) { max_radius2 = radius2; } } // And with the radius, we can determine the length. We need // to know the radius first because we want the round endcaps // to enclose all points. double min_y = 0.0; double max_y = 0.0; for (i = 0; i < num_vertices; i++) { LVector2d v(vpos[i][0], vpos[i][2]); double radius2 = v.length_squared(); if (vpos[i][1] < min_y) { // Adjust the Y pos to account for the point's distance // from the axis. double factor = sqrt(max_radius2 - radius2); min_y = min(min_y, vpos[i][1] + factor); } else if (vpos[i][1] > max_y) { double factor = sqrt(max_radius2 - radius2); max_y = max(max_y, vpos[i][1] - factor); } } double length = max_y - min_y; double radius = sqrt(max_radius2); // Finally, we have everything we need to define the cylinder. LVector3d half = axis * (length / 2.0); LPoint3d point_a = center - half; LPoint3d point_b = center + half; CollisionTube *cstube = new CollisionTube(LCAST(float, point_a), LCAST(float, point_b), radius); apply_collision_flags(cstube, flags); cnode->add_solid(cstube); } } } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::apply_collision_flags // Access: Private // Description: Does funny stuff to the CollisionSolid as // appropriate, based on the settings of the given // CollideFlags. //////////////////////////////////////////////////////////////////// void EggLoader:: apply_collision_flags(CollisionSolid *solid, EggGroup::CollideFlags flags) { if ((flags & EggGroup::CF_intangible) != 0) { solid->set_tangible(false); } if ((flags & EggGroup::CF_level) != 0) { solid->set_effective_normal(LVector3f::up()); } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::find_collision_geometry // Access: Private // Description: Looks for the node, at or below the indicated node, // that contains the associated collision geometry. //////////////////////////////////////////////////////////////////// EggGroup *EggLoader:: find_collision_geometry(EggGroup *egg_group, EggGroup::CollideFlags flags) { if ((flags & EggGroup::CF_descend) != 0) { // If we have the "descend" instruction, we'll get to it when we // get to it. Don't worry about it now. return egg_group; } // Does this group have any polygons? EggGroup::const_iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { if ((*ci)->is_of_type(EggPolygon::get_class_type())) { // Yes! Use this group. return egg_group; } } // Well, the group had no polygons; look for a child group that has // the same collision type. for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { if ((*ci)->is_of_type(EggGroup::get_class_type())) { EggGroup *child_group = DCAST(EggGroup, *ci); if (child_group->get_cs_type() == egg_group->get_cs_type()) { return child_group; } } } // We got nothing. return NULL; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::create_collision_plane // Access: Private // Description: Creates a single CollisionPlane from the indicated // EggPolygon. //////////////////////////////////////////////////////////////////// CollisionPlane *EggLoader:: create_collision_plane(EggPolygon *egg_poly, EggGroup *parent_group) { if (!egg_poly->cleanup()) { egg2pg_cat.info() << "Ignoring degenerate collision plane in " << parent_group->get_name() << "\n"; return NULL; } if (!egg_poly->is_planar()) { egg2pg_cat.warning() << "Non-planar polygon defining collision plane in " << parent_group->get_name() << "\n"; } LMatrix4d mat = egg_poly->get_vertex_to_node(); pvector vertices; if (!egg_poly->empty()) { EggPolygon::const_iterator vi; vi = egg_poly->begin(); Vertexd vert = (*vi)->get_pos3() * mat; vertices.push_back(LCAST(float, vert)); Vertexd last_vert = vert; ++vi; while (vi != egg_poly->end()) { vert = (*vi)->get_pos3() * mat; if (!vert.almost_equal(last_vert)) { vertices.push_back(LCAST(float, vert)); } last_vert = vert; ++vi; } } if (vertices.size() < 3) { return NULL; } Planef plane(vertices[0], vertices[1], vertices[2]); return new CollisionPlane(plane); } //////////////////////////////////////////////////////////////////// // Function: EggLoader::create_collision_polygons // Access: Private // Description: Creates one or more CollisionPolygons from the // indicated EggPolygon, and adds them to the indicated // CollisionNode. //////////////////////////////////////////////////////////////////// void EggLoader:: create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly, EggGroup *parent_group, EggGroup::CollideFlags flags) { LMatrix4d mat = egg_poly->get_vertex_to_node(); PT(EggGroup) group = new EggGroup; if (!egg_poly->triangulate_into(group, false)) { egg2pg_cat.info() << "Ignoring degenerate collision polygon in " << parent_group->get_name() << "\n"; return; } if (group->size() != 1) { egg2pg_cat.info() << "Triangulating concave or non-planar collision polygon in " << parent_group->get_name() << "\n"; } EggGroup::iterator ci; for (ci = group->begin(); ci != group->end(); ++ci) { EggPolygon *poly = DCAST(EggPolygon, *ci); pvector vertices; if (!poly->empty()) { EggPolygon::const_iterator vi; vi = poly->begin(); Vertexd vert = (*vi)->get_pos3() * mat; vertices.push_back(LCAST(float, vert)); Vertexd last_vert = vert; ++vi; while (vi != poly->end()) { vert = (*vi)->get_pos3() * mat; if (!vert.almost_equal(last_vert)) { vertices.push_back(LCAST(float, vert)); } last_vert = vert; ++vi; } } if (vertices.size() >= 3) { const Vertexf *vertices_begin = &vertices[0]; const Vertexf *vertices_end = vertices_begin + vertices.size(); PT(CollisionPolygon) cspoly = new CollisionPolygon(vertices_begin, vertices_end); if (cspoly->is_valid()) { apply_collision_flags(cspoly, flags); cnode->add_solid(cspoly); } } } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::create_collision_floor_mesh // Access: Private // Description: Creates a CollisionFloorMesh from the // indicated EggPolygons, and adds it to the indicated // CollisionNode. //////////////////////////////////////////////////////////////////// void EggLoader:: create_collision_floor_mesh(CollisionNode *cnode, EggGroup *parent_group, EggGroup::CollideFlags flags) { PT(EggGroup) group = new EggGroup; EggVertexPool pool("floorMesh"); pool.local_object(); EggGroup::const_iterator egi; for (egi = parent_group->begin(); egi != parent_group->end(); ++egi) { if ((*egi)->is_of_type(EggPolygon::get_class_type())) { EggPolygon * poly = DCAST(EggPolygon, *egi); if (!poly->triangulate_into(group, false)) { egg2pg_cat.info() << "Ignoring degenerate collision polygon in " << parent_group->get_name() << "\n"; return; } } } if(group->size() == 0) { egg2pg_cat.info() << "empty collision solid\n"; return; } PT(CollisionFloorMesh) cm = new CollisionFloorMesh; pvector triangles; EggGroup::iterator ci; for (ci = group->begin(); ci != group->end(); ++ci) { EggPolygon *poly = DCAST(EggPolygon, *ci); if (poly->get_num_vertices() == 3) { CollisionFloorMesh::TriangleIndices tri; //generate a shared vertex triangle from the vertex pool tri.p1=pool.create_unique_vertex(*poly->get_vertex(0))->get_index(); tri.p2=pool.create_unique_vertex(*poly->get_vertex(1))->get_index(); tri.p3=pool.create_unique_vertex(*poly->get_vertex(2))->get_index(); triangles.push_back(tri); } else if (poly->get_num_vertices() == 4) { //this is a case that really shouldn't happen, but appears to be required //-split up the quad int 2 tris. CollisionFloorMesh::TriangleIndices tri; CollisionFloorMesh::TriangleIndices tri2; //generate a shared vertex triangle from the vertex pool tri.p1=pool.create_unique_vertex(*poly->get_vertex(0))->get_index(); tri.p2=pool.create_unique_vertex(*poly->get_vertex(1))->get_index(); tri.p3=pool.create_unique_vertex(*poly->get_vertex(2))->get_index(); triangles.push_back(tri); //generate a shared vertex triangle from the vertex pool tri2.p1=tri.p1; tri2.p2=tri.p3; tri2.p3=pool.create_unique_vertex(*poly->get_vertex(3))->get_index(); triangles.push_back(tri2); } } //Now we have a set of triangles, and a pool PT(CollisionFloorMesh) csfloor = new CollisionFloorMesh; EggVertexPool::const_iterator vi; for (vi = pool.begin(); vi != pool.end(); vi++) { csfloor->add_vertex(LCAST(float,(*vi)->get_pos3())); } pvector::iterator ti; for (ti = triangles.begin(); ti != triangles.end(); ti++) { CollisionFloorMesh::TriangleIndices triangle = *ti; csfloor->add_triangle(triangle.p1, triangle.p2, triangle.p3); } cnode->add_solid(csfloor); } //////////////////////////////////////////////////////////////////// // Function: EggLoader::apply_deferred_nodes // Access: Private // Description: Walks back over the tree and applies the // DeferredNodeProperties that were saved up along the // way. //////////////////////////////////////////////////////////////////// void EggLoader:: apply_deferred_nodes(PandaNode *node, const DeferredNodeProperty &prop) { DeferredNodeProperty next_prop(prop); // Do we have a DeferredNodeProperty associated with this node? DeferredNodes::const_iterator dni; dni = _deferred_nodes.find(node); if (dni != _deferred_nodes.end()) { const DeferredNodeProperty &def = (*dni).second; next_prop.compose(def); } // Now apply the accumulated state to the node. next_prop.apply_to_node(node); int num_children = node->get_num_children(); for (int i = 0; i < num_children; i++) { apply_deferred_nodes(node->get_child(i), next_prop); } } //////////////////////////////////////////////////////////////////// // Function: EggLoader::expand_all_object_types // Access: Private // Description: Walks the hierarchy and calls expand_object_types() // on each node, to expand all of the ObjectType // definitions in the file at once. Also prunes any // nodes that are flagged "backstage". // // The return value is true if this node should be kept, // false if it should be pruned. //////////////////////////////////////////////////////////////////// bool EggLoader:: expand_all_object_types(EggNode *egg_node) { if (egg_node->is_of_type(EggGroup::get_class_type())) { EggGroup *egg_group = DCAST(EggGroup, egg_node); if (egg_group->get_num_object_types() != 0) { pset expanded; pvector expanded_history; if (!expand_object_types(egg_group, expanded, expanded_history)) { return false; } } } // Now recurse on children, and we might prune children from this // list as we go. if (egg_node->is_of_type(EggGroupNode::get_class_type())) { EggGroupNode *egg_group_node = DCAST(EggGroupNode, egg_node); EggGroupNode::const_iterator ci; ci = egg_group_node->begin(); while (ci != egg_group_node->end()) { EggGroupNode::const_iterator cnext = ci; ++cnext; if (!expand_all_object_types(*ci)) { // Prune this child. egg_group_node->erase(ci); } ci = cnext; } } return true; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::expand_object_types // Access: Private // Description: Recursively expands the group's ObjectType string(s). // It's recursive because an ObjectType string might // itself expand to another ObjectType string, which is // allowed; but we don't want to get caught in a cycle. // // The return value is true if the object type is // expanded and the node is valid, or false if the node // should be ignored (e.g. ObjectType "backstage"). //////////////////////////////////////////////////////////////////// bool EggLoader:: expand_object_types(EggGroup *egg_group, const pset &expanded, const pvector &expanded_history) { int num_object_types = egg_group->get_num_object_types(); // First, copy out the object types so we can recursively modify the // list. vector_string object_types; int i; for (i = 0; i < num_object_types; i++) { object_types.push_back(egg_group->get_object_type(i)); } egg_group->clear_object_types(); for (i = 0; i < num_object_types; i++) { string object_type = object_types[i]; pset new_expanded(expanded); // Check for a cycle. if (!new_expanded.insert(object_type).second) { egg2pg_cat.error() << "Cycle in ObjectType expansions:\n"; pvector::const_iterator pi; for (pi = expanded_history.begin(); pi != expanded_history.end(); ++pi) { egg2pg_cat.error(false) << (*pi) << " -> "; } egg2pg_cat.error(false) << object_type << "\n"; _error = true; } else { // No cycle; continue. pvector new_expanded_history(expanded_history); new_expanded_history.push_back(object_type); if (!do_expand_object_type(egg_group, new_expanded, new_expanded_history, object_type)) { // Ignorable group; stop here. return false; } } } return true; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::do_expand_object_types // Access: Private // Description: Further implementation of expand_object_types(). //////////////////////////////////////////////////////////////////// bool EggLoader:: do_expand_object_type(EggGroup *egg_group, const pset &expanded, const pvector &expanded_history, const string &object_type) { // Try to find the egg syntax that the given objecttype is // shorthand for. First, look in the config file. ConfigVariableString egg_object_type ("egg-object-type-" + downcase(object_type), ""); string egg_syntax = egg_object_type; if (!egg_object_type.has_value()) { // It wasn't defined in a config file. Maybe it's built in? if (cmp_nocase_uh(object_type, "barrier") == 0) { egg_syntax = " { Polyset descend }"; } else if (cmp_nocase_uh(object_type, "solidpoly") == 0) { egg_syntax = " { Polyset descend solid }"; } else if (cmp_nocase_uh(object_type, "turnstile") == 0) { egg_syntax = " { Polyset descend turnstile }"; } else if (cmp_nocase_uh(object_type, "sphere") == 0) { egg_syntax = " { Sphere descend }"; } else if (cmp_nocase_uh(object_type, "tube") == 0) { egg_syntax = " { Tube descend }"; } else if (cmp_nocase_uh(object_type, "trigger") == 0) { egg_syntax = " { Polyset descend intangible }"; } else if (cmp_nocase_uh(object_type, "trigger_sphere") == 0) { egg_syntax = " { Sphere descend intangible }"; } else if (cmp_nocase_uh(object_type, "eye_trigger") == 0) { egg_syntax = " { Polyset descend intangible center }"; } else if (cmp_nocase_uh(object_type, "bubble") == 0) { egg_syntax = " { Sphere keep descend }"; } else if (cmp_nocase_uh(object_type, "ghost") == 0) { egg_syntax = " collide-mask { 0 }"; } else if (cmp_nocase_uh(object_type, "dcs") == 0) { egg_syntax = " { 1 }"; } else if (cmp_nocase_uh(object_type, "model") == 0) { egg_syntax = " { 1 }"; } else if (cmp_nocase_uh(object_type, "none") == 0) { // ObjectType "none" is a special case, meaning nothing in particular. return true; } else if (cmp_nocase_uh(object_type, "backstage") == 0) { // Ignore "backstage" geometry. return false; } else { egg2pg_cat.error() << "Unknown ObjectType " << object_type << "\n"; _error = true; egg2pg_cat.debug() << "returning true\n"; return true; } } if (!egg_syntax.empty()) { if (!egg_group->parse_egg(egg_syntax)) { egg2pg_cat.error() << "Error while parsing definition for ObjectType " << object_type << "\n"; _error = true; } else { // Now we've parsed the object type syntax, which might have // added more object types. Recurse if necessary. if (egg_group->get_num_object_types() != 0) { if (!expand_object_types(egg_group, expanded, expanded_history)) { return false; } } } } return true; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::get_combine_mode // Access: Private, Static // Description: Extracts the combine_mode from the given egg texture, // and returns its corresponding TextureStage value. //////////////////////////////////////////////////////////////////// TextureStage::CombineMode EggLoader:: get_combine_mode(const EggTexture *egg_tex, EggTexture::CombineChannel channel) { switch (egg_tex->get_combine_mode(channel)) { case EggTexture::CM_unspecified: // fall through case EggTexture::CM_modulate: return TextureStage::CM_modulate; case EggTexture::CM_replace: return TextureStage::CM_replace; case EggTexture::CM_add: return TextureStage::CM_add; case EggTexture::CM_add_signed: return TextureStage::CM_add_signed; case EggTexture::CM_interpolate: return TextureStage::CM_interpolate; case EggTexture::CM_subtract: return TextureStage::CM_subtract; case EggTexture::CM_dot3_rgb: return TextureStage::CM_dot3_rgb; case EggTexture::CM_dot3_rgba: return TextureStage::CM_dot3_rgba; }; return TextureStage::CM_undefined; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::get_combine_source // Access: Private, Static // Description: Extracts the combine_source from the given egg texture, // and returns its corresponding TextureStage value. //////////////////////////////////////////////////////////////////// TextureStage::CombineSource EggLoader:: get_combine_source(const EggTexture *egg_tex, EggTexture::CombineChannel channel, int n) { switch (egg_tex->get_combine_source(channel, n)) { case EggTexture::CS_unspecified: // The default source if it is unspecified is based on the // parameter index. switch (n) { case 0: return TextureStage::CS_previous; case 1: return TextureStage::CS_texture; case 2: return TextureStage::CS_constant; } // Otherwise, fall through case EggTexture::CS_texture: return TextureStage::CS_texture; case EggTexture::CS_constant: return TextureStage::CS_constant; case EggTexture::CS_primary_color: return TextureStage::CS_primary_color; case EggTexture::CS_previous: return TextureStage::CS_previous; case EggTexture::CS_constant_color_scale: return TextureStage::CS_constant_color_scale; case EggTexture::CS_last_saved_result: return TextureStage::CS_last_saved_result; }; return TextureStage::CS_undefined; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::get_combine_operand // Access: Private, Static // Description: Extracts the combine_operand from the given egg texture, // and returns its corresponding TextureStage value. //////////////////////////////////////////////////////////////////// TextureStage::CombineOperand EggLoader:: get_combine_operand(const EggTexture *egg_tex, EggTexture::CombineChannel channel, int n) { switch (egg_tex->get_combine_operand(channel, n)) { case EggTexture::CS_unspecified: if (channel == EggTexture::CC_rgb) { // The default operand for RGB is src_color, except for the // third parameter, which defaults to src_alpha. return n < 2 ? TextureStage::CO_src_color : TextureStage::CO_src_alpha; } else { // The default operand for alpha is always src_alpha. return TextureStage::CO_src_alpha; } case EggTexture::CO_src_color: return TextureStage::CO_src_color; case EggTexture::CO_one_minus_src_color: return TextureStage::CO_one_minus_src_color; case EggTexture::CO_src_alpha: return TextureStage::CO_src_alpha; case EggTexture::CO_one_minus_src_alpha: return TextureStage::CO_one_minus_src_alpha; }; return TextureStage::CO_undefined; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::get_color_blend_mode // Access: Private, Static // Description: Converts the EggGroup's BlendMode to the // corresponding ColorBlendAttrib::Mode value. //////////////////////////////////////////////////////////////////// ColorBlendAttrib::Mode EggLoader:: get_color_blend_mode(EggGroup::BlendMode mode) { switch (mode) { case EggGroup::BM_unspecified: case EggGroup::BM_none: return ColorBlendAttrib::M_none; case EggGroup::BM_add: return ColorBlendAttrib::M_add; case EggGroup::BM_subtract: return ColorBlendAttrib::M_subtract; case EggGroup::BM_inv_subtract: return ColorBlendAttrib::M_inv_subtract; case EggGroup::BM_min: return ColorBlendAttrib::M_min; case EggGroup::BM_max: return ColorBlendAttrib::M_max; } return ColorBlendAttrib::M_none; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::get_color_blend_operand // Access: Private, Static // Description: Converts the EggGroup's BlendOperand to the // corresponding ColorBlendAttrib::Operand value. //////////////////////////////////////////////////////////////////// ColorBlendAttrib::Operand EggLoader:: get_color_blend_operand(EggGroup::BlendOperand operand) { switch (operand) { case EggGroup::BO_zero: return ColorBlendAttrib::O_zero; case EggGroup::BO_unspecified: case EggGroup::BO_one: return ColorBlendAttrib::O_one; case EggGroup::BO_incoming_color: return ColorBlendAttrib::O_incoming_color; case EggGroup::BO_one_minus_incoming_color: return ColorBlendAttrib::O_one_minus_incoming_color; case EggGroup::BO_fbuffer_color: return ColorBlendAttrib::O_fbuffer_color; case EggGroup::BO_one_minus_fbuffer_color: return ColorBlendAttrib::O_one_minus_fbuffer_color; case EggGroup::BO_incoming_alpha: return ColorBlendAttrib::O_incoming_alpha; case EggGroup::BO_one_minus_incoming_alpha: return ColorBlendAttrib::O_one_minus_incoming_alpha; case EggGroup::BO_fbuffer_alpha: return ColorBlendAttrib::O_fbuffer_alpha; case EggGroup::BO_one_minus_fbuffer_alpha: return ColorBlendAttrib::O_one_minus_fbuffer_alpha; case EggGroup::BO_constant_color: return ColorBlendAttrib::O_constant_color; case EggGroup::BO_one_minus_constant_color: return ColorBlendAttrib::O_one_minus_constant_color; case EggGroup::BO_constant_alpha: return ColorBlendAttrib::O_constant_alpha; case EggGroup::BO_one_minus_constant_alpha: return ColorBlendAttrib::O_one_minus_constant_alpha; case EggGroup::BO_incoming_color_saturate: return ColorBlendAttrib::O_incoming_color_saturate; case EggGroup::BO_color_scale: return ColorBlendAttrib::O_color_scale; case EggGroup::BO_one_minus_color_scale: return ColorBlendAttrib::O_one_minus_color_scale; case EggGroup::BO_alpha_scale: return ColorBlendAttrib::O_alpha_scale; case EggGroup::BO_one_minus_alpha_scale: return ColorBlendAttrib::O_one_minus_alpha_scale; } return ColorBlendAttrib::O_zero; } //////////////////////////////////////////////////////////////////// // Function: EggLoader::VertexPoolTransform::operator < // Access: Public // Description: //////////////////////////////////////////////////////////////////// bool EggLoader::VertexPoolTransform:: operator < (const EggLoader::VertexPoolTransform &other) const { if (_vertex_pool != other._vertex_pool) { return _vertex_pool < other._vertex_pool; } int compare = _transform.compare_to(other._transform, 0.001); if (compare != 0) { return compare < 0; } if (_bake_in_uvs.size() != other._bake_in_uvs.size()) { return _bake_in_uvs.size() < other._bake_in_uvs.size(); } BakeInUVs::const_iterator ai, bi; ai = _bake_in_uvs.begin(); bi = other._bake_in_uvs.begin(); while (ai != _bake_in_uvs.end()) { nassertr(bi != other._bake_in_uvs.end(), false); if ((*ai) != (*bi)) { return (*ai) < (*bi); } ++ai; ++bi; } nassertr(bi == other._bake_in_uvs.end(), false); return false; }