// Filename: eggToBam.cxx // Created by: drose (28Jun00) // //////////////////////////////////////////////////////////////////// // // 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 "eggToBam.h" #include "config_util.h" #include "bamFile.h" #include "load_egg_file.h" #include "config_egg2pg.h" #include "config_gobj.h" #include "config_chan.h" #include "pandaNode.h" #include "geomNode.h" #include "renderState.h" #include "textureAttrib.h" #include "dcast.h" #include "graphicsPipeSelection.h" #include "graphicsEngine.h" #include "graphicsBuffer.h" #include "graphicsStateGuardian.h" #include "load_prc_file.h" #include "windowProperties.h" #include "frameBufferProperties.h" #include "pystub.h" //////////////////////////////////////////////////////////////////// // Function: EggToBam::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// EggToBam:: EggToBam() : EggToSomething("Bam", ".bam", true, false) { set_program_description ("This program reads Egg files and outputs Bam files, the binary format " "suitable for direct loading of animation and models into Panda. Bam " "files are tied to a particular version of Panda, so should not be " "considered replacements for egg files, but they tend to be smaller and " "load much faster than the equivalent egg files."); // -f is always in effect for egg2bam. It doesn't make sense to // provide it as an option to the user. remove_option("f"); add_path_replace_options(); add_path_store_options(); add_option ("flatten", "flag", 0, "Specifies whether to flatten the egg hierarchy after it is loaded. " "If flag is zero, the egg hierarchy will not be flattened, but will " "instead be written to the bam file exactly as it is. If flag is " "non-zero, the hierarchy will be flattened so that unnecessary nodes " "(usually group nodes with only one child) are eliminated. The default " "if this is not specified is taken from the egg-flatten Config.prc " "variable.", &EggToBam::dispatch_int, &_has_egg_flatten, &_egg_flatten); add_option ("combine-geoms", "flag", 0, "Specifies whether to combine sibling GeomNodes into a common GeomNode " "when possible. This flag is only respected if flatten, above, is also " "enabled (or implicitly true from the Config.prc file). The default if " "this is not specified is taken from the egg-combine-geoms Config.prc " "variable.", &EggToBam::dispatch_int, &_has_egg_combine_geoms, &_egg_combine_geoms); add_option ("suppress-hidden", "flag", 0, "Specifies whether to suppress hidden geometry. If this is nonzero, " "egg geometry tagged as \"hidden\" will be removed from the final " "scene graph; otherwise, it will be preserved (but stashed). The " "default is nonzero, to remove it.", &EggToBam::dispatch_int, NULL, &_egg_suppress_hidden); add_option ("ls", "", 0, "Writes a scene graph listing to standard output after the egg " "file has been loaded, showing the nodes that will be written out.", &EggToBam::dispatch_none, &_ls); add_option ("C", "quality", 0, "Specify the quality level for lossy channel compression. If this " "is specified, the animation channels will be compressed at this " "quality level, which is normally an integer value between 0 and 100, " "inclusive, where higher numbers produce larger files with greater " "quality. Generally, 95 is the highest useful quality level. Use " "-NC (described below) to disable channel compression. If neither " "option is specified, the default comes from the Config.prc file.", &EggToBam::dispatch_int, &_has_compression_quality, &_compression_quality); add_option ("NC", "", 0, "Turn off lossy compression of animation channels. Channels will be " "written exactly as they are, losslessly.", &EggToBam::dispatch_none, &_compression_off); add_option ("rawtex", "", 0, "Record texture data directly in the bam file, instead of storing " "a reference to the texture elsewhere on disk. The textures are " "stored uncompressed, unless -ctex is also specified. " "A particular texture that is encoded into " "multiple different bam files in this way cannot be unified into " "the same part of texture memory if the different bam files are loaded " "together. That being said, this can sometimes be a convenient " "way to ensure the bam file is completely self-contained.", &EggToBam::dispatch_none, &_tex_rawdata); add_option ("txo", "", 0, "Rather than writing texture data directly into the bam file, as in " "-rawtex, create a texture object for each referenced texture. A " "texture object is a kind of mini-bam file, with a .txo extension, " "that contains all of the data needed to recreate a texture, including " "its image contents, filter and wrap settings, and so on. 3-D textures " "and cube maps can also be represented in a single .txo file. Texture " "object files, like bam files, are tied to a particular version of " "Panda.", &EggToBam::dispatch_none, &_tex_txo); #ifdef HAVE_ZLIB add_option ("txopz", "", 0, "In addition to writing texture object files as above, compress each " "one using pzip to a .txo.pz file. In many cases, this will yield a " "disk file size comparable to that achieved by png compression. This " "is an on-disk compression only, and does not affect the amount of " "RAM or texture memory consumed by the texture when it is loaded.", &EggToBam::dispatch_none, &_tex_txopz); #endif // HAVE_ZLIB add_option ("ctex", "", 0, #ifdef HAVE_SQUISH "Pre-compress the texture images using the libsquish library, when " "using -rawtex or -txo. " #else "Asks the graphics card to pre-compress the texture images when using " "-rawtex or -txo. " #endif // HAVE_SQUISH #ifdef HAVE_ZLIB "This is unrelated to the on-disk compression achieved " "via -txopz (and it may be used in conjunction with that parameter). " #endif // HAVE_ZLIB "This will result in a smaller RAM and texture memory footprint for " "the texture images. The same " "effect can be achieved at load time by setting compressed-textures in " "your Config.prc file; but -ctex pre-compresses the " "textures so that they do not need to be compressed at load time. " #ifndef HAVE_SQUISH "Note that, since your Panda is not compiled with the libsquish " "library, using -ctex will make .txo files that are only guaranteed " "to load on the particular graphics card that was used to " "generate them." #endif // HAVE_SQUISH , &EggToBam::dispatch_none, &_tex_ctex); add_option ("mipmap", "", 0, "Records the pre-generated mipmap levels in the texture object file " "when using -rawtex or -txo, regardless of the texture filter mode. This " "will increase the size of the texture object file by about 33%, but " "it prevents the need to compute the mipmaps at runtime. The default " "is to record mipmap levels only when the texture uses a mipmap " "filter mode.", &EggToBam::dispatch_none, &_tex_mipmap); add_option ("ctexq", "quality", 0, "Specifies the compression quality to use when performing the " "texture compression requested by -ctex. This may be one of " "'default', 'fastest', 'normal', or 'best'. The default is 'best'. " "Set it to 'default' to use whatever is specified by the Config.prc " "file. This is a global setting only; individual texture quality " "settings appearing within the egg file will override this.", &EggToBam::dispatch_string, NULL, &_ctex_quality); add_option ("load-display", "display name", 0, "Specifies the particular display module to load to perform the texture " "compression requested by -ctex. If this is omitted, the default is " "taken from the Config.prc file." #ifdef HAVE_SQUISH " Since your Panda has libsquish compiled in, this is not necessary; " "Panda can compress textures without loading a display module." #endif // HAVE_SQUISH , &EggToBam::dispatch_string, NULL, &_load_display); redescribe_option ("cs", "Specify the coordinate system of the resulting " + _format_name + " file. This may be " "one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default " "is z-up."); _force_complete = true; _egg_flatten = 0; _egg_combine_geoms = 0; _egg_suppress_hidden = 1; _tex_txopz = false; _ctex_quality = "best"; } //////////////////////////////////////////////////////////////////// // Function: EggToBam::run // Access: Public // Description: //////////////////////////////////////////////////////////////////// void EggToBam:: run() { if (_has_egg_flatten) { // If the user specified some -flatten, we need to set the // corresponding Config.prc variable. egg_flatten = (_egg_flatten != 0); } if (_has_egg_combine_geoms) { // Ditto with -combine_geoms. egg_combine_geoms = (_egg_combine_geoms != 0); } // We always set egg_suppress_hidden. egg_suppress_hidden = _egg_suppress_hidden; if (_compression_off) { // If the user specified -NC, turn off channel compression. compress_channels = false; } else if (_has_compression_quality) { // Otherwise, if the user specified a compression quality with -C, // use that quality level. compress_channels = true; compress_chan_quality = _compression_quality; } if (_ctex_quality != "default") { // Override the user's config file with the command-line parameter // for texture compression. string prc = "texture-quality-level " + _ctex_quality; load_prc_file_data("prc", prc); } if (!_got_coordinate_system) { // If the user didn't specify otherwise, ensure the coordinate // system is Z-up. _data->set_coordinate_system(CS_zup_right); } PT(PandaNode) root = load_egg_data(_data); if (root == (PandaNode *)NULL) { nout << "Unable to build scene graph from egg file.\n"; exit(1); } if (_tex_ctex) { #ifndef HAVE_SQUISH if (!make_buffer()) { nout << "Unable to initialize graphics context; cannot compress textures.\n"; exit(1); } #endif // HAVE_SQUISH } if (_tex_txo || _tex_txopz || (_tex_ctex && _tex_rawdata)) { collect_textures(root); Textures::iterator ti; for (ti = _textures.begin(); ti != _textures.end(); ++ti) { Texture *tex = (*ti); tex->get_ram_image(); bool want_mipmaps = (_tex_mipmap || tex->uses_mipmaps()); if (want_mipmaps) { // Generate mipmap levels. tex->generate_ram_mipmap_images(); } if (_tex_ctex) { #ifdef HAVE_SQUISH if (!tex->compress_ram_image()) { nout << " couldn't compress " << tex->get_name() << "\n"; } tex->set_compression(Texture::CM_on); #else // HAVE_SQUISH tex->set_keep_ram_image(true); bool has_mipmap_levels = (tex->get_num_ram_mipmap_images() > 1); if (!_engine->extract_texture_data(tex, _gsg)) { nout << " couldn't compress " << tex->get_name() << "\n"; } if (!has_mipmap_levels && !want_mipmaps) { // Make sure we didn't accidentally introduce mipmap levels // by rendezvousing through the graphics card. tex->clear_ram_mipmap_images(); } tex->set_keep_ram_image(false); #endif // HAVE_SQUISH } if (_tex_txo || _tex_txopz) { convert_txo(tex); } } } if (_ls) { root->ls(nout, 0); } // This should be guaranteed because we pass false to the // constructor, above. nassertv(has_output_filename()); Filename filename = get_output_filename(); filename.make_dir(); nout << "Writing " << filename << "\n"; BamFile bam_file; if (!bam_file.open_write(filename)) { nout << "Error in writing.\n"; exit(1); } if (!bam_file.write_object(root)) { nout << "Error in writing.\n"; exit(1); } } //////////////////////////////////////////////////////////////////// // Function: EggToBam::handle_args // Access: Protected, Virtual // Description: Does something with the additional arguments on the // command line (after all the -options have been // parsed). Returns true if the arguments are good, // false otherwise. //////////////////////////////////////////////////////////////////// bool EggToBam:: handle_args(ProgramBase::Args &args) { // If the user specified a path store option, we need to set the // bam-texture-mode Config.prc variable directly to support this // (otherwise the bam code will do what it wants to do anyway). if (_tex_rawdata) { bam_texture_mode = BamFile::BTM_rawdata; } else if (_got_path_store) { bam_texture_mode = BamFile::BTM_unchanged; } else { // Otherwise, the default path store is absolute; then the // bam-texture-mode can do the appropriate thing to it. _path_replace->_path_store = PS_absolute; } return EggToSomething::handle_args(args); } //////////////////////////////////////////////////////////////////// // Function: EggToBam::collect_textures // Access: Private // Description: Recursively walks the scene graph, looking for // Texture references. //////////////////////////////////////////////////////////////////// void EggToBam:: collect_textures(PandaNode *node) { collect_textures(node->get_state()); if (node->is_geom_node()) { GeomNode *geom_node = DCAST(GeomNode, node); int num_geoms = geom_node->get_num_geoms(); for (int i = 0; i < num_geoms; ++i) { collect_textures(geom_node->get_geom_state(i)); } } PandaNode::Children children = node->get_children(); int num_children = children.get_num_children(); for (int i = 0; i < num_children; ++i) { collect_textures(children.get_child(i)); } } //////////////////////////////////////////////////////////////////// // Function: EggToBam::collect_textures // Access: Private // Description: Recursively walks the scene graph, looking for // Texture references. //////////////////////////////////////////////////////////////////// void EggToBam:: collect_textures(const RenderState *state) { const TextureAttrib *tex_attrib = DCAST(TextureAttrib, state->get_attrib(TextureAttrib::get_class_type())); if (tex_attrib != (TextureAttrib *)NULL) { int num_on_stages = tex_attrib->get_num_on_stages(); for (int i = 0; i < num_on_stages; ++i) { _textures.insert(tex_attrib->get_on_texture(tex_attrib->get_on_stage(i))); } } } //////////////////////////////////////////////////////////////////// // Function: EggToBam::convert_txo // Access: Private // Description: If the indicated Texture was not already loaded from // a txo file, writes it to a txo file and updates the // Texture object to reference the new file. //////////////////////////////////////////////////////////////////// void EggToBam:: convert_txo(Texture *tex) { if (!tex->get_loaded_from_txo()) { Filename fullpath = tex->get_fullpath().get_filename_index(0); if (_tex_txopz) { fullpath.set_extension("txo.pz"); // We use this clumsy syntax so that the new extension appears to be // two separate extensions, .txo followed by .pz, which is what // Texture::write() expects to find. fullpath = Filename(fullpath.get_fullpath()); } else { fullpath.set_extension("txo"); } if (tex->write(fullpath)) { nout << " Writing " << fullpath; if (tex->get_ram_image_compression() != Texture::CM_off) { nout << " (compressed " << tex->get_ram_image_compression() << ")"; } nout << "\n"; tex->set_loaded_from_txo(); tex->set_fullpath(fullpath); tex->clear_alpha_fullpath(); Filename filename = tex->get_filename().get_filename_index(0); if (_tex_txopz) { filename.set_extension("txo.pz"); filename = Filename(filename.get_fullpath()); } else { filename.set_extension("txo"); } tex->set_filename(filename); tex->clear_alpha_filename(); } } } //////////////////////////////////////////////////////////////////// // Function: EggToBam::make_buffer // Access: Private // Description: Creates a GraphicsBuffer for communicating with the // graphics card. //////////////////////////////////////////////////////////////////// bool EggToBam:: make_buffer() { if (!_load_display.empty()) { // Override the user's config file with the command-line parameter. string prc = "load-display " + _load_display; load_prc_file_data("prc", prc); } GraphicsPipeSelection *selection = GraphicsPipeSelection::get_global_ptr(); _pipe = selection->make_default_pipe(); if (_pipe == (GraphicsPipe *)NULL) { nout << "Unable to create graphics pipe.\n"; return false; } _engine = new GraphicsEngine; FrameBufferProperties fbprops = FrameBufferProperties::get_default(); // Some graphics drivers can only create single-buffered offscreen // buffers. So request that. fbprops.set_back_buffers(0); WindowProperties winprops; winprops.set_size(1, 1); winprops.set_origin(0, 0); winprops.set_undecorated(true); winprops.set_open(true); winprops.set_z_order(WindowProperties::Z_bottom); // We don't care how big the buffer is; we just need it to manifest // the GSG. _buffer = _engine->make_output(_pipe, "buffer", 0, fbprops, winprops, GraphicsPipe::BF_fb_props_optional); _engine->open_windows(); if (_buffer == (GraphicsOutput *)NULL || !_buffer->is_valid()) { nout << "Unable to create graphics window.\n"; return false; } _gsg = _buffer->get_gsg(); return true; } int main(int argc, char *argv[]) { // A call to pystub() to force libpystub.so to be linked in. pystub(); EggToBam prog; prog.parse_command_line(argc, argv); prog.run(); return 0; }