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