Panda3D
eggReader.cxx
1 // Filename: eggReader.cxx
2 // Created by: drose (14Feb00)
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 "eggReader.h"
16 
17 #include "pnmImage.h"
18 #include "config_util.h"
19 #include "eggTextureCollection.h"
20 #include "eggGroup.h"
21 #include "eggGroupNode.h"
22 #include "eggSwitchCondition.h"
23 #include "string_utils.h"
24 #include "dcast.h"
25 
26 ////////////////////////////////////////////////////////////////////
27 // Function: EggReader::Constructor
28 // Access: Public
29 // Description:
30 ////////////////////////////////////////////////////////////////////
31 EggReader::
32 EggReader() {
33  clear_runlines();
34  add_runline("[opts] input.egg");
35 
36  redescribe_option
37  ("cs",
38  "Specify the coordinate system to operate in. This may be "
39  " one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default "
40  "is the coordinate system of the input egg file.");
41 
42  add_option
43  ("f", "", 80,
44  "Force complete loading: load up the egg file along with all of its "
45  "external references.",
46  &EggReader::dispatch_none, &_force_complete);
47 
48  add_option
49  ("noabs", "", 0,
50  "Don't allow the input egg file to have absolute pathnames. "
51  "If it does, abort with an error. This option is designed to help "
52  "detect errors when populating or building a standalone model tree, "
53  "which should be self-contained and include only relative pathnames.",
54  &EggReader::dispatch_none, &_noabs);
55 
56  _tex_type = (PNMFileType *)NULL;
57  _delod = -1.0;
58 
59  _got_tex_dirname = false;
60  _got_tex_extension = false;
61 }
62 
63 ////////////////////////////////////////////////////////////////////
64 // Function: EggRead::add_texture_options
65 // Access: Public
66 // Description: Adds -td, -te, etc. as valid options for this
67 // program. If the user specifies one of the options on
68 // the command line, the textures will be copied and
69 // converted as each egg file is read.
70 //
71 // Note that if you call this function to add these
72 // options, you must call do_reader_options() at the
73 // appropriate point before or during processing to
74 // execute the options if the user specified them.
75 ////////////////////////////////////////////////////////////////////
76 void EggReader::
78  add_option
79  ("td", "dirname", 40,
80  "Copy textures to the indicated directory. The copy is performed "
81  "only if the destination file does not exist or is older than the "
82  "source file.",
83  &EggReader::dispatch_filename, &_got_tex_dirname, &_tex_dirname);
84 
85  add_option
86  ("te", "ext", 40,
87  "Rename textures to have the indicated extension. This also "
88  "automatically copies them to the new filename (possibly in a "
89  "different directory if -td is also specified), and may implicitly "
90  "convert to a different image format according to the extension.",
91  &EggReader::dispatch_string, &_got_tex_extension, &_tex_extension);
92 
93  add_option
94  ("tt", "type", 40,
95  "Explicitly specifies the image format to convert textures to "
96  "when copying them via -td or -te. Normally, this is unnecessary as "
97  "the image format can be determined by the extension, but sometimes "
98  "the extension is insufficient to unambiguously specify an image "
99  "type.",
100  &EggReader::dispatch_image_type, NULL, &_tex_type);
101 }
102 
103 ////////////////////////////////////////////////////////////////////
104 // Function: EggRead::add_delod_options
105 // Access: Public
106 // Description: Adds -delod as a valid option for this program.
107 //
108 // Note that if you call this function to add these
109 // options, you must call do_reader_options() at the
110 // appropriate point before or during processing to
111 // execute the options if the user specified them.
112 ////////////////////////////////////////////////////////////////////
113 void EggReader::
114 add_delod_options(double default_delod) {
115  _delod = default_delod;
116 
117  if (default_delod < 0) {
118  add_option
119  ("delod", "dist", 40,
120  "Eliminate LOD's by choosing the level that would be appropriate for "
121  "a camera at the indicated fixed distance from each LOD. "
122  "Use -delod -1 to keep all the LOD's as they are, which is "
123  "the default.\n",
124  &EggReader::dispatch_double, NULL, &_delod);
125 
126  } else {
127  add_option
128  ("delod", "dist", 40,
129  "Eliminate LOD's by choosing the level that would be appropriate for "
130  "a camera at the indicated fixed distance from each LOD. "
131  "Use -delod -1 to keep all the LOD's as they are. The default value "
132  "is " + format_string(default_delod) + ".",
133  &EggReader::dispatch_double, NULL, &_delod);
134  }
135 }
136 
137 ////////////////////////////////////////////////////////////////////
138 // Function: EggReader::as_reader
139 // Access: Public, Virtual
140 // Description: Returns this object as an EggReader pointer, if it is
141 // in fact an EggReader, or NULL if it is not.
142 //
143 // This is intended to work around the C++ limitation
144 // that prevents downcasts past virtual inheritance.
145 // Since both EggReader and EggWriter inherit virtually
146 // from EggSingleBase, we need functions like this to downcast
147 // to the appropriate pointer.
148 ////////////////////////////////////////////////////////////////////
151  return this;
152 }
153 
154 ////////////////////////////////////////////////////////////////////
155 // Function: EggReader::pre_process_egg_file
156 // Access: Public, Virtual
157 // Description: Performs any processing of the egg file that is
158 // appropriate after reading it in.
159 //
160 // Normally, you should not need to call this function
161 // directly; it is called automatically at startup.
162 ////////////////////////////////////////////////////////////////////
163 void EggReader::
165 }
166 
167 ////////////////////////////////////////////////////////////////////
168 // Function: EggReader::handle_args
169 // Access: Protected, Virtual
170 // Description:
171 ////////////////////////////////////////////////////////////////////
172 bool EggReader::
173 handle_args(ProgramBase::Args &args) {
174  if (args.empty()) {
175  nout << "You must specify the egg file(s) to read on the command line.\n";
176  return false;
177  }
178 
179  // Any separate egg files that are listed on the command line will
180  // get implicitly loaded up into one big egg file.
181 
182  if (!args.empty()) {
183  _data->set_egg_filename(Filename::from_os_specific(args[0]));
184  }
185  Args::const_iterator ai;
186  for (ai = args.begin(); ai != args.end(); ++ai) {
187  Filename filename = Filename::from_os_specific(*ai);
188 
189  EggData file_data;
190  if (!file_data.read(filename)) {
191  // Rather than returning false, we simply exit here, so the
192  // ProgramBase won't try to tell the user how to run the program
193  // just because we got a bad egg file.
194  exit(1);
195  }
196 
197  if (_noabs && file_data.original_had_absolute_pathnames()) {
198  nout << filename.get_basename()
199  << " includes absolute pathnames!\n";
200  exit(1);
201  }
202 
203  DSearchPath file_path;
204  file_path.append_directory(filename.get_dirname());
205 
206  if (_force_complete) {
207  if (!file_data.load_externals()) {
208  exit(1);
209  }
210  }
211 
212  // Now resolve the filenames again according to the user's
213  // specified _path_replace.
214  convert_paths(&file_data, _path_replace, file_path);
215 
216  _data->merge(file_data);
217  }
218 
220 
221  return true;
222 }
223 
224 ////////////////////////////////////////////////////////////////////
225 // Function: EggReader::post_command_line
226 // Access: Protected, Virtual
227 // Description: This is called after the command line has been
228 // completely processed, and it gives the program a
229 // chance to do some last-minute processing and
230 // validation of the options and arguments. It should
231 // return true if everything is fine, false if there is
232 // an error.
233 ////////////////////////////////////////////////////////////////////
234 bool EggReader::
235 post_command_line() {
236  return EggSingleBase::post_command_line();
237 }
238 
239 ////////////////////////////////////////////////////////////////////
240 // Function: EggReader::do_reader_options
241 // Access: Protected
242 // Description: Postprocesses the egg file as the user requested
243 // according to whatever command-line options are in
244 // effect. Returns true if everything is done
245 // correctly, false if there was some problem.
246 ////////////////////////////////////////////////////////////////////
247 bool EggReader::
248 do_reader_options() {
249  bool okflag = true;
250 
251  if (_got_tex_dirname || _got_tex_extension) {
252  if (!copy_textures()) {
253  okflag = false;
254  }
255  }
256 
257  if (_delod >= 0.0) {
258  do_delod(_data);
259  }
260 
261  return okflag;
262 }
263 
264 ////////////////////////////////////////////////////////////////////
265 // Function: EggReader::copy_textures
266 // Access: Private
267 // Description: Renames and copies the textures referenced in the egg
268 // file, if so specified by the -td and -te options.
269 // Returns true if all textures are copied successfully,
270 // false if any one of them failed.
271 ////////////////////////////////////////////////////////////////////
272 bool EggReader::
273 copy_textures() {
274  bool success = true;
275  EggTextureCollection textures;
276  textures.find_used_textures(_data);
277 
278  EggTextureCollection::const_iterator ti;
279  for (ti = textures.begin(); ti != textures.end(); ++ti) {
280  EggTexture *tex = (*ti);
281  Filename orig_filename = tex->get_filename();
282  if (!orig_filename.exists()) {
283  bool found = orig_filename.resolve_filename(get_model_path());
284  if (!found) {
285  nout << "Cannot find " << orig_filename << "\n";
286  success = false;
287  continue;
288  }
289  }
290 
291  Filename new_filename = orig_filename;
292  if (_got_tex_dirname) {
293  new_filename.set_dirname(_tex_dirname);
294  }
295  if (_got_tex_extension) {
296  new_filename.set_extension(_tex_extension);
297  }
298 
299  if (orig_filename != new_filename) {
300  tex->set_filename(new_filename);
301 
302  // The new filename is different; does it need copying?
303  int compare =
304  orig_filename.compare_timestamps(new_filename, true, true);
305  if (compare > 0) {
306  // Yes, it does. Copy it!
307  nout << "Reading " << orig_filename << "\n";
308  PNMImage image;
309  if (!image.read(orig_filename)) {
310  nout << " unable to read!\n";
311  success = false;
312  } else {
313  nout << "Writing " << new_filename << "\n";
314  if (!image.write(new_filename, _tex_type)) {
315  nout << " unable to write!\n";
316  success = false;
317  }
318  }
319  }
320  }
321  }
322 
323  return success;
324 }
325 
326 ////////////////////////////////////////////////////////////////////
327 // Function: EggReader::do_delod
328 // Access: Private
329 // Description: Removes all the LOD's in the egg file by treating the
330 // camera as being _delod distance from each LOD.
331 // Returns true if this particular group should be
332 // preserved, false if it should be removed.
333 ////////////////////////////////////////////////////////////////////
334 bool EggReader::
335 do_delod(EggNode *node) {
336  if (node->is_of_type(EggGroup::get_class_type())) {
337  EggGroup *group = DCAST(EggGroup, node);
338  if (group->has_lod()) {
339  const EggSwitchCondition &cond = group->get_lod();
340  if (cond.is_of_type(EggSwitchConditionDistance::get_class_type())) {
341  const EggSwitchConditionDistance *dist =
342  DCAST(EggSwitchConditionDistance, &cond);
343  if (_delod >= dist->_switch_out && _delod < dist->_switch_in) {
344  // Preserve this group node, but not the LOD information
345  // itself.
346  nout << "Preserving LOD " << node->get_name()
347  << " (" << dist->_switch_out << " to " << dist->_switch_in
348  << ")\n";
349  group->clear_lod();
350  } else {
351  // Remove this group node.
352  nout << "Eliminating LOD " << node->get_name()
353  << " (" << dist->_switch_out << " to " << dist->_switch_in
354  << ")\n";
355  return false;
356  }
357  }
358  }
359  }
360 
361  // Now process all the children.
362  if (node->is_of_type(EggGroupNode::get_class_type())) {
363  EggGroupNode *group = DCAST(EggGroupNode, node);
364  EggGroupNode::iterator ci;
365  ci = group->begin();
366  while (ci != group->end()) {
367  EggNode *child = *ci;
368  ++ci;
369 
370  if (!do_delod(child)) {
371  group->remove_child(child);
372  }
373  }
374  }
375 
376  return true;
377 }
string get_dirname() const
Returns the directory part of the filename.
Definition: filename.I:424
string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:436
void set_extension(const string &s)
Replaces the file extension.
Definition: filename.cxx:837
const Filename & get_filename() const
Returns a nonmodifiable reference to the filename.
void add_delod_options(double default_delod=-1.0)
Adds -delod as a valid option for this program.
Definition: eggReader.cxx:114
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:68
void add_texture_options()
Adds -td, -te, etc.
Definition: eggReader.cxx:77
A base class for nodes in the hierarchy that are not leaf nodes.
Definition: eggGroupNode.h:51
Defines a texture map that may be applied to geometry.
Definition: eggTexture.h:33
bool read(const Filename &filename, PNMFileType *type=NULL, bool report_unknown_type=true)
Reads the indicated image filename.
Definition: pnmImage.cxx:245
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition: pnmFileType.h:35
virtual EggReader * as_reader()
Returns this object as an EggReader pointer, if it is in fact an EggReader, or NULL if it is not...
Definition: eggReader.cxx:150
This is a collection of textures by TRef name.
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
static void convert_paths(EggNode *node, PathReplace *path_replace, const DSearchPath &additional_path)
Recursively walks the egg hierarchy.
Definition: eggBase.cxx:170
bool resolve_filename(const DSearchPath &searchpath, const string &default_extension=string())
Searches the given search path for the filename.
Definition: filename.cxx:1699
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition: eggGroup.h:36
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
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
bool read(Filename filename, string display_name=string())
Opens the indicated filename and reads the egg data contents from it.
Definition: eggData.cxx:71
void set_dirname(const string &s)
Replaces the directory part of the filename.
Definition: filename.cxx:726
virtual void pre_process_egg_file()
Performs any processing of the egg file that is appropriate after reading it in.
Definition: eggReader.cxx:164
bool write(const Filename &filename, PNMFileType *type=NULL) const
Writes the image to the indicated filename.
Definition: pnmImage.cxx:362
int find_used_textures(EggNode *node)
Walks the egg hierarchy beginning at the indicated node, looking for textures that are referenced by ...
This is the base class for a program that reads egg files, but doesn&#39;t write an egg file...
Definition: eggReader.h:30
bool original_had_absolute_pathnames() const
Returns true if the data processed in the last call to read() contained absolute pathnames, or false if those pathnames were all relative.
Definition: eggData.I:100
int compare_timestamps(const Filename &other, bool this_missing_is_old=true, bool other_missing_is_old=true) const
Returns a number less than zero if the file named by this object is older than the given file...
Definition: filename.cxx:1521
A base class for things that may be directly added into the egg hierarchy.
Definition: eggNode.h:38
This class stores a list of directories that can be searched, in order, to locate a particular file...
Definition: dSearchPath.h:32
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:63
This corresponds to a <SwitchCondition> entry within a group.
A SwitchCondition that switches the levels-of-detail based on distance from the camera&#39;s eyepoint...
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1356
static Filename from_os_specific(const string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes, and no drive letter) based on the supplied filename string that describes a filename in the local system conventions (for instance, on Windows, it may use backslashes or begin with a drive letter and a colon).
Definition: filename.cxx:332