// Filename: eggReader.cxx // Created by: drose (14Feb00) // //////////////////////////////////////////////////////////////////// // // 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 "eggReader.h" #include "pnmImage.h" #include "config_util.h" #include "eggTextureCollection.h" #include "eggGroup.h" #include "eggGroupNode.h" #include "eggSwitchCondition.h" #include "string_utils.h" #include "dcast.h" //////////////////////////////////////////////////////////////////// // Function: EggReader::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// EggReader:: EggReader() { clear_runlines(); add_runline("[opts] input.egg"); redescribe_option ("cs", "Specify the coordinate system to operate in. This may be " " one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default " "is the coordinate system of the input egg file."); add_option ("f", "", 80, "Force complete loading: load up the egg file along with all of its " "external references.", &EggReader::dispatch_none, &_force_complete); add_option ("noabs", "", 0, "Don't allow the input egg file to have absolute pathnames. " "If it does, abort with an error. This option is designed to help " "detect errors when populating or building a standalone model tree, " "which should be self-contained and include only relative pathnames.", &EggReader::dispatch_none, &_noabs); _tex_type = (PNMFileType *)NULL; _delod = -1.0; _got_tex_dirname = false; _got_tex_extension = false; } //////////////////////////////////////////////////////////////////// // Function: EggRead::add_texture_options // Access: Public // Description: Adds -td, -te, etc. as valid options for this // program. If the user specifies one of the options on // the command line, the textures will be copied and // converted as each egg file is read. // // Note that if you call this function to add these // options, you must call do_reader_options() at the // appropriate point before or during processing to // execute the options if the user specified them. //////////////////////////////////////////////////////////////////// void EggReader:: add_texture_options() { add_option ("td", "dirname", 40, "Copy textures to the indicated directory. The copy is performed " "only if the destination file does not exist or is older than the " "source file.", &EggReader::dispatch_filename, &_got_tex_dirname, &_tex_dirname); add_option ("te", "ext", 40, "Rename textures to have the indicated extension. This also " "automatically copies them to the new filename (possibly in a " "different directory if -td is also specified), and may implicitly " "convert to a different image format according to the extension.", &EggReader::dispatch_string, &_got_tex_extension, &_tex_extension); add_option ("tt", "type", 40, "Explicitly specifies the image format to convert textures to " "when copying them via -td or -te. Normally, this is unnecessary as " "the image format can be determined by the extension, but sometimes " "the extension is insufficient to unambiguously specify an image " "type.", &EggReader::dispatch_image_type, NULL, &_tex_type); } //////////////////////////////////////////////////////////////////// // Function: EggRead::add_delod_options // Access: Public // Description: Adds -delod as a valid option for this program. // // Note that if you call this function to add these // options, you must call do_reader_options() at the // appropriate point before or during processing to // execute the options if the user specified them. //////////////////////////////////////////////////////////////////// void EggReader:: add_delod_options(double default_delod) { _delod = default_delod; if (default_delod < 0) { add_option ("delod", "dist", 40, "Eliminate LOD's by choosing the level that would be appropriate for " "a camera at the indicated fixed distance from each LOD. " "Use -delod -1 to keep all the LOD's as they are, which is " "the default.\n", &EggReader::dispatch_double, NULL, &_delod); } else { add_option ("delod", "dist", 40, "Eliminate LOD's by choosing the level that would be appropriate for " "a camera at the indicated fixed distance from each LOD. " "Use -delod -1 to keep all the LOD's as they are. The default value " "is " + format_string(default_delod) + ".", &EggReader::dispatch_double, NULL, &_delod); } } //////////////////////////////////////////////////////////////////// // Function: EggReader::as_reader // Access: Public, Virtual // Description: Returns this object as an EggReader pointer, if it is // in fact an EggReader, or NULL if it is not. // // This is intended to work around the C++ limitation // that prevents downcasts past virtual inheritance. // Since both EggReader and EggWriter inherit virtually // from EggSingleBase, we need functions like this to downcast // to the appropriate pointer. //////////////////////////////////////////////////////////////////// EggReader *EggReader:: as_reader() { return this; } //////////////////////////////////////////////////////////////////// // Function: EggReader::pre_process_egg_file // Access: Public, Virtual // Description: Performs any processing of the egg file that is // appropriate after reading it in. // // Normally, you should not need to call this function // directly; it is called automatically at startup. //////////////////////////////////////////////////////////////////// void EggReader:: pre_process_egg_file() { } //////////////////////////////////////////////////////////////////// // Function: EggReader::handle_args // Access: Protected, Virtual // Description: //////////////////////////////////////////////////////////////////// bool EggReader:: handle_args(ProgramBase::Args &args) { if (args.empty()) { nout << "You must specify the egg file(s) to read on the command line.\n"; return false; } // Any separate egg files that are listed on the command line will // get implicitly loaded up into one big egg file. if (!args.empty()) { _data->set_egg_filename(Filename::from_os_specific(args[0])); } Args::const_iterator ai; for (ai = args.begin(); ai != args.end(); ++ai) { Filename filename = Filename::from_os_specific(*ai); EggData file_data; if (!file_data.read(filename)) { // Rather than returning false, we simply exit here, so the // ProgramBase won't try to tell the user how to run the program // just because we got a bad egg file. exit(1); } if (_noabs && file_data.original_had_absolute_pathnames()) { nout << filename.get_basename() << " includes absolute pathnames!\n"; exit(1); } DSearchPath file_path; file_path.append_directory(filename.get_dirname()); if (_force_complete) { if (!file_data.load_externals()) { exit(1); } } // Now resolve the filenames again according to the user's // specified _path_replace. convert_paths(&file_data, _path_replace, file_path); _data->merge(file_data); } pre_process_egg_file(); return true; } //////////////////////////////////////////////////////////////////// // Function: EggReader::post_command_line // Access: Protected, Virtual // Description: This is called after the command line has been // completely processed, and it gives the program a // chance to do some last-minute processing and // validation of the options and arguments. It should // return true if everything is fine, false if there is // an error. //////////////////////////////////////////////////////////////////// bool EggReader:: post_command_line() { return EggSingleBase::post_command_line(); } //////////////////////////////////////////////////////////////////// // Function: EggReader::do_reader_options // Access: Protected // Description: Postprocesses the egg file as the user requested // according to whatever command-line options are in // effect. Returns true if everything is done // correctly, false if there was some problem. //////////////////////////////////////////////////////////////////// bool EggReader:: do_reader_options() { bool okflag = true; if (_got_tex_dirname || _got_tex_extension) { if (!copy_textures()) { okflag = false; } } if (_delod >= 0.0) { do_delod(_data); } return okflag; } //////////////////////////////////////////////////////////////////// // Function: EggReader::copy_textures // Access: Private // Description: Renames and copies the textures referenced in the egg // file, if so specified by the -td and -te options. // Returns true if all textures are copied successfully, // false if any one of them failed. //////////////////////////////////////////////////////////////////// bool EggReader:: copy_textures() { bool success = true; EggTextureCollection textures; textures.find_used_textures(_data); EggTextureCollection::const_iterator ti; for (ti = textures.begin(); ti != textures.end(); ++ti) { EggTexture *tex = (*ti); Filename orig_filename = tex->get_filename(); if (!orig_filename.exists()) { bool found = orig_filename.resolve_filename(get_texture_path()) || orig_filename.resolve_filename(get_model_path()); if (!found) { nout << "Cannot find " << orig_filename << "\n"; success = false; continue; } } Filename new_filename = orig_filename; if (_got_tex_dirname) { new_filename.set_dirname(_tex_dirname); } if (_got_tex_extension) { new_filename.set_extension(_tex_extension); } if (orig_filename != new_filename) { tex->set_filename(new_filename); // The new filename is different; does it need copying? int compare = orig_filename.compare_timestamps(new_filename, true, true); if (compare > 0) { // Yes, it does. Copy it! nout << "Reading " << orig_filename << "\n"; PNMImage image; if (!image.read(orig_filename)) { nout << " unable to read!\n"; success = false; } else { nout << "Writing " << new_filename << "\n"; if (!image.write(new_filename, _tex_type)) { nout << " unable to write!\n"; success = false; } } } } } return success; } //////////////////////////////////////////////////////////////////// // Function: EggReader::do_delod // Access: Private // Description: Removes all the LOD's in the egg file by treating the // camera as being _delod distance from each LOD. // Returns true if this particular group should be // preserved, false if it should be removed. //////////////////////////////////////////////////////////////////// bool EggReader:: do_delod(EggNode *node) { if (node->is_of_type(EggGroup::get_class_type())) { EggGroup *group = DCAST(EggGroup, node); if (group->has_lod()) { const EggSwitchCondition &cond = group->get_lod(); if (cond.is_of_type(EggSwitchConditionDistance::get_class_type())) { const EggSwitchConditionDistance *dist = DCAST(EggSwitchConditionDistance, &cond); if (_delod >= dist->_switch_out && _delod < dist->_switch_in) { // Preserve this group node, but not the LOD information // itself. nout << "Preserving LOD " << node->get_name() << " (" << dist->_switch_out << " to " << dist->_switch_in << ")\n"; group->clear_lod(); } else { // Remove this group node. nout << "Eliminating LOD " << node->get_name() << " (" << dist->_switch_out << " to " << dist->_switch_in << ")\n"; return false; } } } } // Now process all the children. if (node->is_of_type(EggGroupNode::get_class_type())) { EggGroupNode *group = DCAST(EggGroupNode, node); EggGroupNode::iterator ci; ci = group->begin(); while (ci != group->end()) { EggNode *child = *ci; ++ci; if (!do_delod(child)) { group->remove_child(child); } } } return true; }