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 }