// Filename: eggMaterialCollection.cxx // Created by: drose (30Apr01) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) Carnegie Mellon University. All rights reserved. // // All use of this software is subject to the terms of the revised BSD // license. You should have received a copy of this license along // with this source code in a file named "LICENSE." // //////////////////////////////////////////////////////////////////// #include "eggMaterialCollection.h" #include "eggGroupNode.h" #include "eggPrimitive.h" #include "eggMaterial.h" #include "nameUniquifier.h" #include "dcast.h" #include //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// EggMaterialCollection:: EggMaterialCollection() { } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::Copy Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// EggMaterialCollection:: EggMaterialCollection(const EggMaterialCollection ©) : _materials(copy._materials), _ordered_materials(copy._ordered_materials) { } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::Copy Assignment Operator // Access: Public // Description: //////////////////////////////////////////////////////////////////// EggMaterialCollection &EggMaterialCollection:: operator = (const EggMaterialCollection ©) { _materials = copy._materials; _ordered_materials = copy._ordered_materials; return *this; } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::Destructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// EggMaterialCollection:: ~EggMaterialCollection() { } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::clear // Access: Public // Description: Removes all materials from the collection. //////////////////////////////////////////////////////////////////// void EggMaterialCollection:: clear() { _materials.clear(); _ordered_materials.clear(); } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::extract_materials // Access: Public // Description: Walks the egg hierarchy beginning at the indicated // node, and removes any EggMaterials encountered in the // hierarchy, adding them to the collection. Returns // the number of EggMaterials encountered. //////////////////////////////////////////////////////////////////// int EggMaterialCollection:: extract_materials(EggGroupNode *node) { // Since this traversal is destructive, we'll handle it within the // EggGroupNode code. return node->find_materials(this); } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::insert_materials // Access: Public // Description: Adds a series of EggMaterial nodes to the beginning of // the indicated node to reflect each of the materials in // the collection. Returns an iterator representing the // first position after the newly inserted materials. //////////////////////////////////////////////////////////////////// EggGroupNode::iterator EggMaterialCollection:: insert_materials(EggGroupNode *node) { return insert_materials(node, node->begin()); } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::insert_materials // Access: Public // Description: Adds a series of EggMaterial nodes to the beginning of // the indicated node to reflect each of the materials in // the collection. Returns an iterator representing the // first position after the newly inserted materials. //////////////////////////////////////////////////////////////////// EggGroupNode::iterator EggMaterialCollection:: insert_materials(EggGroupNode *node, EggGroupNode::iterator position) { OrderedMaterials::iterator oti; for (oti = _ordered_materials.begin(); oti != _ordered_materials.end(); ++oti) { EggMaterial *material = (*oti); position = node->insert(position, material); } return position; } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::find_used_materials // Access: Public // Description: Walks the egg hierarchy beginning at the indicated // node, looking for materials that are referenced by // primitives but are not already members of the // collection, adding them to the collection. // // If this is called following extract_materials(), it // can be used to pick up any additional material // references that appeared in the egg hierarchy (but // whose EggMaterial node was not actually part of the // hierarchy). // // If this is called in lieu of extract_materials(), it // will fill up the collection with all of the // referenced materials (and only the referenced // materials), without destructively removing the // EggMaterials from the hierarchy. // // This also has the side effect of incrementing the // internal usage count for a material in the collection // each time a material reference is encountered. This // side effect is taken advantage of by // remove_unused_materials(). //////////////////////////////////////////////////////////////////// int EggMaterialCollection:: find_used_materials(EggNode *node) { int num_found = 0; if (node->is_of_type(EggPrimitive::get_class_type())) { EggPrimitive *primitive = DCAST(EggPrimitive, node); if (primitive->has_material()) { EggMaterial *tex = primitive->get_material(); Materials::iterator ti = _materials.find(tex); if (ti == _materials.end()) { // Here's a new material! num_found++; _materials.insert(Materials::value_type(tex, 1)); _ordered_materials.push_back(tex); } else { // Here's a material we'd already known about. Increment its // usage count. (*ti).second++; } } } else if (node->is_of_type(EggGroupNode::get_class_type())) { EggGroupNode *group = DCAST(EggGroupNode, node); EggGroupNode::iterator ci; for (ci = group->begin(); ci != group->end(); ++ci) { EggNode *child = *ci; num_found += find_used_materials(child); } } return num_found; } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::remove_unused_materials // Access: Public // Description: Removes any materials from the collection that aren't // referenced by any primitives in the indicated egg // hierarchy. This also, incidentally, adds materials to // the collection that had been referenced by primitives // but had not previously appeared in the collection. //////////////////////////////////////////////////////////////////// void EggMaterialCollection:: remove_unused_materials(EggNode *node) { // We'll do this the easy way: First, we'll remove *all* the // materials from the collection, and then we'll add back only those // that appear in the hierarchy. clear(); find_used_materials(node); } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::collapse_equivalent_materials // Access: Public // Description: Walks through the collection and collapses together // any separate materials that are equivalent according // to the indicated equivalence factor, eq (see // EggMaterial::is_equivalent_to()). The return value is // the number of materials removed. // // This flavor of collapse_equivalent_materials() // automatically adjusts all the primitives in the egg // hierarchy to refer to the new material pointers. //////////////////////////////////////////////////////////////////// int EggMaterialCollection:: collapse_equivalent_materials(int eq, EggGroupNode *node) { MaterialReplacement removed; int num_collapsed = collapse_equivalent_materials(eq, removed); // And now walk the egg hierarchy and replace any references to a // removed material with its replacement. replace_materials(node, removed); return num_collapsed; } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::collapse_equivalent_materials // Access: Public // Description: Walks through the collection and collapses together // any separate materials that are equivalent according // to the indicated equivalence factor, eq (see // EggMaterial::is_equivalent_to()). The return value is // the number of materials removed. // // This flavor of collapse_equivalent_materials() does // not adjust any primitives in the egg hierarchy; // instead, it fills up the 'removed' map with an entry // for each removed material, mapping it back to the // equivalent retained material. It's up to the user to // then call replace_materials() with this map, if // desired, to apply these changes to the egg hierarchy. //////////////////////////////////////////////////////////////////// int EggMaterialCollection:: collapse_equivalent_materials(int eq, EggMaterialCollection::MaterialReplacement &removed) { int num_collapsed = 0; typedef pset Collapser; UniqueEggMaterials uet(eq); Collapser collapser(uet); // First, put all of the materials into the Collapser structure, to // find out the unique materials. OrderedMaterials::const_iterator oti; for (oti = _ordered_materials.begin(); oti != _ordered_materials.end(); ++oti) { EggMaterial *tex = (*oti); pair result = collapser.insert(tex); if (!result.second) { // This material is non-unique; another one was already there. EggMaterial *first = *(result.first); removed.insert(MaterialReplacement::value_type(tex, first)); num_collapsed++; } } // Now record all of the unique materials only. clear(); Collapser::const_iterator ci; for (ci = collapser.begin(); ci != collapser.end(); ++ci) { add_material(*ci); } return num_collapsed; } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::replace_materials // Access: Public, Static // Description: Walks the egg hierarchy, changing out any reference // to a material appearing on the left side of the map // with its corresponding material on the right side. // This is most often done following a call to // collapse_equivalent_materials(). It does not directly // affect the Collection. //////////////////////////////////////////////////////////////////// void EggMaterialCollection:: replace_materials(EggGroupNode *node, const EggMaterialCollection::MaterialReplacement &replace) { EggGroupNode::iterator ci; for (ci = node->begin(); ci != node->end(); ++ci) { EggNode *child = *ci; if (child->is_of_type(EggPrimitive::get_class_type())) { EggPrimitive *primitive = DCAST(EggPrimitive, child); if (primitive->has_material()) { PT(EggMaterial) tex = primitive->get_material(); MaterialReplacement::const_iterator ri; ri = replace.find(tex); if (ri != replace.end()) { // Here's a material we want to replace. primitive->set_material((*ri).second); } } } else if (child->is_of_type(EggGroupNode::get_class_type())) { EggGroupNode *group_child = DCAST(EggGroupNode, child); replace_materials(group_child, replace); } } } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::uniquify_mrefs // Access: Public // Description: Guarantees that each material in the collection has a // unique MRef name. This is essential before writing // an egg file. //////////////////////////////////////////////////////////////////// void EggMaterialCollection:: uniquify_mrefs() { NameUniquifier nu(".mref", "mref"); OrderedMaterials::const_iterator oti; for (oti = _ordered_materials.begin(); oti != _ordered_materials.end(); ++oti) { EggMaterial *tex = (*oti); tex->set_name(nu.add_name(tex->get_name())); } } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::sort_by_mref // Access: Public // Description: Sorts all the materials into alphabetical order by // MRef name. Subsequent operations using begin()/end() // will traverse in this sorted order. //////////////////////////////////////////////////////////////////// void EggMaterialCollection:: sort_by_mref() { sort(_ordered_materials.begin(), _ordered_materials.end(), NamableOrderByName()); } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::add_material // Access: Public // Description: Explicitly adds a new material to the collection. // Returns true if the material was added, false if it // was already there or if there was some error. //////////////////////////////////////////////////////////////////// bool EggMaterialCollection:: add_material(EggMaterial *material) { nassertr(_materials.size() == _ordered_materials.size(), false); PT(EggMaterial) new_tex = material; Materials::const_iterator ti; ti = _materials.find(new_tex); if (ti != _materials.end()) { // This material is already a member of the collection. return false; } _materials.insert(Materials::value_type(new_tex, 0)); _ordered_materials.push_back(new_tex); nassertr(_materials.size() == _ordered_materials.size(), false); return true; } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::remove_material // Access: Public // Description: Explicitly removes a material from the collection. // Returns true if the material was removed, false if it // wasn't there or if there was some error. //////////////////////////////////////////////////////////////////// bool EggMaterialCollection:: remove_material(EggMaterial *material) { nassertr(_materials.size() == _ordered_materials.size(), false); Materials::iterator ti; ti = _materials.find(material); if (ti == _materials.end()) { // This material is not a member of the collection. return false; } _materials.erase(ti); OrderedMaterials::iterator oti; PT(EggMaterial) ptex = material; oti = find(_ordered_materials.begin(), _ordered_materials.end(), ptex); nassertr(oti != _ordered_materials.end(), false); _ordered_materials.erase(oti); nassertr(_materials.size() == _ordered_materials.size(), false); return true; } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::create_unique_material // Access: Public // Description: Creates a new material if there is not already one // equivalent (according to eq, see // EggMaterial::is_equivalent_to()) to the indicated // material, or returns the existing one if there is. //////////////////////////////////////////////////////////////////// EggMaterial *EggMaterialCollection:: create_unique_material(const EggMaterial ©, int eq) { // This requires a complete linear traversal, not terribly // efficient. OrderedMaterials::const_iterator oti; for (oti = _ordered_materials.begin(); oti != _ordered_materials.end(); ++oti) { EggMaterial *tex = (*oti); if (copy.is_equivalent_to(*tex, eq)) { return tex; } } EggMaterial *new_material = new EggMaterial(copy); add_material(new_material); return new_material; } //////////////////////////////////////////////////////////////////// // Function: EggMaterialCollection::find_mref // Access: Public // Description: Returns the material with the indicated MRef name, or // NULL if no material matches. //////////////////////////////////////////////////////////////////// EggMaterial *EggMaterialCollection:: find_mref(const string &mref_name) const { // This requires a complete linear traversal, not terribly // efficient. OrderedMaterials::const_iterator oti; for (oti = _ordered_materials.begin(); oti != _ordered_materials.end(); ++oti) { EggMaterial *tex = (*oti); if (tex->get_name() == mref_name) { return tex; } } return (EggMaterial *)NULL; }