1/*
2---------------------------------------------------------------------------
3Open Asset Import Library (assimp)
4---------------------------------------------------------------------------
5
6Copyright (c) 2006-2017, assimp team
7
8
9All rights reserved.
10
11Redistribution and use of this software in source and binary forms,
12with or without modification, are permitted provided that the following
13conditions are met:
14
15* Redistributions of source code must retain the above
16 copyright notice, this list of conditions and the
17 following disclaimer.
18
19* Redistributions in binary form must reproduce the above
20 copyright notice, this list of conditions and the
21 following disclaimer in the documentation and/or other
22 materials provided with the distribution.
23
24* Neither the name of the assimp team, nor the names of its
25 contributors may be used to endorse or promote products
26 derived from this software without specific prior
27 written permission of the assimp team.
28
29THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40---------------------------------------------------------------------------
41*/
42/** @file RemoveRedundantMaterials.cpp
43 * @brief Implementation of the "RemoveRedundantMaterials" post processing step
44*/
45
46// internal headers
47
48#include "RemoveRedundantMaterials.h"
49#include "ParsingUtils.h"
50#include "ProcessHelper.h"
51#include "MaterialSystem.h"
52#include <stdio.h>
53
54using namespace Assimp;
55
56// ------------------------------------------------------------------------------------------------
57// Constructor to be privately used by Importer
58RemoveRedundantMatsProcess::RemoveRedundantMatsProcess()
59: configFixedMaterials() {
60 // nothing to do here
61}
62
63// ------------------------------------------------------------------------------------------------
64// Destructor, private as well
65RemoveRedundantMatsProcess::~RemoveRedundantMatsProcess()
66{
67 // nothing to do here
68}
69
70// ------------------------------------------------------------------------------------------------
71// Returns whether the processing step is present in the given flag field.
72bool RemoveRedundantMatsProcess::IsActive( unsigned int pFlags) const
73{
74 return (pFlags & aiProcess_RemoveRedundantMaterials) != 0;
75}
76
77// ------------------------------------------------------------------------------------------------
78// Setup import properties
79void RemoveRedundantMatsProcess::SetupProperties(const Importer* pImp)
80{
81 // Get value of AI_CONFIG_PP_RRM_EXCLUDE_LIST
82 configFixedMaterials = pImp->GetPropertyString(AI_CONFIG_PP_RRM_EXCLUDE_LIST,"");
83}
84
85// ------------------------------------------------------------------------------------------------
86// Executes the post processing step on the given imported data.
87void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
88{
89 DefaultLogger::get()->debug("RemoveRedundantMatsProcess begin");
90
91 unsigned int redundantRemoved = 0, unreferencedRemoved = 0;
92 if (pScene->mNumMaterials)
93 {
94 // Find out which materials are referenced by meshes
95 std::vector<bool> abReferenced(pScene->mNumMaterials,false);
96 for (unsigned int i = 0;i < pScene->mNumMeshes;++i)
97 abReferenced[pScene->mMeshes[i]->mMaterialIndex] = true;
98
99 // If a list of materials to be excluded was given, match the list with
100 // our imported materials and 'salt' all positive matches to ensure that
101 // we get unique hashes later.
102 if (configFixedMaterials.length()) {
103
104 std::list<std::string> strings;
105 ConvertListToStrings(configFixedMaterials,strings);
106
107 for (unsigned int i = 0; i < pScene->mNumMaterials;++i) {
108 aiMaterial* mat = pScene->mMaterials[i];
109
110 aiString name;
111 mat->Get(AI_MATKEY_NAME,name);
112
113 if (name.length) {
114 std::list<std::string>::const_iterator it = std::find(strings.begin(), strings.end(), name.data);
115 if (it != strings.end()) {
116
117 // Our brilliant 'salt': A single material property with ~ as first
118 // character to mark it as internal and temporary.
119 const int dummy = 1;
120 ((aiMaterial*)mat)->AddProperty(&dummy,1,"~RRM.UniqueMaterial",0,0);
121
122 // Keep this material even if no mesh references it
123 abReferenced[i] = true;
124 DefaultLogger::get()->debug(std::string("Found positive match in exclusion list: \'") + name.data + "\'");
125 }
126 }
127 }
128 }
129
130 // TODO: re-implement this algorithm to work in-place
131 unsigned int *aiMappingTable = new unsigned int[pScene->mNumMaterials];
132 for ( unsigned int i=0; i<pScene->mNumMaterials; i++ ) {
133 aiMappingTable[ i ] = 0;
134 }
135 unsigned int iNewNum = 0;
136
137 // Iterate through all materials and calculate a hash for them
138 // store all hashes in a list and so a quick search whether
139 // we do already have a specific hash. This allows us to
140 // determine which materials are identical.
141 uint32_t *aiHashes = new uint32_t[ pScene->mNumMaterials ];;
142 for (unsigned int i = 0; i < pScene->mNumMaterials;++i)
143 {
144 // No mesh is referencing this material, remove it.
145 if (!abReferenced[i]) {
146 ++unreferencedRemoved;
147 delete pScene->mMaterials[i];
148 pScene->mMaterials[i] = nullptr;
149 continue;
150 }
151
152 // Check all previously mapped materials for a matching hash.
153 // On a match we can delete this material and just make it ref to the same index.
154 uint32_t me = aiHashes[i] = ComputeMaterialHash(pScene->mMaterials[i]);
155 for (unsigned int a = 0; a < i;++a)
156 {
157 if (abReferenced[a] && me == aiHashes[a]) {
158 ++redundantRemoved;
159 me = 0;
160 aiMappingTable[i] = aiMappingTable[a];
161 delete pScene->mMaterials[i];
162 pScene->mMaterials[i] = nullptr;
163 break;
164 }
165 }
166 // This is a new material that is referenced, add to the map.
167 if (me) {
168 aiMappingTable[i] = iNewNum++;
169 }
170 }
171 // If the new material count differs from the original,
172 // we need to rebuild the material list and remap mesh material indexes.
173 if (iNewNum != pScene->mNumMaterials) {
174 ai_assert(iNewNum > 0);
175 aiMaterial** ppcMaterials = new aiMaterial*[iNewNum];
176 ::memset(ppcMaterials,0,sizeof(void*)*iNewNum);
177 for (unsigned int p = 0; p < pScene->mNumMaterials;++p)
178 {
179 // if the material is not referenced ... remove it
180 if (!abReferenced[p]) {
181 continue;
182 }
183
184 // generate new names for modified materials that had no names
185 const unsigned int idx = aiMappingTable[p];
186 if (ppcMaterials[idx]) {
187 aiString sz;
188 if( ppcMaterials[idx]->Get(AI_MATKEY_NAME, sz) != AI_SUCCESS ) {
189 sz.length = ::ai_snprintf(sz.data,MAXLEN,"JoinedMaterial_#%u",p);
190 ((aiMaterial*)ppcMaterials[idx])->AddProperty(&sz,AI_MATKEY_NAME);
191 }
192 } else {
193 ppcMaterials[idx] = pScene->mMaterials[p];
194 }
195 }
196 // update all material indices
197 for (unsigned int p = 0; p < pScene->mNumMeshes;++p) {
198 aiMesh* mesh = pScene->mMeshes[p];
199 ai_assert( NULL!=mesh );
200 mesh->mMaterialIndex = aiMappingTable[mesh->mMaterialIndex];
201 }
202 // delete the old material list
203 delete[] pScene->mMaterials;
204 pScene->mMaterials = ppcMaterials;
205 pScene->mNumMaterials = iNewNum;
206 }
207 // delete temporary storage
208 delete[] aiHashes;
209 delete[] aiMappingTable;
210 }
211 if (redundantRemoved == 0 && unreferencedRemoved == 0)
212 {
213 DefaultLogger::get()->debug("RemoveRedundantMatsProcess finished ");
214 }
215 else
216 {
217 char szBuffer[128]; // should be sufficiently large
218 ::ai_snprintf(szBuffer,128,"RemoveRedundantMatsProcess finished. Removed %u redundant and %u unused materials.",
219 redundantRemoved,unreferencedRemoved);
220 DefaultLogger::get()->info(szBuffer);
221 }
222}
223