eggData.cxx

00001 // Filename: eggData.cxx
00002 // Created by:  drose (20Jan99)
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 "eggData.h"
00016 #include "eggCoordinateSystem.h"
00017 #include "eggTextureCollection.h"
00018 #include "eggMaterialCollection.h"
00019 #include "eggComment.h"
00020 #include "eggPoolUniquifier.h"
00021 #include "config_egg.h"
00022 #include "config_util.h"
00023 #include "config_express.h"
00024 #include "string_utils.h"
00025 #include "dSearchPath.h"
00026 #include "virtualFileSystem.h"
00027 #include "lightMutexHolder.h"
00028 #include "zStream.h"
00029 
00030 extern int eggyyparse();
00031 #include "parserDefs.h"
00032 #include "lexerDefs.h"
00033 
00034 TypeHandle EggData::_type_handle;
00035 
00036 ////////////////////////////////////////////////////////////////////
00037 //     Function: EggData::resolve_egg_filename
00038 //       Access: Public, Static
00039 //  Description: Looks for the indicated filename, first along the
00040 //               indicated searchpath, and then along the model_path.
00041 //               If found, updates the filename to the full path and
00042 //               returns true; otherwise, returns false.
00043 ////////////////////////////////////////////////////////////////////
00044 bool EggData::
00045 resolve_egg_filename(Filename &egg_filename, const DSearchPath &searchpath) {
00046   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
00047   
00048   if (egg_filename.is_fully_qualified() && vfs->exists(egg_filename)) {
00049     return true;
00050   }
00051   
00052   vfs->resolve_filename(egg_filename, searchpath, "egg") ||
00053     vfs->resolve_filename(egg_filename, get_model_path(), "egg");
00054   
00055   return vfs->exists(egg_filename);
00056 }
00057 
00058 ////////////////////////////////////////////////////////////////////
00059 //     Function: EggData::read
00060 //       Access: Public
00061 //  Description: Opens the indicated filename and reads the egg data
00062 //               contents from it.  Returns true if the file was
00063 //               successfully opened and read, false if there were
00064 //               some errors, in which case the data may be partially
00065 //               read.
00066 //
00067 //               error is the output stream to which to write error
00068 //               messages.
00069 ////////////////////////////////////////////////////////////////////
00070 bool EggData::
00071 read(Filename filename, string display_name) {
00072   filename.set_text();
00073   set_egg_filename(filename);
00074 
00075   if (display_name.empty()) {
00076     display_name = filename;
00077   }
00078 
00079   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
00080     
00081   istream *file = vfs->open_read_file(filename, true);
00082   if (file == (istream *)NULL) {
00083     egg_cat.error() << "Unable to open " << display_name << "\n";
00084     return false;
00085   }
00086   
00087   egg_cat.info()
00088     << "Reading " << display_name << "\n";
00089   
00090   bool read_ok = read(*file);
00091   vfs->close_read_file(file);
00092   return read_ok;
00093 }
00094 
00095 
00096 ////////////////////////////////////////////////////////////////////
00097 //     Function: EggData::read
00098 //       Access: Public
00099 //  Description: Parses the egg syntax contained in the indicated
00100 //               input stream.  Returns true if the stream was a
00101 //               completely valid egg file, false if there were some
00102 //               errors, in which case the data may be partially read.
00103 //
00104 //               Before you call this routine, you should probably
00105 //               call set_egg_filename() to set the name of the egg
00106 //               file we're processing, if at all possible.  If there
00107 //               is no such filename, you may set it to the empty
00108 //               string.
00109 ////////////////////////////////////////////////////////////////////
00110 bool EggData::
00111 read(istream &in) {
00112   // First, dispense with any children we had previously.  We will
00113   // replace them with the new data.
00114   clear();
00115 
00116   // Create a temporary EggData structure to read into.  We initialize
00117   // it with a copy of ourselves, so that it will get our _coordsys
00118   // value, if the user set it.
00119   PT(EggData) data = new EggData(*this);
00120 
00121   int error_count;
00122   {
00123     LightMutexHolder holder(egg_lock);
00124     egg_init_parser(in, get_egg_filename(), data, data);
00125     eggyyparse();
00126     egg_cleanup_parser();
00127     error_count = egg_error_count();
00128   }
00129 
00130   data->post_read();
00131 
00132   steal_children(*data);
00133   (*this) = *data;
00134 
00135   return (error_count == 0);
00136 }
00137 
00138 ////////////////////////////////////////////////////////////////////
00139 //     Function: EggData::merge
00140 //       Access: Public
00141 //  Description: Appends the other egg structure to the end of this
00142 //               one.  The other egg structure is invalidated.
00143 ////////////////////////////////////////////////////////////////////
00144 void EggData::
00145 merge(EggData &other) {
00146   if (get_coordinate_system() == CS_default) {
00147     // If we haven't specified a coordinate system yet, we inherit the
00148     // other one's.
00149     set_coordinate_system(other.get_coordinate_system());
00150 
00151   } else {
00152     // Otherwise, the other one is forced into our coordinate system
00153     // before we merge.
00154     other.set_coordinate_system(get_coordinate_system());
00155   }
00156   steal_children(other);
00157 }
00158 
00159 
00160 ////////////////////////////////////////////////////////////////////
00161 //     Function: EggData::load_externals
00162 //       Access: Public
00163 //  Description: Loads up all the egg files referenced by <File>
00164 //               entries within the egg structure, and inserts their
00165 //               contents in place of the <File> entries.  Searches
00166 //               for files in the searchpath, if not found directly,
00167 //               and writes error messages to the indicated output
00168 //               stream.  Returns true if all externals were loaded
00169 //               successfully, false otherwise.
00170 ////////////////////////////////////////////////////////////////////
00171 bool EggData::
00172 load_externals(const DSearchPath &searchpath) {
00173   return
00174     r_load_externals(searchpath, get_coordinate_system(), NULL);
00175 }
00176 
00177 ////////////////////////////////////////////////////////////////////
00178 //     Function: EggData::load_externals
00179 //       Access: Public
00180 //  Description: Loads up all the egg files referenced by <File>
00181 //               entries within the egg structure, and inserts their
00182 //               contents in place of the <File> entries.  Searches
00183 //               for files in the searchpath, if not found directly,
00184 //               and writes error messages to the indicated output
00185 //               stream.  Returns true if all externals were loaded
00186 //               successfully, false otherwise.
00187 ////////////////////////////////////////////////////////////////////
00188 bool EggData::
00189 load_externals(const DSearchPath &searchpath, BamCacheRecord *record) {
00190   return
00191     r_load_externals(searchpath, get_coordinate_system(), record);
00192 }
00193 
00194 ////////////////////////////////////////////////////////////////////
00195 //     Function: EggData::collapse_equivalent_textures
00196 //       Access: Public
00197 //  Description: Removes duplicate references to the same texture
00198 //               image with the same properties.  Considers two
00199 //               texture references with identical properties, but
00200 //               different tref names, to be equivalent, and collapses
00201 //               them, choosing one tref name to keep arbitrarily.
00202 //               Returns the number of textures removed.
00203 ////////////////////////////////////////////////////////////////////
00204 int EggData::
00205 collapse_equivalent_textures() {
00206   EggTextureCollection textures;
00207   textures.find_used_textures(this);
00208   return
00209     textures.collapse_equivalent_textures(~EggTexture::E_tref_name, this);
00210 }
00211 
00212 ////////////////////////////////////////////////////////////////////
00213 //     Function: EggData::collapse_equivalent_materials
00214 //       Access: Public
00215 //  Description: Removes duplicate references to the same material
00216 //               with the same properties.  Considers two material
00217 //               references with identical properties, but different
00218 //               mref names, to be equivalent, and collapses them,
00219 //               choosing one mref name to keep arbitrarily.  Returns
00220 //               the number of materials removed.
00221 ////////////////////////////////////////////////////////////////////
00222 int EggData::
00223 collapse_equivalent_materials() {
00224   EggMaterialCollection materials;
00225   materials.find_used_materials(this);
00226   return
00227     materials.collapse_equivalent_materials(~EggMaterial::E_mref_name, this);
00228 }
00229 
00230 ////////////////////////////////////////////////////////////////////
00231 //     Function: EggData::write_egg
00232 //       Access: Public
00233 //  Description: The main interface for writing complete egg files.
00234 ////////////////////////////////////////////////////////////////////
00235 bool EggData::
00236 write_egg(Filename filename) {
00237   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
00238   filename.set_text();
00239   vfs->delete_file(filename);
00240   ostream *file = vfs->open_write_file(filename, true, true);
00241   if (file == (ostream *)NULL) {
00242     egg_cat.error() << "Unable to open " << filename << " for writing.\n";
00243     return false;
00244   }
00245 
00246   bool wrote_ok = write_egg(*file);
00247   vfs->close_write_file(file);
00248   return wrote_ok;
00249 }
00250 
00251 ////////////////////////////////////////////////////////////////////
00252 //     Function: EggData::write_egg
00253 //       Access: Public
00254 //  Description: The main interface for writing complete egg files.
00255 ////////////////////////////////////////////////////////////////////
00256 bool EggData::
00257 write_egg(ostream &out) {
00258   pre_write();
00259   write(out, 0);
00260   return true;
00261 }
00262 
00263 
00264 ////////////////////////////////////////////////////////////////////
00265 //     Function: EggData::set_coordinate_system
00266 //       Access: Public
00267 //  Description: Changes the coordinate system of the EggData.  If the
00268 //               coordinate system was previously different, this may
00269 //               result in a conversion of the data.
00270 ////////////////////////////////////////////////////////////////////
00271 void EggData::
00272 set_coordinate_system(CoordinateSystem new_coordsys) {
00273   if (new_coordsys == CS_default) {
00274     new_coordsys = get_default_coordinate_system();
00275   }
00276   if (new_coordsys != _coordsys &&
00277       (_coordsys != CS_default && _coordsys != CS_invalid)) {
00278     // Time to convert the data.
00279     LMatrix4d mat = LMatrix4d::convert_mat(_coordsys, new_coordsys);
00280     LMatrix4d inv = LMatrix4d::convert_mat(new_coordsys, _coordsys);
00281 
00282     r_transform(mat, inv, new_coordsys);
00283     r_transform_vertices(mat);
00284 
00285     // Now we have to update the under_flags to ensure that all the
00286     // cached relative matrices are correct.
00287     update_under(0);
00288   }
00289 
00290   _coordsys = new_coordsys;
00291 }
00292 
00293 ////////////////////////////////////////////////////////////////////
00294 //     Function: EggData::write
00295 //       Access: Protected, Virtual
00296 //  Description: Writes the egg data out to the indicated output
00297 //               stream.
00298 ////////////////////////////////////////////////////////////////////
00299 void EggData::
00300 write(ostream &out, int indent_level) const {
00301   PT(EggCoordinateSystem) ecs = new EggCoordinateSystem(_coordsys);
00302   ecs->write(out, indent_level);
00303   EggGroupNode::write(out, indent_level);
00304   out << flush;
00305 }
00306 
00307 
00308 ////////////////////////////////////////////////////////////////////
00309 //     Function: EggData::post_read
00310 //       Access: Private
00311 //  Description: Does whatever processing is appropriate after reading
00312 //               the data in from an egg file.
00313 ////////////////////////////////////////////////////////////////////
00314 void EggData::
00315 post_read() {
00316   CoordinateSystem old_coordsys = _coordsys;
00317   _coordsys = find_coordsys_entry();
00318 
00319   if (_coordsys == CS_default) {
00320     // If the egg file didn't contain a <CoordinateSystem> entry,
00321     // assume it's Y-up, by convention.
00322     _coordsys = CS_yup_right;
00323 
00324   } else if (_coordsys == CS_invalid) {
00325     egg_cat.warning()
00326       << "Contradictory <CoordinateSystem> entries encountered.\n";
00327     _coordsys = CS_yup_right;
00328   }
00329 
00330   r_mark_coordsys(_coordsys);
00331 
00332   if (old_coordsys != CS_default) {
00333     // Now if we had a previous definition, enforce it.  This might
00334     // convert the data to the given coordinate system.
00335     set_coordinate_system(old_coordsys);
00336   }
00337 
00338   // Fill this in before we automatically resolve pathnames.
00339   _had_absolute_pathnames = has_absolute_pathnames();
00340 
00341   if (get_auto_resolve_externals()) {
00342     // Resolve filenames that are relative to the egg file.
00343     DSearchPath dir;
00344     dir.append_directory(get_egg_filename().get_dirname());
00345     resolve_filenames(dir);
00346   }
00347 }
00348 
00349 ////////////////////////////////////////////////////////////////////
00350 //     Function: EggData::pre_write
00351 //       Access: Private
00352 //  Description: Does whatever processing is appropriate just before
00353 //               writing the data out to an egg file.  This includes
00354 //               verifying that vertex pool names are unique, etc.
00355 ////////////////////////////////////////////////////////////////////
00356 void EggData::
00357 pre_write() {
00358   // Pull out all of the texture definitions in the file and massage
00359   // them a bit.
00360   EggTextureCollection textures;
00361   textures.extract_textures(this);
00362 
00363   // Remove any textures that aren't being used.
00364   textures.remove_unused_textures(this);
00365 
00366   // Collapse out any textures that are completely equivalent.  For
00367   // this purpose, we consider two textures with identical properties
00368   // but different tref names to be different.
00369   textures.collapse_equivalent_textures(~0, this);
00370 
00371   // Make sure all of the textures have unique TRef names.
00372   textures.uniquify_trefs();
00373   textures.sort_by_tref();
00374 
00375   // Do the same thing with the materials.
00376   EggMaterialCollection materials;
00377   materials.extract_materials(this);
00378   materials.remove_unused_materials(this);
00379   materials.collapse_equivalent_materials(~0, this);
00380   materials.uniquify_mrefs();
00381   materials.sort_by_mref();
00382 
00383   // Now put them all back at the head of the file, after any initial
00384   // comment records.
00385   iterator ci = begin();
00386   while (ci != end() && (*ci)->is_of_type(EggComment::get_class_type())) {
00387     ++ci;
00388   }
00389   textures.insert_textures(this, ci);
00390   materials.insert_materials(this, ci);
00391 
00392   // Also make sure that the vertex pools are uniquely named.  This
00393   // also checks textures and materials, which is kind of redundant
00394   // since we just did that, but we don't mind.
00395   EggPoolUniquifier pu;
00396   pu.uniquify(this);
00397 }