Panda3D
eggMaterialCollection.cxx
1 // Filename: eggMaterialCollection.cxx
2 // Created by: drose (30Apr01)
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 "eggMaterialCollection.h"
16 #include "eggGroupNode.h"
17 #include "eggPrimitive.h"
18 #include "eggMaterial.h"
19 
20 #include "nameUniquifier.h"
21 #include "dcast.h"
22 
23 #include <algorithm>
24 
25 ////////////////////////////////////////////////////////////////////
26 // Function: EggMaterialCollection::Constructor
27 // Access: Public
28 // Description:
29 ////////////////////////////////////////////////////////////////////
30 EggMaterialCollection::
31 EggMaterialCollection() {
32 }
33 
34 ////////////////////////////////////////////////////////////////////
35 // Function: EggMaterialCollection::Copy Constructor
36 // Access: Public
37 // Description:
38 ////////////////////////////////////////////////////////////////////
39 EggMaterialCollection::
40 EggMaterialCollection(const EggMaterialCollection &copy) :
41  _materials(copy._materials),
42  _ordered_materials(copy._ordered_materials)
43 {
44 }
45 
46 ////////////////////////////////////////////////////////////////////
47 // Function: EggMaterialCollection::Copy Assignment Operator
48 // Access: Public
49 // Description:
50 ////////////////////////////////////////////////////////////////////
51 EggMaterialCollection &EggMaterialCollection::
52 operator = (const EggMaterialCollection &copy) {
53  _materials = copy._materials;
54  _ordered_materials = copy._ordered_materials;
55  return *this;
56 }
57 
58 ////////////////////////////////////////////////////////////////////
59 // Function: EggMaterialCollection::Destructor
60 // Access: Public
61 // Description:
62 ////////////////////////////////////////////////////////////////////
63 EggMaterialCollection::
64 ~EggMaterialCollection() {
65 }
66 
67 ////////////////////////////////////////////////////////////////////
68 // Function: EggMaterialCollection::clear
69 // Access: Public
70 // Description: Removes all materials from the collection.
71 ////////////////////////////////////////////////////////////////////
73 clear() {
74  _materials.clear();
75  _ordered_materials.clear();
76 }
77 
78 ////////////////////////////////////////////////////////////////////
79 // Function: EggMaterialCollection::extract_materials
80 // Access: Public
81 // Description: Walks the egg hierarchy beginning at the indicated
82 // node, and removes any EggMaterials encountered in the
83 // hierarchy, adding them to the collection. Returns
84 // the number of EggMaterials encountered.
85 ////////////////////////////////////////////////////////////////////
88  // Since this traversal is destructive, we'll handle it within the
89  // EggGroupNode code.
90  return node->find_materials(this);
91 }
92 
93 ////////////////////////////////////////////////////////////////////
94 // Function: EggMaterialCollection::insert_materials
95 // Access: Public
96 // Description: Adds a series of EggMaterial nodes to the beginning of
97 // the indicated node to reflect each of the materials in
98 // the collection. Returns an iterator representing the
99 // first position after the newly inserted materials.
100 ////////////////////////////////////////////////////////////////////
101 EggGroupNode::iterator EggMaterialCollection::
103  return insert_materials(node, node->begin());
104 }
105 
106 ////////////////////////////////////////////////////////////////////
107 // Function: EggMaterialCollection::insert_materials
108 // Access: Public
109 // Description: Adds a series of EggMaterial nodes to the beginning of
110 // the indicated node to reflect each of the materials in
111 // the collection. Returns an iterator representing the
112 // first position after the newly inserted materials.
113 ////////////////////////////////////////////////////////////////////
114 EggGroupNode::iterator EggMaterialCollection::
115 insert_materials(EggGroupNode *node, EggGroupNode::iterator position) {
116  OrderedMaterials::iterator oti;
117  for (oti = _ordered_materials.begin();
118  oti != _ordered_materials.end();
119  ++oti) {
120  EggMaterial *material = (*oti);
121  position = node->insert(position, material);
122  }
123 
124  return position;
125 }
126 
127 ////////////////////////////////////////////////////////////////////
128 // Function: EggMaterialCollection::find_used_materials
129 // Access: Public
130 // Description: Walks the egg hierarchy beginning at the indicated
131 // node, looking for materials that are referenced by
132 // primitives but are not already members of the
133 // collection, adding them to the collection.
134 //
135 // If this is called following extract_materials(), it
136 // can be used to pick up any additional material
137 // references that appeared in the egg hierarchy (but
138 // whose EggMaterial node was not actually part of the
139 // hierarchy).
140 //
141 // If this is called in lieu of extract_materials(), it
142 // will fill up the collection with all of the
143 // referenced materials (and only the referenced
144 // materials), without destructively removing the
145 // EggMaterials from the hierarchy.
146 //
147 // This also has the side effect of incrementing the
148 // internal usage count for a material in the collection
149 // each time a material reference is encountered. This
150 // side effect is taken advantage of by
151 // remove_unused_materials().
152 ////////////////////////////////////////////////////////////////////
155  int num_found = 0;
156 
157  if (node->is_of_type(EggPrimitive::get_class_type())) {
158  EggPrimitive *primitive = DCAST(EggPrimitive, node);
159  if (primitive->has_material()) {
160  EggMaterial *tex = primitive->get_material();
161  Materials::iterator ti = _materials.find(tex);
162  if (ti == _materials.end()) {
163  // Here's a new material!
164  num_found++;
165  _materials.insert(Materials::value_type(tex, 1));
166  _ordered_materials.push_back(tex);
167  } else {
168  // Here's a material we'd already known about. Increment its
169  // usage count.
170  (*ti).second++;
171  }
172  }
173 
174  } else if (node->is_of_type(EggGroupNode::get_class_type())) {
175  EggGroupNode *group = DCAST(EggGroupNode, node);
176 
177  EggGroupNode::iterator ci;
178  for (ci = group->begin(); ci != group->end(); ++ci) {
179  EggNode *child = *ci;
180 
181  num_found += find_used_materials(child);
182  }
183  }
184 
185  return num_found;
186 }
187 
188 ////////////////////////////////////////////////////////////////////
189 // Function: EggMaterialCollection::remove_unused_materials
190 // Access: Public
191 // Description: Removes any materials from the collection that aren't
192 // referenced by any primitives in the indicated egg
193 // hierarchy. This also, incidentally, adds materials to
194 // the collection that had been referenced by primitives
195 // but had not previously appeared in the collection.
196 ////////////////////////////////////////////////////////////////////
199  // We'll do this the easy way: First, we'll remove *all* the
200  // materials from the collection, and then we'll add back only those
201  // that appear in the hierarchy.
202  clear();
203  find_used_materials(node);
204 }
205 
206 ////////////////////////////////////////////////////////////////////
207 // Function: EggMaterialCollection::collapse_equivalent_materials
208 // Access: Public
209 // Description: Walks through the collection and collapses together
210 // any separate materials that are equivalent according
211 // to the indicated equivalence factor, eq (see
212 // EggMaterial::is_equivalent_to()). The return value is
213 // the number of materials removed.
214 //
215 // This flavor of collapse_equivalent_materials()
216 // automatically adjusts all the primitives in the egg
217 // hierarchy to refer to the new material pointers.
218 ////////////////////////////////////////////////////////////////////
221  MaterialReplacement removed;
222  int num_collapsed = collapse_equivalent_materials(eq, removed);
223 
224  // And now walk the egg hierarchy and replace any references to a
225  // removed material with its replacement.
226  replace_materials(node, removed);
227 
228  return num_collapsed;
229 }
230 
231 ////////////////////////////////////////////////////////////////////
232 // Function: EggMaterialCollection::collapse_equivalent_materials
233 // Access: Public
234 // Description: Walks through the collection and collapses together
235 // any separate materials that are equivalent according
236 // to the indicated equivalence factor, eq (see
237 // EggMaterial::is_equivalent_to()). The return value is
238 // the number of materials removed.
239 //
240 // This flavor of collapse_equivalent_materials() does
241 // not adjust any primitives in the egg hierarchy;
242 // instead, it fills up the 'removed' map with an entry
243 // for each removed material, mapping it back to the
244 // equivalent retained material. It's up to the user to
245 // then call replace_materials() with this map, if
246 // desired, to apply these changes to the egg hierarchy.
247 ////////////////////////////////////////////////////////////////////
250  int num_collapsed = 0;
251 
253  UniqueEggMaterials uet(eq);
254  Collapser collapser(uet);
255 
256  // First, put all of the materials into the Collapser structure, to
257  // find out the unique materials.
258  OrderedMaterials::const_iterator oti;
259  for (oti = _ordered_materials.begin();
260  oti != _ordered_materials.end();
261  ++oti) {
262  EggMaterial *tex = (*oti);
263 
264  pair<Collapser::const_iterator, bool> result = collapser.insert(tex);
265  if (!result.second) {
266  // This material is non-unique; another one was already there.
267  EggMaterial *first = *(result.first);
268  removed.insert(MaterialReplacement::value_type(tex, first));
269  num_collapsed++;
270  }
271  }
272 
273  // Now record all of the unique materials only.
274  clear();
275  Collapser::const_iterator ci;
276  for (ci = collapser.begin(); ci != collapser.end(); ++ci) {
277  add_material(*ci);
278  }
279 
280  return num_collapsed;
281 }
282 
283 ////////////////////////////////////////////////////////////////////
284 // Function: EggMaterialCollection::replace_materials
285 // Access: Public, Static
286 // Description: Walks the egg hierarchy, changing out any reference
287 // to a material appearing on the left side of the map
288 // with its corresponding material on the right side.
289 // This is most often done following a call to
290 // collapse_equivalent_materials(). It does not directly
291 // affect the Collection.
292 ////////////////////////////////////////////////////////////////////
296  EggGroupNode::iterator ci;
297  for (ci = node->begin();
298  ci != node->end();
299  ++ci) {
300  EggNode *child = *ci;
301  if (child->is_of_type(EggPrimitive::get_class_type())) {
302  EggPrimitive *primitive = DCAST(EggPrimitive, child);
303  if (primitive->has_material()) {
304  PT(EggMaterial) tex = primitive->get_material();
305  MaterialReplacement::const_iterator ri;
306  ri = replace.find(tex);
307  if (ri != replace.end()) {
308  // Here's a material we want to replace.
309  primitive->set_material((*ri).second);
310  }
311  }
312 
313  } else if (child->is_of_type(EggGroupNode::get_class_type())) {
314  EggGroupNode *group_child = DCAST(EggGroupNode, child);
315  replace_materials(group_child, replace);
316  }
317  }
318 }
319 
320 ////////////////////////////////////////////////////////////////////
321 // Function: EggMaterialCollection::uniquify_mrefs
322 // Access: Public
323 // Description: Guarantees that each material in the collection has a
324 // unique MRef name. This is essential before writing
325 // an egg file.
326 ////////////////////////////////////////////////////////////////////
329  NameUniquifier nu(".mref", "mref");
330 
331  OrderedMaterials::const_iterator oti;
332  for (oti = _ordered_materials.begin();
333  oti != _ordered_materials.end();
334  ++oti) {
335  EggMaterial *tex = (*oti);
336 
337  tex->set_name(nu.add_name(tex->get_name()));
338  }
339 }
340 
341 ////////////////////////////////////////////////////////////////////
342 // Function: EggMaterialCollection::sort_by_mref
343 // Access: Public
344 // Description: Sorts all the materials into alphabetical order by
345 // MRef name. Subsequent operations using begin()/end()
346 // will traverse in this sorted order.
347 ////////////////////////////////////////////////////////////////////
350  sort(_ordered_materials.begin(), _ordered_materials.end(),
352 }
353 
354 ////////////////////////////////////////////////////////////////////
355 // Function: EggMaterialCollection::add_material
356 // Access: Public
357 // Description: Explicitly adds a new material to the collection.
358 // Returns true if the material was added, false if it
359 // was already there or if there was some error.
360 ////////////////////////////////////////////////////////////////////
363  nassertr(_materials.size() == _ordered_materials.size(), false);
364 
365  PT(EggMaterial) new_tex = material;
366 
367  Materials::const_iterator ti;
368  ti = _materials.find(new_tex);
369  if (ti != _materials.end()) {
370  // This material is already a member of the collection.
371  return false;
372  }
373 
374  _materials.insert(Materials::value_type(new_tex, 0));
375  _ordered_materials.push_back(new_tex);
376 
377  nassertr(_materials.size() == _ordered_materials.size(), false);
378  return true;
379 }
380 
381 ////////////////////////////////////////////////////////////////////
382 // Function: EggMaterialCollection::remove_material
383 // Access: Public
384 // Description: Explicitly removes a material from the collection.
385 // Returns true if the material was removed, false if it
386 // wasn't there or if there was some error.
387 ////////////////////////////////////////////////////////////////////
390  nassertr(_materials.size() == _ordered_materials.size(), false);
391 
392  Materials::iterator ti;
393  ti = _materials.find(material);
394  if (ti == _materials.end()) {
395  // This material is not a member of the collection.
396  return false;
397  }
398 
399  _materials.erase(ti);
400 
401  OrderedMaterials::iterator oti;
402  PT(EggMaterial) ptex = material;
403  oti = find(_ordered_materials.begin(), _ordered_materials.end(), ptex);
404  nassertr(oti != _ordered_materials.end(), false);
405 
406  _ordered_materials.erase(oti);
407 
408  nassertr(_materials.size() == _ordered_materials.size(), false);
409  return true;
410 }
411 
412 ////////////////////////////////////////////////////////////////////
413 // Function: EggMaterialCollection::create_unique_material
414 // Access: Public
415 // Description: Creates a new material if there is not already one
416 // equivalent (according to eq, see
417 // EggMaterial::is_equivalent_to()) to the indicated
418 // material, or returns the existing one if there is.
419 ////////////////////////////////////////////////////////////////////
421 create_unique_material(const EggMaterial &copy, int eq) {
422  // This requires a complete linear traversal, not terribly
423  // efficient.
424  OrderedMaterials::const_iterator oti;
425  for (oti = _ordered_materials.begin();
426  oti != _ordered_materials.end();
427  ++oti) {
428  EggMaterial *tex = (*oti);
429  if (copy.is_equivalent_to(*tex, eq)) {
430  return tex;
431  }
432  }
433 
434  EggMaterial *new_material = new EggMaterial(copy);
435  add_material(new_material);
436  return new_material;
437 }
438 
439 ////////////////////////////////////////////////////////////////////
440 // Function: EggMaterialCollection::find_mref
441 // Access: Public
442 // Description: Returns the material with the indicated MRef name, or
443 // NULL if no material matches.
444 ////////////////////////////////////////////////////////////////////
446 find_mref(const string &mref_name) const {
447  // This requires a complete linear traversal, not terribly
448  // efficient.
449  OrderedMaterials::const_iterator oti;
450  for (oti = _ordered_materials.begin();
451  oti != _ordered_materials.end();
452  ++oti) {
453  EggMaterial *tex = (*oti);
454  if (tex->get_name() == mref_name) {
455  return tex;
456  }
457  }
458 
459  return (EggMaterial *)NULL;
460 }
A base class for any of a number of kinds of geometry primitives: polygons, point lights...
Definition: eggPrimitive.h:51
void sort_by_mref()
Sorts all the materials into alphabetical order by MRef name.
This is our own Panda specialization on the default STL map.
Definition: pmap.h:52
void uniquify_mrefs()
Guarantees that each material in the collection has a unique MRef name.
bool is_equivalent_to(const EggMaterial &other, int eq) const
Returns true if the two materials are equivalent in all relevant properties (according to eq)...
A base class for nodes in the hierarchy that are not leaf nodes.
Definition: eggGroupNode.h:51
This is a collection of materials by MRef name.
bool add_material(EggMaterial *material)
Explicitly adds a new material to the collection.
void clear()
Removes all materials from the collection.
int collapse_equivalent_materials(int eq, EggGroupNode *node)
Walks through the collection and collapses together any separate materials that are equivalent accord...
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...
static void replace_materials(EggGroupNode *node, const MaterialReplacement &replace)
Walks the egg hierarchy, changing out any reference to a material appearing on the left side of the m...
A handy class for converting a list of arbitrary names (strings) so that each name is guaranteed to b...
bool remove_material(EggMaterial *material)
Explicitly removes a material from the collection.
void set_material(EggMaterial *material)
Applies the indicated material to the primitive.
Definition: eggPrimitive.I:241
int extract_materials(EggGroupNode *node)
Walks the egg hierarchy beginning at the indicated node, and removes any EggMaterials encountered in ...
An STL function object for sorting materials into order by properties.
Definition: eggMaterial.h:117
EggMaterial * get_material() const
Returns a pointer to the applied material, or NULL if there is no material applied.
Definition: eggPrimitive.I:262
void remove_unused_materials(EggNode *node)
Removes any materials from the collection that aren&#39;t referenced by any primitives in the indicated e...
string add_name(const string &name)
If name is nonempty and so far unique, returns it unchanged.
A base class for things that may be directly added into the egg hierarchy.
Definition: eggNode.h:38
EggMaterial * create_unique_material(const EggMaterial &copy, int eq)
Creates a new material if there is not already one equivalent (according to eq, see EggMaterial::is_e...
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 is our own Panda specialization on the default STL set.
Definition: pset.h:52
int find_used_materials(EggNode *node)
Walks the egg hierarchy beginning at the indicated node, looking for materials that are referenced by...
bool has_material() const
Returns true if the primitive is materiald (and get_material() will return a real pointer)...
Definition: eggPrimitive.I:275
An STL function object for sorting an array of pointers to Namables into order by name...
Definition: namable.h:68
EggMaterial * find_mref(const string &mref_name) const
Returns the material with the indicated MRef name, or NULL if no material matches.