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
43/** @file OptimizeMeshes.cpp
44 * @brief Implementation of the aiProcess_OptimizeMeshes step
45 */
46
47
48#ifndef ASSIMP_BUILD_NO_OPTIMIZEMESHES_PROCESS
49
50
51#include "OptimizeMeshes.h"
52#include "ProcessHelper.h"
53#include <assimp/SceneCombiner.h>
54#include "Exceptional.h"
55
56using namespace Assimp;
57
58static const unsigned int NotSet = 0xffffffff;
59static const unsigned int DeadBeef = 0xdeadbeef;
60
61// ------------------------------------------------------------------------------------------------
62// Constructor to be privately used by Importer
63OptimizeMeshesProcess::OptimizeMeshesProcess()
64 : mScene()
65 , pts(false)
66 , max_verts( NotSet )
67 , max_faces( NotSet ) {
68 // empty
69}
70
71// ------------------------------------------------------------------------------------------------
72// Destructor, private as well
73OptimizeMeshesProcess::~OptimizeMeshesProcess() {
74 // empty
75}
76
77// ------------------------------------------------------------------------------------------------
78// Returns whether the processing step is present in the given flag field.
79bool OptimizeMeshesProcess::IsActive( unsigned int pFlags) const
80{
81 // Our behaviour needs to be different if the SortByPType or SplitLargeMeshes
82 // steps are active. Thus we need to query their flags here and store the
83 // information, although we're breaking const-correctness.
84 // That's a serious design flaw, consider redesign.
85 if( 0 != (pFlags & aiProcess_OptimizeMeshes) ) {
86 pts = (0 != (pFlags & aiProcess_SortByPType));
87 max_verts = ( 0 != ( pFlags & aiProcess_SplitLargeMeshes ) ) ? DeadBeef : max_verts;
88 return true;
89 }
90 return false;
91}
92
93// ------------------------------------------------------------------------------------------------
94// Setup properties for the post-processing step
95void OptimizeMeshesProcess::SetupProperties(const Importer* pImp)
96{
97 if( max_verts == DeadBeef /* magic hack */ ) {
98 max_faces = pImp->GetPropertyInteger(AI_CONFIG_PP_SLM_TRIANGLE_LIMIT,AI_SLM_DEFAULT_MAX_TRIANGLES);
99 max_verts = pImp->GetPropertyInteger(AI_CONFIG_PP_SLM_VERTEX_LIMIT,AI_SLM_DEFAULT_MAX_VERTICES);
100 }
101}
102
103// ------------------------------------------------------------------------------------------------
104// Execute step
105void OptimizeMeshesProcess::Execute( aiScene* pScene)
106{
107 const unsigned int num_old = pScene->mNumMeshes;
108 if (num_old <= 1) {
109 DefaultLogger::get()->debug("Skipping OptimizeMeshesProcess");
110 return;
111 }
112
113 DefaultLogger::get()->debug("OptimizeMeshesProcess begin");
114 mScene = pScene;
115
116 // need to clear persistent members from previous runs
117 merge_list.resize( 0 );
118 output.resize( 0 );
119
120 // ensure we have the right sizes
121 merge_list.reserve(pScene->mNumMeshes);
122 output.reserve(pScene->mNumMeshes);
123
124 // Prepare lookup tables
125 meshes.resize(pScene->mNumMeshes);
126 FindInstancedMeshes(pScene->mRootNode);
127 if( max_verts == DeadBeef ) /* undo the magic hack */
128 max_verts = NotSet;
129
130 // ... instanced meshes are immediately processed and added to the output list
131 for (unsigned int i = 0, n = 0; i < pScene->mNumMeshes;++i) {
132 meshes[i].vertex_format = GetMeshVFormatUnique(pScene->mMeshes[i]);
133
134 if (meshes[i].instance_cnt > 1 && meshes[i].output_id == NotSet ) {
135 meshes[i].output_id = n++;
136 output.push_back(mScene->mMeshes[i]);
137 }
138 }
139
140 // and process all nodes in the scenegraph recursively
141 ProcessNode(pScene->mRootNode);
142 if (!output.size()) {
143 throw DeadlyImportError("OptimizeMeshes: No meshes remaining; there's definitely something wrong");
144 }
145
146 meshes.resize( 0 );
147 ai_assert(output.size() <= num_old);
148
149 mScene->mNumMeshes = static_cast<unsigned int>(output.size());
150 std::copy(output.begin(),output.end(),mScene->mMeshes);
151
152 if (output.size() != num_old) {
153 char tmp[512];
154 ::ai_snprintf(tmp,512,"OptimizeMeshesProcess finished. Input meshes: %u, Output meshes: %u",num_old,pScene->mNumMeshes);
155 DefaultLogger::get()->info(tmp);
156 } else {
157 DefaultLogger::get()->debug( "OptimizeMeshesProcess finished" );
158 }
159}
160
161// ------------------------------------------------------------------------------------------------
162// Process meshes for a single node
163void OptimizeMeshesProcess::ProcessNode( aiNode* pNode)
164{
165 for (unsigned int i = 0; i < pNode->mNumMeshes;++i) {
166 unsigned int& im = pNode->mMeshes[i];
167
168 if (meshes[im].instance_cnt > 1) {
169 im = meshes[im].output_id;
170 }
171 else {
172 merge_list.resize( 0 );
173 unsigned int verts = 0, faces = 0;
174
175 // Find meshes to merge with us
176 for (unsigned int a = i+1; a < pNode->mNumMeshes;++a) {
177 unsigned int am = pNode->mMeshes[a];
178 if (meshes[am].instance_cnt == 1 && CanJoin(im,am,verts,faces)) {
179
180 merge_list.push_back(mScene->mMeshes[am]);
181 verts += mScene->mMeshes[am]->mNumVertices;
182 faces += mScene->mMeshes[am]->mNumFaces;
183
184 pNode->mMeshes[a] = pNode->mMeshes[pNode->mNumMeshes - 1];
185 --pNode->mNumMeshes;
186 --a;
187 }
188 }
189
190 // and merge all meshes which we found, replace the old ones
191 if (!merge_list.empty()) {
192 merge_list.push_back(mScene->mMeshes[im]);
193
194 aiMesh* out;
195 SceneCombiner::MergeMeshes(&out,0,merge_list.begin(),merge_list.end());
196 output.push_back(out);
197 } else {
198 output.push_back(mScene->mMeshes[im]);
199 }
200 im = static_cast<unsigned int>(output.size()-1);
201 }
202 }
203
204
205 for( unsigned int i = 0; i < pNode->mNumChildren; ++i ) {
206 ProcessNode( pNode->mChildren[ i ] );
207 }
208}
209
210// ------------------------------------------------------------------------------------------------
211// Check whether two meshes can be joined
212bool OptimizeMeshesProcess::CanJoin ( unsigned int a, unsigned int b, unsigned int verts, unsigned int faces )
213{
214 if (meshes[a].vertex_format != meshes[b].vertex_format)
215 return false;
216
217 aiMesh* ma = mScene->mMeshes[a], *mb = mScene->mMeshes[b];
218
219 if ((NotSet != max_verts && verts+mb->mNumVertices > max_verts) ||
220 (NotSet != max_faces && faces+mb->mNumFaces > max_faces)) {
221 return false;
222 }
223
224 // Never merge unskinned meshes with skinned meshes
225 if (ma->mMaterialIndex != mb->mMaterialIndex || ma->HasBones() != mb->HasBones())
226 return false;
227
228 // Never merge meshes with different kinds of primitives if SortByPType did already
229 // do its work. We would destroy everything again ...
230 if (pts && ma->mPrimitiveTypes != mb->mPrimitiveTypes)
231 return false;
232
233 // If both meshes are skinned, check whether we have many bones defined in both meshes.
234 // If yes, we can join them.
235 if (ma->HasBones()) {
236 // TODO
237 return false;
238 }
239 return true;
240}
241
242// ------------------------------------------------------------------------------------------------
243// Build a LUT of all instanced meshes
244void OptimizeMeshesProcess::FindInstancedMeshes (aiNode* pNode)
245{
246 for( unsigned int i = 0; i < pNode->mNumMeshes; ++i ) {
247 ++meshes[ pNode->mMeshes[ i ] ].instance_cnt;
248 }
249
250 for( unsigned int i = 0; i < pNode->mNumChildren; ++i ) {
251 FindInstancedMeshes( pNode->mChildren[ i ] );
252 }
253}
254
255// ------------------------------------------------------------------------------------------------
256
257#endif // !! ASSIMP_BUILD_NO_OPTIMIZEMESHES_PROCESS
258