// Filename: eggToFlt.cxx // Created by: drose (01Oct03) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved // // All use of this software is subject to the terms of the Panda 3d // Software license. You should have received a copy of this license // along with this source code; you will also find a current copy of // the license at http://etc.cmu.edu/panda3d/docs/license/ . // // To contact the maintainers of this program write to // panda3d-general@lists.sourceforge.net . // //////////////////////////////////////////////////////////////////// #include "eggToFlt.h" #include "fltHeader.h" #include "fltBead.h" #include "fltGroup.h" #include "fltFace.h" #include "fltVertexList.h" #include "fltVertex.h" #include "fltTexture.h" #include "fltTransformTranslate.h" #include "fltTransformRotateAboutEdge.h" #include "fltTransformScale.h" #include "fltTransformGeneralMatrix.h" #include "eggPolygon.h" #include "eggPoint.h" #include "eggPrimitive.h" #include "eggExternalReference.h" #include "eggGroup.h" #include "eggGroupNode.h" #include "eggTexture.h" #include "eggTransform3d.h" #include "dcast.h" #include "string_utils.h" #include "vector_string.h" //////////////////////////////////////////////////////////////////// // Function: EggToFlt::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// EggToFlt:: EggToFlt() : EggToSomething("MultiGen", ".flt", true, false) { set_binary_output(true); set_program_description ("egg2flt converts files from egg format to MultiGen .flt " "format. It attempts to be as robust as possible, and matches " "the capabilities of flt2egg. Generally, converting a model " "from egg2flt and then back via flt2egg will result in essentially " "the same egg file, within the limitations of what can be " "represented in flt."); add_option ("attr", "none/new/all", 0, "Specifies whether to write (or rewrite) .attr files for each " "texture image. MultiGen stores texture properties like mipmapping " "in a separate .attr file for each different texture image. " "If this parameter is \"none\", these files will not be generated; " "if this is \"new\", these files will only be generated if they " "do not already exist (even if the properties have changed). " "Specifying \"all\" causes these to be rewritten every time.", &EggToFlt::dispatch_attr, NULL, &_auto_attr_update); // Flt files are always in the z-up coordinate system. Don't // confuse the user with this meaningless option. remove_option("cs"); _coordinate_system = CS_zup_right; _got_coordinate_system = true; _auto_attr_update = FltHeader::AU_if_missing; } //////////////////////////////////////////////////////////////////// // Function: EggToFlt::run // Access: Public // Description: //////////////////////////////////////////////////////////////////// void EggToFlt:: run() { _flt_header = new FltHeader(_path_replace); _flt_header->set_auto_attr_update(_auto_attr_update); traverse(&_data, _flt_header, FltGeometry::BT_none); // Finally, write the resulting file out. FltError result = _flt_header->write_flt(get_output()); if (result != FE_ok) { nout << "Cannot write " << get_output_filename() << "\n"; exit(1); } } //////////////////////////////////////////////////////////////////// // Function: EggToFlt::dispatch_attr // Access: Protected, Static // Description: Dispatch function for the -attr parameter. //////////////////////////////////////////////////////////////////// bool EggToFlt:: dispatch_attr(const string &opt, const string &arg, void *var) { FltHeader::AttrUpdate *ip = (FltHeader::AttrUpdate *)var; if (cmp_nocase(arg, "none") == 0) { *ip = FltHeader::AU_none; } else if (cmp_nocase(arg, "new") == 0) { *ip = FltHeader::AU_if_missing; } else if (cmp_nocase(arg, "all") == 0) { *ip = FltHeader::AU_always; } else { nout << "-" << opt << " requires either \"none\", \"new\", or \"all\".\n"; return false; } return true; } //////////////////////////////////////////////////////////////////// // Function: EggToFlt::traverse // Access: Private // Description: //////////////////////////////////////////////////////////////////// void EggToFlt:: traverse(EggNode *egg_node, FltBead *flt_node, FltGeometry::BillboardType billboard) { if (egg_node->is_of_type(EggPolygon::get_class_type()) || egg_node->is_of_type(EggPoint::get_class_type())) { // It's a polygon or point light. EggPrimitive *egg_primitive = DCAST(EggPrimitive, egg_node); convert_primitive(egg_primitive, flt_node, billboard); } else if (egg_node->is_of_type(EggExternalReference::get_class_type())) { // Convert external references. } else if (egg_node->is_of_type(EggGroup::get_class_type())) { // An EggGroup creates a fltBead, and recurses. EggGroup *egg_group = DCAST(EggGroup, egg_node); if (egg_group->get_group_type() == EggGroup::GT_joint) { // Ignore joints and their children. return; } convert_group(egg_group, flt_node, billboard); } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) { // Some kind of grouping node other than an EggGroup. Just recurse. EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node); EggGroupNode::iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { traverse(*ci, flt_node, billboard); } } } //////////////////////////////////////////////////////////////////// // Function: EggToFlt::convert_primitive // Access: Private // Description: Converts an egg polygon or series of light points to // the corresponding Flt geometry, and adds it to the // indicated flt_node. //////////////////////////////////////////////////////////////////// void EggToFlt:: convert_primitive(EggPrimitive *egg_primitive, FltBead *flt_node, FltGeometry::BillboardType billboard) { FltFace *flt_face = new FltFace(_flt_header); flt_node->add_child(flt_face); flt_face->_billboard_type = billboard; if (egg_primitive->has_color()) { flt_face->set_color(egg_primitive->get_color()); } if (egg_primitive->is_of_type(EggPoint::get_class_type())) { // A series of points, instead of a polygon. flt_face->_draw_type = FltFace::DT_omni_light; } else if (egg_primitive->get_bface_flag()) { // A polygon whose backface is visible. flt_face->_draw_type = FltFace::DT_solid_no_cull; } else { // A normal polygon. flt_face->_draw_type = FltFace::DT_solid_cull_backface; } if (egg_primitive->has_texture()) { EggTexture *egg_texture = egg_primitive->get_texture(); FltTexture *flt_texture = get_flt_texture(egg_texture); flt_face->set_texture(flt_texture); } // Create a vertex list representing the vertices in the // primitive, and add it as a child of the face bead. This is how // Flt files associate vertices with faces. FltVertexList *flt_vertices = new FltVertexList(_flt_header); flt_face->add_child(flt_vertices); EggPrimitive::iterator vi; bool all_verts_have_color = true; bool all_verts_have_normal = true; for (vi = egg_primitive->begin(); vi != egg_primitive->end(); ++vi) { EggVertex *egg_vertex = (*vi); FltVertex *flt_vertex = get_flt_vertex(egg_vertex, egg_primitive); flt_vertices->add_vertex(flt_vertex); if (!egg_vertex->has_color()) { all_verts_have_color = false; } if (!egg_vertex->has_normal()) { all_verts_have_normal = false; } } if (all_verts_have_color) { // If all the vertices of the face have a color specification, // then we specify per-vertex color on the face. if (all_verts_have_normal) { // And similarly with the normals. flt_face->_light_mode = FltFace::LM_vertex_with_normal; } else { flt_face->_light_mode = FltFace::LM_vertex_no_normal; } } else { if (all_verts_have_normal) { flt_face->_light_mode = FltFace::LM_face_with_normal; } else { flt_face->_light_mode = FltFace::LM_face_no_normal; } } } //////////////////////////////////////////////////////////////////// // Function: EggToFlt::convert_group // Access: Private // Description: Converts an egg group to the corresponding flt group, // and adds it to the indicated parent node. Also // recurses on the children of the egg group. //////////////////////////////////////////////////////////////////// void EggToFlt:: convert_group(EggGroup *egg_group, FltBead *flt_node, FltGeometry::BillboardType billboard) { ostringstream egg_syntax; FltGroup *flt_group = new FltGroup(_flt_header); flt_node->add_child(flt_group); flt_group->set_id(egg_group->get_name()); switch (egg_group->get_billboard_type()) { // MultiGen represents billboarding at the polygon level, so we // have to remember this flag for later. case EggGroup::BT_axis: billboard = FltGeometry::BT_axial; break; case EggGroup::BT_point_world_relative: billboard = FltGeometry::BT_point; break; case EggGroup::BT_point_camera_relative: // Not sure if this is the right flag for MultiGen. billboard = FltGeometry::BT_fixed; break; default: break; } if (egg_group->has_transform()) { apply_transform(egg_group, flt_group); } if (egg_group->get_switch_flag()) { if (egg_group->get_switch_fps() != 0.0) { // A sequence animation. flt_group->_flags |= FltGroup::F_forward_animation; egg_syntax << " fps { " << egg_group->get_switch_fps() << " }\n"; } else { // Just a switch node. egg_group->write_switch_flags(egg_syntax, 2); } } // Pick up any additional egg attributes that MultiGen doesn't // support; these will get written to the comment field where // flt2egg will find it. egg_group->write_collide_flags(egg_syntax, 2); egg_group->write_model_flags(egg_syntax, 2); egg_group->write_object_types(egg_syntax, 2); egg_group->write_decal_flags(egg_syntax, 2); egg_group->write_tags(egg_syntax, 2); egg_group->write_render_mode(egg_syntax, 2); apply_egg_syntax(egg_syntax.str(), flt_group); EggGroup::iterator ci; for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { traverse(*ci, flt_group, billboard); } } //////////////////////////////////////////////////////////////////// // Function: EggToFlt::apply_transform // Access: Private // Description: Applies the indicated egg transform to the indicated // flt bead. //////////////////////////////////////////////////////////////////// void EggToFlt:: apply_transform(EggTransform3d *egg_transform, FltBead *flt_node) { flt_node->clear_transform(); bool components_ok = true; int num_components = egg_transform->get_num_components(); for (int i = num_components - 1; i >= 0 && components_ok; i--) { switch (egg_transform->get_component_type(i)) { case EggTransform3d::CT_translate: { FltTransformTranslate *translate = new FltTransformTranslate(_flt_header); translate->set(LPoint3d::zero(), egg_transform->get_component_vector(i)); flt_node->add_transform_step(translate); } break; case EggTransform3d::CT_rotx: { FltTransformRotateAboutEdge *rotate = new FltTransformRotateAboutEdge(_flt_header); rotate->set(LPoint3d::zero(), LPoint3d(1.0, 0.0, 0.0), egg_transform->get_component_number(i)); flt_node->add_transform_step(rotate); } break; case EggTransform3d::CT_roty: { FltTransformRotateAboutEdge *rotate = new FltTransformRotateAboutEdge(_flt_header); rotate->set(LPoint3d::zero(), LPoint3d(0.0, 1.0, 0.0), egg_transform->get_component_number(i)); flt_node->add_transform_step(rotate); } break; case EggTransform3d::CT_rotz: { FltTransformRotateAboutEdge *rotate = new FltTransformRotateAboutEdge(_flt_header); rotate->set(LPoint3d::zero(), LPoint3d(0.0, 0.0, 1.0), egg_transform->get_component_number(i)); flt_node->add_transform_step(rotate); } break; case EggTransform3d::CT_rotate: { FltTransformRotateAboutEdge *rotate = new FltTransformRotateAboutEdge(_flt_header); rotate->set(LPoint3d::zero(), egg_transform->get_component_vector(i), egg_transform->get_component_number(i)); flt_node->add_transform_step(rotate); } break; case EggTransform3d::CT_scale: { FltTransformScale *scale = new FltTransformScale(_flt_header); scale->set(LPoint3d::zero(), LCAST(float, egg_transform->get_component_vector(i))); flt_node->add_transform_step(scale); } break; case EggTransform3d::CT_uniform_scale: { FltTransformScale *scale = new FltTransformScale(_flt_header); float factor = (float)egg_transform->get_component_number(i); scale->set(LPoint3d::zero(), LVecBase3f(factor, factor, factor)); flt_node->add_transform_step(scale); } break; case EggTransform3d::CT_matrix: { FltTransformGeneralMatrix *matrix = new FltTransformGeneralMatrix(_flt_header); matrix->set_matrix(egg_transform->get_component_matrix(i)); flt_node->add_transform_step(matrix); } break; default: // Don't know how to convert this component. components_ok = false; } } if (components_ok) { // Verify that the transform was computed correctly. if (!flt_node->get_transform().almost_equal(egg_transform->get_transform())) { nout << "Incorrect transform! Expected:\n"; egg_transform->get_transform().write(nout, 2); nout << "Computed:\n"; flt_node->get_transform().write(nout, 2); nout << "\n"; components_ok = false; } } if (!components_ok) { // Just store the overall transform. flt_node->set_transform(egg_transform->get_transform()); } } //////////////////////////////////////////////////////////////////// // Function: EggToFlt::apply_egg_syntax // Access: Private // Description: Adds the indicated sequence of egg syntax lines // (presumably representing egg features not directly // supported by MultiGen) to the flt record as a // comment, so that flt2egg will reapply it to the egg // groups. //////////////////////////////////////////////////////////////////// void EggToFlt:: apply_egg_syntax(const string &egg_syntax, FltRecord *flt_record) { if (!egg_syntax.empty()) { ostringstream out; out << " {\n" << egg_syntax << "}"; flt_record->set_comment(out.str()); } } //////////////////////////////////////////////////////////////////// // Function: EggToFlt::get_flt_vertex // Access: Private // Description: Returns a FltVertex corresponding to the indicated // EggVertex. If the vertex has not been seen before // (in this particular vertex frame), creates a new one. //////////////////////////////////////////////////////////////////// FltVertex *EggToFlt:: get_flt_vertex(EggVertex *egg_vertex, EggNode *context) { const LMatrix4d *frame = context->get_vertex_to_node_ptr(); VertexMap &vertex_map = _vertex_map_per_frame[frame]; VertexMap::iterator vi = vertex_map.find(egg_vertex); if (vi != vertex_map.end()) { return (*vi).second; } FltVertex *flt_vertex = new FltVertex(_flt_header); flt_vertex->_pos = egg_vertex->get_pos3(); if (egg_vertex->has_color()) { flt_vertex->set_color(egg_vertex->get_color()); } if (egg_vertex->has_normal()) { flt_vertex->_normal = LCAST(float, egg_vertex->get_normal()); flt_vertex->_has_normal = true; } if (egg_vertex->has_uv()) { flt_vertex->_uv = LCAST(float, egg_vertex->get_uv()); flt_vertex->_has_uv = true; } if (frame != (const LMatrix4d *)NULL) { flt_vertex->_pos = flt_vertex->_pos * (*frame); flt_vertex->_normal = flt_vertex->_normal * LCAST(float, (*frame)); } _flt_header->add_vertex(flt_vertex); vertex_map[egg_vertex] = flt_vertex; return flt_vertex; } //////////////////////////////////////////////////////////////////// // Function: EggToFlt::get_flt_texture // Access: Private // Description: Returns a FltTexture corresponding to the indicated // EggTexture. If the texture has not been seen before, // creates a new one. //////////////////////////////////////////////////////////////////// FltTexture *EggToFlt:: get_flt_texture(EggTexture *egg_texture) { // We have to maintain this map based on the filename, not the egg // pointer, because there may be multiple EggTextures with the same // filename, and we have to collapse them together. Filename filename = egg_texture->get_filename(); TextureMap::iterator vi = _texture_map.find(filename); if (vi != _texture_map.end()) { return (*vi).second; } FltTexture *flt_texture = new FltTexture(_flt_header); flt_texture->set_texture_filename(filename); switch (egg_texture->get_minfilter()) { case EggTexture::FT_nearest: flt_texture->_min_filter = FltTexture::MN_point; break; case EggTexture::FT_linear: flt_texture->_min_filter = FltTexture::MN_bilinear; break; case EggTexture::FT_nearest_mipmap_nearest: flt_texture->_min_filter = FltTexture::MN_mipmap_point; break; case EggTexture::FT_nearest_mipmap_linear: flt_texture->_min_filter = FltTexture::MN_mipmap_linear; break; case EggTexture::FT_linear_mipmap_nearest: flt_texture->_min_filter = FltTexture::MN_mipmap_bilinear; break; case EggTexture::FT_linear_mipmap_linear: flt_texture->_min_filter = FltTexture::MN_mipmap_trilinear; break; default: break; } switch (egg_texture->get_magfilter()) { case EggTexture::FT_nearest: flt_texture->_mag_filter = FltTexture::MG_point; break; case EggTexture::FT_linear: flt_texture->_mag_filter = FltTexture::MG_bilinear; break; default: break; } switch (egg_texture->get_wrap_mode()) { case EggTexture::WM_repeat: flt_texture->_repeat = FltTexture::RT_repeat; break; case EggTexture::WM_clamp: flt_texture->_repeat = FltTexture::RT_clamp; break; default: break; } switch (egg_texture->get_wrap_u()) { case EggTexture::WM_repeat: flt_texture->_repeat_u = FltTexture::RT_repeat; break; case EggTexture::WM_clamp: flt_texture->_repeat_u = FltTexture::RT_clamp; break; default: break; } switch (egg_texture->get_wrap_v()) { case EggTexture::WM_repeat: flt_texture->_repeat_v = FltTexture::RT_repeat; break; case EggTexture::WM_clamp: flt_texture->_repeat_v = FltTexture::RT_clamp; break; default: break; } switch (egg_texture->get_env_type()) { case EggTexture::ET_modulate: flt_texture->_env_type = FltTexture::ET_modulate; break; case EggTexture::ET_decal: flt_texture->_env_type = FltTexture::ET_decal; break; default: break; } switch (egg_texture->get_format()) { case EggTexture::F_luminance_alpha: case EggTexture::F_luminance_alphamask: flt_texture->_internal_format = FltTexture::IF_ia_8; break; case EggTexture::F_rgb5: case EggTexture::F_rgb332: flt_texture->_internal_format = FltTexture::IF_rgb_5; break; case EggTexture::F_rgba4: case EggTexture::F_rgba5: flt_texture->_internal_format = FltTexture::IF_rgba_4; break; case EggTexture::F_rgba8: case EggTexture::F_rgba: case EggTexture::F_rgbm: case EggTexture::F_rgb: case EggTexture::F_rgb8: flt_texture->_internal_format = FltTexture::IF_rgba_8; break; case EggTexture::F_rgba12: flt_texture->_internal_format = FltTexture::IF_rgba_12; break; case EggTexture::F_alpha: flt_texture->_internal_format = FltTexture::IF_i_16; flt_texture->_intensity_is_alpha = true; break; case EggTexture::F_luminance: flt_texture->_internal_format = FltTexture::IF_i_16; break; case EggTexture::F_rgb12: flt_texture->_internal_format = FltTexture::IF_rgb_12; break; default: break; } _flt_header->add_texture(flt_texture); _texture_map[filename] = flt_texture; return flt_texture; } int main(int argc, char *argv[]) { EggToFlt prog; prog.parse_command_line(argc, argv); prog.run(); return 0; }