Panda3D

eggToBam.cxx

00001 // Filename: eggToBam.cxx
00002 // Created by:  drose (28Jun00)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "eggToBam.h"
00016 
00017 #include "config_util.h"
00018 #include "bamFile.h"
00019 #include "load_egg_file.h"
00020 #include "config_egg2pg.h"
00021 #include "config_gobj.h"
00022 #include "config_chan.h"
00023 #include "pandaNode.h"
00024 #include "geomNode.h"
00025 #include "renderState.h"
00026 #include "textureAttrib.h"
00027 #include "dcast.h"
00028 #include "graphicsPipeSelection.h"
00029 #include "graphicsEngine.h"
00030 #include "graphicsBuffer.h"
00031 #include "graphicsStateGuardian.h"
00032 #include "load_prc_file.h"
00033 #include "windowProperties.h"
00034 #include "frameBufferProperties.h"
00035 #include "pystub.h"
00036 
00037 ////////////////////////////////////////////////////////////////////
00038 //     Function: EggToBam::Constructor
00039 //       Access: Public
00040 //  Description:
00041 ////////////////////////////////////////////////////////////////////
00042 EggToBam::
00043 EggToBam() :
00044   EggToSomething("Bam", ".bam", true, false)
00045 {
00046   set_program_description
00047     ("This program reads Egg files and outputs Bam files, the binary format "
00048      "suitable for direct loading of animation and models into Panda.  Bam "
00049      "files are tied to a particular version of Panda, so should not be "
00050      "considered replacements for egg files, but they tend to be smaller and "
00051      "load much faster than the equivalent egg files.");
00052 
00053   // -f is always in effect for egg2bam.  It doesn't make sense to
00054   // provide it as an option to the user.
00055   remove_option("f");
00056 
00057   add_path_replace_options();
00058   add_path_store_options();
00059 
00060   add_option
00061     ("flatten", "flag", 0,
00062      "Specifies whether to flatten the egg hierarchy after it is loaded.  "
00063      "If flag is zero, the egg hierarchy will not be flattened, but will "
00064      "instead be written to the bam file exactly as it is.  If flag is "
00065      "non-zero, the hierarchy will be flattened so that unnecessary nodes "
00066      "(usually group nodes with only one child) are eliminated.  The default "
00067      "if this is not specified is taken from the egg-flatten Config.prc "
00068      "variable.",
00069      &EggToBam::dispatch_int, &_has_egg_flatten, &_egg_flatten);
00070 
00071   add_option
00072     ("combine-geoms", "flag", 0,
00073      "Specifies whether to combine sibling GeomNodes into a common GeomNode "
00074      "when possible.  This flag is only respected if flatten, above, is also "
00075      "enabled (or implicitly true from the Config.prc file).  The default if "
00076      "this is not specified is taken from the egg-combine-geoms Config.prc "
00077      "variable.",
00078      &EggToBam::dispatch_int, &_has_egg_combine_geoms, &_egg_combine_geoms);
00079 
00080   add_option
00081     ("suppress-hidden", "flag", 0,
00082      "Specifies whether to suppress hidden geometry.  If this is nonzero, "
00083      "egg geometry tagged as \"hidden\" will be removed from the final "
00084      "scene graph; otherwise, it will be preserved (but stashed).  The "
00085      "default is nonzero, to remove it.",
00086      &EggToBam::dispatch_int, NULL, &_egg_suppress_hidden);
00087 
00088   add_option
00089     ("ls", "", 0,
00090      "Writes a scene graph listing to standard output after the egg "
00091      "file has been loaded, showing the nodes that will be written out.",
00092      &EggToBam::dispatch_none, &_ls);
00093 
00094   add_option
00095     ("C", "quality", 0,
00096      "Specify the quality level for lossy channel compression.  If this "
00097      "is specified, the animation channels will be compressed at this "
00098      "quality level, which is normally an integer value between 0 and 100, "
00099      "inclusive, where higher numbers produce larger files with greater "
00100      "quality.  Generally, 95 is the highest useful quality level.  Use "
00101      "-NC (described below) to disable channel compression.  If neither "
00102      "option is specified, the default comes from the Config.prc file.",
00103      &EggToBam::dispatch_int, &_has_compression_quality, &_compression_quality);
00104 
00105   add_option
00106     ("NC", "", 0,
00107      "Turn off lossy compression of animation channels.  Channels will be "
00108      "written exactly as they are, losslessly.",
00109      &EggToBam::dispatch_none, &_compression_off);
00110 
00111   add_option
00112     ("rawtex", "", 0,
00113      "Record texture data directly in the bam file, instead of storing "
00114      "a reference to the texture elsewhere on disk.  The textures are "
00115      "stored uncompressed, unless -ctex is also specified.  "
00116      "A particular texture that is encoded into "
00117      "multiple different bam files in this way cannot be unified into "
00118      "the same part of texture memory if the different bam files are loaded "
00119      "together.  That being said, this can sometimes be a convenient "
00120      "way to ensure the bam file is completely self-contained.",
00121      &EggToBam::dispatch_none, &_tex_rawdata);
00122 
00123   add_option
00124     ("txo", "", 0,
00125      "Rather than writing texture data directly into the bam file, as in "
00126      "-rawtex, create a texture object for each referenced texture.  A "
00127      "texture object is a kind of mini-bam file, with a .txo extension, "
00128      "that contains all of the data needed to recreate a texture, including "
00129      "its image contents, filter and wrap settings, and so on.  3-D textures "
00130      "and cube maps can also be represented in a single .txo file.  Texture "
00131      "object files, like bam files, are tied to a particular version of "
00132      "Panda.",
00133      &EggToBam::dispatch_none, &_tex_txo);
00134 
00135 #ifdef HAVE_ZLIB
00136   add_option
00137     ("txopz", "", 0,
00138      "In addition to writing texture object files as above, compress each "
00139      "one using pzip to a .txo.pz file.  In many cases, this will yield a "
00140      "disk file size comparable to that achieved by png compression.  This "
00141      "is an on-disk compression only, and does not affect the amount of "
00142      "RAM or texture memory consumed by the texture when it is loaded.",
00143      &EggToBam::dispatch_none, &_tex_txopz);
00144 #endif  // HAVE_ZLIB
00145 
00146   add_option
00147     ("ctex", "", 0,
00148 #ifdef HAVE_SQUISH
00149      "Pre-compress the texture images using the libsquish library, when "
00150      "using -rawtex or -txo.  "
00151 #else
00152      "Asks the graphics card to pre-compress the texture images when using "
00153      "-rawtex or -txo.  "
00154 #endif  // HAVE_SQUISH
00155 #ifdef HAVE_ZLIB
00156      "This is unrelated to the on-disk compression achieved "
00157      "via -txopz (and it may be used in conjunction with that parameter).  "
00158 #endif  // HAVE_ZLIB
00159      "This will result in a smaller RAM and texture memory footprint for "
00160      "the texture images.  The same "
00161      "effect can be achieved at load time by setting compressed-textures in "
00162      "your Config.prc file; but -ctex pre-compresses the "
00163      "textures so that they do not need to be compressed at load time.  "
00164 #ifndef HAVE_SQUISH
00165      "Note that, since your Panda is not compiled with the libsquish "
00166      "library, using -ctex will make .txo files that are only guaranteed "
00167      "to load on the particular graphics card that was used to "
00168      "generate them."
00169 #endif  // HAVE_SQUISH
00170      ,
00171      &EggToBam::dispatch_none, &_tex_ctex);
00172 
00173   add_option
00174     ("mipmap", "", 0,
00175      "Records the pre-generated mipmap levels in the texture object file "
00176      "when using -rawtex or -txo, regardless of the texture filter mode.  This "
00177      "will increase the size of the texture object file by about 33%, but "
00178      "it prevents the need to compute the mipmaps at runtime.  The default "
00179      "is to record mipmap levels only when the texture uses a mipmap "
00180      "filter mode.",
00181      &EggToBam::dispatch_none, &_tex_mipmap);
00182 
00183   add_option
00184     ("ctexq", "quality", 0,
00185      "Specifies the compression quality to use when performing the "
00186      "texture compression requested by -ctex.  This may be one of "
00187      "'default', 'fastest', 'normal', or 'best'.  The default is 'best'.  "
00188      "Set it to 'default' to use whatever is specified by the Config.prc "
00189      "file.  This is a global setting only; individual texture quality "
00190      "settings appearing within the egg file will override this.",
00191      &EggToBam::dispatch_string, NULL, &_ctex_quality);
00192 
00193   add_option
00194     ("load-display", "display name", 0,
00195      "Specifies the particular display module to load to perform the texture "
00196      "compression requested by -ctex.  If this is omitted, the default is "
00197      "taken from the Config.prc file."
00198 #ifdef HAVE_SQUISH
00199      "  Since your Panda has libsquish compiled in, this is not necessary; "
00200      "Panda can compress textures without loading a display module."
00201 #endif  // HAVE_SQUISH
00202      ,
00203      &EggToBam::dispatch_string, NULL, &_load_display);
00204 
00205   redescribe_option
00206     ("cs",
00207      "Specify the coordinate system of the resulting " + _format_name +
00208      " file.  This may be "
00209      "one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'.  The default "
00210      "is z-up.");
00211 
00212   _force_complete = true;
00213   _egg_flatten = 0;
00214   _egg_combine_geoms = 0;
00215   _egg_suppress_hidden = 1;
00216   _tex_txopz = false;
00217   _ctex_quality = "best";
00218 }
00219 
00220 ////////////////////////////////////////////////////////////////////
00221 //     Function: EggToBam::run
00222 //       Access: Public
00223 //  Description:
00224 ////////////////////////////////////////////////////////////////////
00225 void EggToBam::
00226 run() {
00227   if (_has_egg_flatten) {
00228     // If the user specified some -flatten, we need to set the
00229     // corresponding Config.prc variable.
00230     egg_flatten = (_egg_flatten != 0);
00231   }
00232   if (_has_egg_combine_geoms) {
00233     // Ditto with -combine_geoms.
00234     egg_combine_geoms = (_egg_combine_geoms != 0);
00235   }
00236 
00237   // We always set egg_suppress_hidden.
00238   egg_suppress_hidden = _egg_suppress_hidden;
00239 
00240   if (_compression_off) {
00241     // If the user specified -NC, turn off channel compression.
00242     compress_channels = false;
00243 
00244   } else if (_has_compression_quality) {
00245     // Otherwise, if the user specified a compression quality with -C,
00246     // use that quality level.
00247     compress_channels = true;
00248     compress_chan_quality = _compression_quality;
00249   }
00250 
00251   if (_ctex_quality != "default") {
00252     // Override the user's config file with the command-line parameter
00253     // for texture compression.
00254     string prc = "texture-quality-level " + _ctex_quality;
00255     load_prc_file_data("prc", prc);
00256   }
00257 
00258   if (!_got_coordinate_system) {
00259     // If the user didn't specify otherwise, ensure the coordinate
00260     // system is Z-up.
00261     _data->set_coordinate_system(CS_zup_right);
00262   }
00263 
00264   PT(PandaNode) root = load_egg_data(_data);
00265   if (root == (PandaNode *)NULL) {
00266     nout << "Unable to build scene graph from egg file.\n";
00267     exit(1);
00268   }
00269 
00270   if (_tex_ctex) {
00271 #ifndef HAVE_SQUISH
00272     if (!make_buffer()) {
00273       nout << "Unable to initialize graphics context; cannot compress textures.\n";
00274       exit(1);
00275     }
00276 #endif  // HAVE_SQUISH
00277   }
00278 
00279   if (_tex_txo || _tex_txopz || (_tex_ctex && _tex_rawdata)) {
00280     collect_textures(root);
00281     Textures::iterator ti;
00282     for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
00283       Texture *tex = (*ti);
00284       tex->get_ram_image();
00285       bool want_mipmaps = (_tex_mipmap || tex->uses_mipmaps());
00286       if (want_mipmaps) {
00287   // Generate mipmap levels.
00288   tex->generate_ram_mipmap_images();
00289       }
00290 
00291       if (_tex_ctex) {
00292 #ifdef HAVE_SQUISH
00293         if (!tex->compress_ram_image()) {
00294           nout << "  couldn't compress " << tex->get_name() << "\n";
00295         }
00296         tex->set_compression(Texture::CM_on);
00297 #else  // HAVE_SQUISH
00298         tex->set_keep_ram_image(true);
00299   bool has_mipmap_levels = (tex->get_num_ram_mipmap_images() > 1);
00300         if (!_engine->extract_texture_data(tex, _gsg)) {
00301           nout << "  couldn't compress " << tex->get_name() << "\n";
00302         }
00303   if (!has_mipmap_levels && !want_mipmaps) {
00304     // Make sure we didn't accidentally introduce mipmap levels
00305     // by rendezvousing through the graphics card.
00306     tex->clear_ram_mipmap_images();
00307   }
00308         tex->set_keep_ram_image(false);
00309 #endif  // HAVE_SQUISH
00310       }
00311 
00312       if (_tex_txo || _tex_txopz) {
00313         convert_txo(tex);
00314       }
00315     }
00316   }
00317   
00318   if (_ls) {
00319     root->ls(nout, 0);
00320   }
00321   
00322   // This should be guaranteed because we pass false to the
00323   // constructor, above.
00324   nassertv(has_output_filename());
00325 
00326   Filename filename = get_output_filename();
00327   filename.make_dir();
00328   nout << "Writing " << filename << "\n";
00329   BamFile bam_file;
00330   if (!bam_file.open_write(filename)) {
00331     nout << "Error in writing.\n";
00332     exit(1);
00333   }
00334   
00335   if (!bam_file.write_object(root)) {
00336     nout << "Error in writing.\n";
00337     exit(1);
00338   }
00339 }
00340 
00341 ////////////////////////////////////////////////////////////////////
00342 //     Function: EggToBam::handle_args
00343 //       Access: Protected, Virtual
00344 //  Description: Does something with the additional arguments on the
00345 //               command line (after all the -options have been
00346 //               parsed).  Returns true if the arguments are good,
00347 //               false otherwise.
00348 ////////////////////////////////////////////////////////////////////
00349 bool EggToBam::
00350 handle_args(ProgramBase::Args &args) {
00351   // If the user specified a path store option, we need to set the
00352   // bam-texture-mode Config.prc variable directly to support this
00353   // (otherwise the bam code will do what it wants to do anyway).
00354   if (_tex_rawdata) {
00355     bam_texture_mode = BamFile::BTM_rawdata;
00356 
00357   } else if (_got_path_store) {
00358     bam_texture_mode = BamFile::BTM_unchanged;
00359 
00360   } else {
00361     // Otherwise, the default path store is absolute; then the
00362     // bam-texture-mode can do the appropriate thing to it.
00363     _path_replace->_path_store = PS_absolute;
00364   }
00365 
00366   return EggToSomething::handle_args(args);
00367 }
00368 
00369 ////////////////////////////////////////////////////////////////////
00370 //     Function: EggToBam::collect_textures
00371 //       Access: Private
00372 //  Description: Recursively walks the scene graph, looking for
00373 //               Texture references.
00374 ////////////////////////////////////////////////////////////////////
00375 void EggToBam::
00376 collect_textures(PandaNode *node) {
00377   collect_textures(node->get_state());
00378   if (node->is_geom_node()) {
00379     GeomNode *geom_node = DCAST(GeomNode, node);
00380     int num_geoms = geom_node->get_num_geoms();
00381     for (int i = 0; i < num_geoms; ++i) {
00382       collect_textures(geom_node->get_geom_state(i));
00383     }
00384   }
00385 
00386   PandaNode::Children children = node->get_children();
00387   int num_children = children.get_num_children();
00388   for (int i = 0; i < num_children; ++i) {
00389     collect_textures(children.get_child(i));
00390   }
00391 }
00392 
00393 ////////////////////////////////////////////////////////////////////
00394 //     Function: EggToBam::collect_textures
00395 //       Access: Private
00396 //  Description: Recursively walks the scene graph, looking for
00397 //               Texture references.
00398 ////////////////////////////////////////////////////////////////////
00399 void EggToBam::
00400 collect_textures(const RenderState *state) {
00401   const TextureAttrib *tex_attrib = DCAST(TextureAttrib, state->get_attrib(TextureAttrib::get_class_type()));
00402   if (tex_attrib != (TextureAttrib *)NULL) {
00403     int num_on_stages = tex_attrib->get_num_on_stages();
00404     for (int i = 0; i < num_on_stages; ++i) {
00405       _textures.insert(tex_attrib->get_on_texture(tex_attrib->get_on_stage(i)));
00406     }
00407   }
00408 }
00409 
00410 ////////////////////////////////////////////////////////////////////
00411 //     Function: EggToBam::convert_txo
00412 //       Access: Private
00413 //  Description: If the indicated Texture was not already loaded from
00414 //               a txo file, writes it to a txo file and updates the
00415 //               Texture object to reference the new file.
00416 ////////////////////////////////////////////////////////////////////
00417 void EggToBam::
00418 convert_txo(Texture *tex) {
00419   if (!tex->get_loaded_from_txo()) {
00420     Filename fullpath = tex->get_fullpath().get_filename_index(0);
00421     if (_tex_txopz) {
00422       fullpath.set_extension("txo.pz");
00423       // We use this clumsy syntax so that the new extension appears to be
00424       // two separate extensions, .txo followed by .pz, which is what
00425       // Texture::write() expects to find.
00426       fullpath = Filename(fullpath.get_fullpath());
00427     } else {
00428       fullpath.set_extension("txo");
00429     }
00430 
00431     if (tex->write(fullpath)) {
00432       nout << "  Writing " << fullpath;
00433       if (tex->get_ram_image_compression() != Texture::CM_off) {
00434         nout << " (compressed " << tex->get_ram_image_compression() << ")";
00435       }
00436       nout << "\n";
00437       tex->set_loaded_from_txo();
00438       tex->set_fullpath(fullpath);
00439       tex->clear_alpha_fullpath();
00440 
00441       Filename filename = tex->get_filename().get_filename_index(0);
00442       if (_tex_txopz) {
00443         filename.set_extension("txo.pz");
00444         filename = Filename(filename.get_fullpath());
00445       } else {
00446         filename.set_extension("txo");
00447       }
00448 
00449       tex->set_filename(filename);
00450       tex->clear_alpha_filename();
00451     }
00452   }
00453 }
00454 
00455 ////////////////////////////////////////////////////////////////////
00456 //     Function: EggToBam::make_buffer
00457 //       Access: Private
00458 //  Description: Creates a GraphicsBuffer for communicating with the
00459 //               graphics card.
00460 ////////////////////////////////////////////////////////////////////
00461 bool EggToBam::
00462 make_buffer() {
00463   if (!_load_display.empty()) {
00464     // Override the user's config file with the command-line parameter.
00465     string prc = "load-display " + _load_display;
00466     load_prc_file_data("prc", prc);
00467   }
00468 
00469   GraphicsPipeSelection *selection = GraphicsPipeSelection::get_global_ptr();
00470   _pipe = selection->make_default_pipe();
00471   if (_pipe == (GraphicsPipe *)NULL) {
00472     nout << "Unable to create graphics pipe.\n";
00473     return false;
00474   }
00475 
00476   _engine = new GraphicsEngine;
00477 
00478   FrameBufferProperties fbprops = FrameBufferProperties::get_default();
00479 
00480   // Some graphics drivers can only create single-buffered offscreen
00481   // buffers.  So request that.
00482   fbprops.set_back_buffers(0);
00483 
00484   WindowProperties winprops;
00485   winprops.set_size(1, 1);
00486   winprops.set_origin(0, 0);
00487   winprops.set_undecorated(true);
00488   winprops.set_open(true);
00489   winprops.set_z_order(WindowProperties::Z_bottom);
00490 
00491   // We don't care how big the buffer is; we just need it to manifest
00492   // the GSG.
00493   _buffer = _engine->make_output(_pipe, "buffer", 0, 
00494                                  fbprops, winprops,
00495                                  GraphicsPipe::BF_fb_props_optional);
00496   _engine->open_windows();
00497   if (_buffer == (GraphicsOutput *)NULL || !_buffer->is_valid()) {
00498     nout << "Unable to create graphics window.\n";
00499     return false;
00500   }
00501   _gsg = _buffer->get_gsg();
00502 
00503   return true;
00504 }
00505 
00506 
00507 int main(int argc, char *argv[]) {
00508   // A call to pystub() to force libpystub.so to be linked in.
00509   pystub();
00510 
00511   EggToBam prog;
00512   prog.parse_command_line(argc, argv);
00513   prog.run();
00514   return 0;
00515 }
 All Classes Functions Variables Enumerations