Panda3D
eggData.cxx
1 // Filename: eggData.cxx
2 // Created by: drose (20Jan99)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "eggData.h"
16 #include "eggCoordinateSystem.h"
17 #include "eggTextureCollection.h"
18 #include "eggMaterialCollection.h"
19 #include "eggComment.h"
20 #include "eggPoolUniquifier.h"
21 #include "config_egg.h"
22 #include "config_util.h"
23 #include "config_express.h"
24 #include "string_utils.h"
25 #include "dSearchPath.h"
26 #include "virtualFileSystem.h"
27 #include "lightMutexHolder.h"
28 #include "zStream.h"
29 
30 extern int eggyyparse();
31 #include "parserDefs.h"
32 #include "lexerDefs.h"
33 
34 TypeHandle EggData::_type_handle;
35 
36 ////////////////////////////////////////////////////////////////////
37 // Function: EggData::resolve_egg_filename
38 // Access: Public, Static
39 // Description: Looks for the indicated filename, first along the
40 // indicated searchpath, and then along the model_path.
41 // If found, updates the filename to the full path and
42 // returns true; otherwise, returns false.
43 ////////////////////////////////////////////////////////////////////
44 bool EggData::
45 resolve_egg_filename(Filename &egg_filename, const DSearchPath &searchpath) {
47 
48  if (egg_filename.is_fully_qualified() && vfs->exists(egg_filename)) {
49  return true;
50  }
51 
52  vfs->resolve_filename(egg_filename, searchpath, "egg") ||
53  vfs->resolve_filename(egg_filename, get_model_path(), "egg");
54 
55  return vfs->exists(egg_filename);
56 }
57 
58 ////////////////////////////////////////////////////////////////////
59 // Function: EggData::read
60 // Access: Public
61 // Description: Opens the indicated filename and reads the egg data
62 // contents from it. Returns true if the file was
63 // successfully opened and read, false if there were
64 // some errors, in which case the data may be partially
65 // read.
66 //
67 // error is the output stream to which to write error
68 // messages.
69 ////////////////////////////////////////////////////////////////////
70 bool EggData::
71 read(Filename filename, string display_name) {
72  filename.set_text();
73  set_egg_filename(filename);
74 
75  if (display_name.empty()) {
76  display_name = filename;
77  }
78 
80 
81  PT(VirtualFile) vfile = vfs->get_file(filename);
82  if (vfile == NULL) {
83  egg_cat.error() << "Could not find " << display_name << "\n";
84  return false;
85  }
86  set_egg_timestamp(vfile->get_timestamp());
87 
88  istream *file = vfile->open_read_file(true);
89  if (file == (istream *)NULL) {
90  egg_cat.error() << "Unable to open " << display_name << "\n";
91  return false;
92  }
93 
94  egg_cat.info()
95  << "Reading " << display_name << "\n";
96 
97  bool read_ok = read(*file);
98  vfile->close_read_file(file);
99  return read_ok;
100 }
101 
102 
103 ////////////////////////////////////////////////////////////////////
104 // Function: EggData::read
105 // Access: Public
106 // Description: Parses the egg syntax contained in the indicated
107 // input stream. Returns true if the stream was a
108 // completely valid egg file, false if there were some
109 // errors, in which case the data may be partially read.
110 //
111 // Before you call this routine, you should probably
112 // call set_egg_filename() to set the name of the egg
113 // file we're processing, if at all possible. If there
114 // is no such filename, you may set it to the empty
115 // string.
116 ////////////////////////////////////////////////////////////////////
117 bool EggData::
118 read(istream &in) {
119  // First, dispense with any children we had previously. We will
120  // replace them with the new data.
121  clear();
122 
123  // Create a temporary EggData structure to read into. We initialize
124  // it with a copy of ourselves, so that it will get our _coordsys
125  // value, if the user set it.
126  PT(EggData) data = new EggData(*this);
127 
128  int error_count;
129  {
130  LightMutexHolder holder(egg_lock);
131  egg_init_parser(in, get_egg_filename(), data, data);
132  eggyyparse();
133  egg_cleanup_parser();
134  error_count = egg_error_count();
135  }
136 
137  data->post_read();
138 
139  steal_children(*data);
140  (*this) = *data;
141 
142  return (error_count == 0);
143 }
144 
145 ////////////////////////////////////////////////////////////////////
146 // Function: EggData::merge
147 // Access: Public
148 // Description: Appends the other egg structure to the end of this
149 // one. The other egg structure is invalidated.
150 ////////////////////////////////////////////////////////////////////
151 void EggData::
152 merge(EggData &other) {
153  if (get_coordinate_system() == CS_default) {
154  // If we haven't specified a coordinate system yet, we inherit the
155  // other one's.
157 
158  } else {
159  // Otherwise, the other one is forced into our coordinate system
160  // before we merge.
162  }
163  steal_children(other);
164 }
165 
166 
167 ////////////////////////////////////////////////////////////////////
168 // Function: EggData::load_externals
169 // Access: Public
170 // Description: Loads up all the egg files referenced by <File>
171 // entries within the egg structure, and inserts their
172 // contents in place of the <File> entries. Searches
173 // for files in the searchpath, if not found directly,
174 // and writes error messages to the indicated output
175 // stream. Returns true if all externals were loaded
176 // successfully, false otherwise.
177 ////////////////////////////////////////////////////////////////////
178 bool EggData::
179 load_externals(const DSearchPath &searchpath) {
180  return
181  r_load_externals(searchpath, get_coordinate_system(), NULL);
182 }
183 
184 ////////////////////////////////////////////////////////////////////
185 // Function: EggData::load_externals
186 // Access: Public
187 // Description: Loads up all the egg files referenced by <File>
188 // entries within the egg structure, and inserts their
189 // contents in place of the <File> entries. Searches
190 // for files in the searchpath, if not found directly,
191 // and writes error messages to the indicated output
192 // stream. Returns true if all externals were loaded
193 // successfully, false otherwise.
194 ////////////////////////////////////////////////////////////////////
195 bool EggData::
196 load_externals(const DSearchPath &searchpath, BamCacheRecord *record) {
197  return
198  r_load_externals(searchpath, get_coordinate_system(), record);
199 }
200 
201 ////////////////////////////////////////////////////////////////////
202 // Function: EggData::collapse_equivalent_textures
203 // Access: Public
204 // Description: Removes duplicate references to the same texture
205 // image with the same properties. Considers two
206 // texture references with identical properties, but
207 // different tref names, to be equivalent, and collapses
208 // them, choosing one tref name to keep arbitrarily.
209 // Returns the number of textures removed.
210 ////////////////////////////////////////////////////////////////////
211 int EggData::
213  EggTextureCollection textures;
214  textures.find_used_textures(this);
215  return
216  textures.collapse_equivalent_textures(~EggTexture::E_tref_name, this);
217 }
218 
219 ////////////////////////////////////////////////////////////////////
220 // Function: EggData::collapse_equivalent_materials
221 // Access: Public
222 // Description: Removes duplicate references to the same material
223 // with the same properties. Considers two material
224 // references with identical properties, but different
225 // mref names, to be equivalent, and collapses them,
226 // choosing one mref name to keep arbitrarily. Returns
227 // the number of materials removed.
228 ////////////////////////////////////////////////////////////////////
229 int EggData::
231  EggMaterialCollection materials;
232  materials.find_used_materials(this);
233  return
234  materials.collapse_equivalent_materials(~EggMaterial::E_mref_name, this);
235 }
236 
237 ////////////////////////////////////////////////////////////////////
238 // Function: EggData::write_egg
239 // Access: Public
240 // Description: The main interface for writing complete egg files.
241 ////////////////////////////////////////////////////////////////////
242 bool EggData::
243 write_egg(Filename filename) {
245  filename.set_text();
246  vfs->delete_file(filename);
247  ostream *file = vfs->open_write_file(filename, true, true);
248  if (file == (ostream *)NULL) {
249  egg_cat.error() << "Unable to open " << filename << " for writing.\n";
250  return false;
251  }
252 
253  bool wrote_ok = write_egg(*file);
254  vfs->close_write_file(file);
255  return wrote_ok;
256 }
257 
258 ////////////////////////////////////////////////////////////////////
259 // Function: EggData::write_egg
260 // Access: Public
261 // Description: The main interface for writing complete egg files.
262 ////////////////////////////////////////////////////////////////////
263 bool EggData::
264 write_egg(ostream &out) {
265  pre_write();
266  write(out, 0);
267  return true;
268 }
269 
270 
271 ////////////////////////////////////////////////////////////////////
272 // Function: EggData::set_coordinate_system
273 // Access: Public
274 // Description: Changes the coordinate system of the EggData. If the
275 // coordinate system was previously different, this may
276 // result in a conversion of the data.
277 ////////////////////////////////////////////////////////////////////
278 void EggData::
279 set_coordinate_system(CoordinateSystem new_coordsys) {
280  if (new_coordsys == CS_default) {
281  new_coordsys = get_default_coordinate_system();
282  }
283  if (new_coordsys != _coordsys &&
284  (_coordsys != CS_default && _coordsys != CS_invalid)) {
285  // Time to convert the data.
286  LMatrix4d mat = LMatrix4d::convert_mat(_coordsys, new_coordsys);
287  LMatrix4d inv = LMatrix4d::convert_mat(new_coordsys, _coordsys);
288 
289  r_transform(mat, inv, new_coordsys);
290  r_transform_vertices(mat);
291 
292  // Now we have to update the under_flags to ensure that all the
293  // cached relative matrices are correct.
294  update_under(0);
295  }
296 
297  _coordsys = new_coordsys;
298 }
299 
300 ////////////////////////////////////////////////////////////////////
301 // Function: EggData::write
302 // Access: Protected, Virtual
303 // Description: Writes the egg data out to the indicated output
304 // stream.
305 ////////////////////////////////////////////////////////////////////
306 void EggData::
307 write(ostream &out, int indent_level) const {
308  PT(EggCoordinateSystem) ecs = new EggCoordinateSystem(_coordsys);
309  ecs->write(out, indent_level);
310  EggGroupNode::write(out, indent_level);
311  out << flush;
312 }
313 
314 
315 ////////////////////////////////////////////////////////////////////
316 // Function: EggData::post_read
317 // Access: Private
318 // Description: Does whatever processing is appropriate after reading
319 // the data in from an egg file.
320 ////////////////////////////////////////////////////////////////////
321 void EggData::
322 post_read() {
323  CoordinateSystem old_coordsys = _coordsys;
324  _coordsys = find_coordsys_entry();
325 
326  if (_coordsys == CS_default) {
327  // If the egg file didn't contain a <CoordinateSystem> entry,
328  // assume it's Y-up, by convention.
329  _coordsys = CS_yup_right;
330 
331  } else if (_coordsys == CS_invalid) {
332  egg_cat.warning()
333  << "Contradictory <CoordinateSystem> entries encountered.\n";
334  _coordsys = CS_yup_right;
335  }
336 
337  r_mark_coordsys(_coordsys);
338 
339  if (old_coordsys != CS_default) {
340  // Now if we had a previous definition, enforce it. This might
341  // convert the data to the given coordinate system.
342  set_coordinate_system(old_coordsys);
343  }
344 
345  // Fill this in before we automatically resolve pathnames.
346  _had_absolute_pathnames = has_absolute_pathnames();
347 
349  // Resolve filenames that are relative to the egg file.
350  DSearchPath dir;
351  dir.append_directory(get_egg_filename().get_dirname());
352  resolve_filenames(dir);
353  }
354 }
355 
356 ////////////////////////////////////////////////////////////////////
357 // Function: EggData::pre_write
358 // Access: Private
359 // Description: Does whatever processing is appropriate just before
360 // writing the data out to an egg file. This includes
361 // verifying that vertex pool names are unique, etc.
362 ////////////////////////////////////////////////////////////////////
363 void EggData::
364 pre_write() {
365  // Pull out all of the texture definitions in the file and massage
366  // them a bit.
367  EggTextureCollection textures;
368  textures.extract_textures(this);
369 
370  // Remove any textures that aren't being used.
371  textures.remove_unused_textures(this);
372 
373  // Collapse out any textures that are completely equivalent. For
374  // this purpose, we consider two textures with identical properties
375  // but different tref names to be different.
376  textures.collapse_equivalent_textures(~0, this);
377 
378  // Make sure all of the textures have unique TRef names.
379  textures.uniquify_trefs();
380  textures.sort_by_tref();
381 
382  // Do the same thing with the materials.
383  EggMaterialCollection materials;
384  materials.extract_materials(this);
385  materials.remove_unused_materials(this);
386  materials.collapse_equivalent_materials(~0, this);
387  materials.uniquify_mrefs();
388  materials.sort_by_mref();
389 
390  // Now put them all back at the head of the file, after any initial
391  // comment records.
392  iterator ci = begin();
393  while (ci != end() && (*ci)->is_of_type(EggComment::get_class_type())) {
394  ++ci;
395  }
396  textures.insert_textures(this, ci);
397  materials.insert_materials(this, ci);
398 
399  // Also make sure that the vertex pools are uniquely named. This
400  // also checks textures and materials, which is kind of redundant
401  // since we just did that, but we don't mind.
403  pu.uniquify(this);
404 }
void sort_by_mref()
Sorts all the materials into alphabetical order by MRef name.
virtual void write(ostream &out, int indent_level) const
Writes the group and all of its children to the indicated output stream in Egg format.
void sort_by_tref()
Sorts all the textures into alphabetical order by TRef name.
int collapse_equivalent_textures()
Removes duplicate references to the same texture image with the same properties.
Definition: eggData.cxx:212
static bool resolve_egg_filename(Filename &egg_filename, const DSearchPath &searchpath=DSearchPath())
Looks for the indicated filename, first along the indicated searchpath, and then along the model_path...
Definition: eggData.cxx:45
int collapse_equivalent_textures(int eq, EggGroupNode *node)
Walks through the collection and collapses together any separate textures that are equivalent accordi...
void uniquify_trefs()
Guarantees that each texture in the collection has a unique TRef name.
bool resolve_filename(Filename &filename, const DSearchPath &searchpath, const string &default_extension=string()) const
Searches the given search path for the filename.
void uniquify_mrefs()
Guarantees that each material in the collection has a unique MRef name.
This is a 4-by-4 transform matrix.
Definition: lmatrix.h:4716
The <CoordinateSystem> entry at the top of an egg file.
bool write_egg(Filename filename)
The main interface for writing complete egg files.
Definition: eggData.cxx:243
This is a collection of materials by MRef name.
A hierarchy of directories and files that appears to be one continuous file system, even though the files may originate from several different sources that may not be related to the actual OS&#39;s file system.
void remove_unused_textures(EggNode *node)
Removes any textures from the collection that aren&#39;t referenced by any primitives in the indicated eg...
void set_text()
Indicates that the filename represents a text file.
Definition: filename.I:507
CoordinateSystem get_coordinate_system() const
Returns the coordinate system in which the egg file is defined.
Definition: eggData.I:111
void set_egg_filename(const Filename &egg_filename)
Sets the filename–especially the directory part–in which the egg file is considered to reside...
Definition: eggData.I:124
const Filename & get_egg_filename() const
Returns the directory in which the egg file is considered to reside.
Definition: eggData.I:135
This is a collection of textures by TRef name.
int collapse_equivalent_materials(int eq, EggGroupNode *node)
Walks through the collection and collapses together any separate materials that are equivalent accord...
void append_directory(const Filename &directory)
Adds a new directory to the end of the search list.
This is the primary interface into all the egg data, and the root of the egg file structure...
Definition: eggData.h:41
int extract_textures(EggGroupNode *node)
Walks the egg hierarchy beginning at the indicated node, and removes any EggTextures encountered in t...
The abstract base class for a file or directory within the VirtualFileSystem.
Definition: virtualFile.h:37
EggGroupNode::iterator insert_materials(EggGroupNode *node)
Adds a series of EggMaterial nodes to the beginning of the indicated node to reflect each of the mate...
bool is_fully_qualified() const
Returns true if the filename is fully qualified, e.g.
Definition: filename.I:682
void set_coordinate_system(CoordinateSystem coordsys)
Changes the coordinate system of the EggData.
Definition: eggData.cxx:279
bool exists(const Filename &filename) const
Convenience function; returns true if the named file exists.
This is a specialization of EggNameUniquifier to generate unique names for textures, materials, and vertex pools prior to writing out an egg file.
EggGroupNode::iterator insert_textures(EggGroupNode *node)
Adds a series of EggTexture nodes to the beginning of the indicated node to reflect each of the textu...
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
An instance of this class is written to the front of a Bam or Txo file to make the file a cached inst...
Similar to MutexHolder, but for a light mutex.
bool load_externals(const DSearchPath &searchpath=DSearchPath())
Loads up all the egg files referenced by <File> entries within the egg structure, and inserts their c...
Definition: eggData.cxx:179
void steal_children(EggGroupNode &other)
Moves all the children from the other node to this one.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
bool read(Filename filename, string display_name=string())
Opens the indicated filename and reads the egg data contents from it.
Definition: eggData.cxx:71
int extract_materials(EggGroupNode *node)
Walks the egg hierarchy beginning at the indicated node, and removes any EggMaterials encountered in ...
PointerTo< VirtualFile > get_file(const Filename &filename, bool status_only=false) const
Looks up the file by the indicated name in the file system.
void uniquify(EggNode *node)
Begins the traversal from the indicated node.
int find_used_textures(EggNode *node)
Walks the egg hierarchy beginning at the indicated node, looking for textures that are referenced by ...
void remove_unused_materials(EggNode *node)
Removes any materials from the collection that aren&#39;t referenced by any primitives in the indicated e...
static const LMatrix4d & convert_mat(CoordinateSystem from, CoordinateSystem to)
Returns a matrix that transforms from the indicated coordinate system to the indicated coordinate sys...
Definition: lmatrix.cxx:1835
ostream * open_write_file(const Filename &filename, bool auto_wrap, bool truncate)
Convenience function; returns a newly allocated ostream if the file exists and can be written...
bool get_auto_resolve_externals() const
Indicates whether the EggData object will automatically resolve any external references when read() i...
Definition: eggData.I:82
void set_egg_timestamp(time_t egg_timestamp)
Sets the timestamp of the egg file on disk, at the time it was opened for reading.
Definition: eggData.I:147
This class stores a list of directories that can be searched, in order, to locate a particular file...
Definition: dSearchPath.h:32
bool delete_file(const Filename &filename)
Attempts to delete the indicated file or directory.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:85
bool has_absolute_pathnames() const
Returns true if any nodes at this level and below include a reference to a file via an absolute pathn...
static void close_write_file(ostream *stream)
Closes a file opened by a previous call to open_write_file().
int find_used_materials(EggNode *node)
Walks the egg hierarchy beginning at the indicated node, looking for materials that are referenced by...
int collapse_equivalent_materials()
Removes duplicate references to the same material with the same properties.
Definition: eggData.cxx:230
void resolve_filenames(const DSearchPath &searchpath)
Walks the tree and attempts to resolve any filenames encountered.
void merge(EggData &other)
Appends the other egg structure to the end of this one.
Definition: eggData.cxx:152