Panda3D
 All Classes Functions Variables Enumerations
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.
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 is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:63
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.
EggMaterial * get_material() const
Returns a pointer to the applied material, or NULL if there is no material applied.
Definition: eggPrimitive.I:262
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
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.
bool has_material() const
Returns true if the primitive is materiald (and get_material() will return a real pointer)...
Definition: eggPrimitive.I:275
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...
This is our own Panda specialization on the default STL set.
Definition: pset.h:52
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)...
EggMaterial * find_mref(const string &mref_name) const
Returns the material with the indicated MRef name, or NULL if no material matches.
int find_used_materials(EggNode *node)
Walks the egg hierarchy beginning at the indicated node, looking for materials that are referenced by...
An STL function object for sorting an array of pointers to Namables into order by name...
Definition: namable.h:68