/src/assimp/code/Common/Subdivision.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | Open Asset Import Library (assimp) |
3 | | ---------------------------------------------------------------------- |
4 | | |
5 | | Copyright (c) 2006-2024, assimp team |
6 | | |
7 | | All rights reserved. |
8 | | |
9 | | Redistribution and use of this software in source and binary forms, |
10 | | with or without modification, are permitted provided that the |
11 | | following conditions are met: |
12 | | |
13 | | * Redistributions of source code must retain the above |
14 | | copyright notice, this list of conditions and the |
15 | | following disclaimer. |
16 | | |
17 | | * Redistributions in binary form must reproduce the above |
18 | | copyright notice, this list of conditions and the |
19 | | following disclaimer in the documentation and/or other |
20 | | materials provided with the distribution. |
21 | | |
22 | | * Neither the name of the assimp team, nor the names of its |
23 | | contributors may be used to endorse or promote products |
24 | | derived from this software without specific prior |
25 | | written permission of the assimp team. |
26 | | |
27 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
28 | | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
29 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
30 | | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
31 | | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
32 | | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
33 | | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
34 | | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
35 | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
36 | | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
37 | | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
38 | | |
39 | | ---------------------------------------------------------------------- |
40 | | */ |
41 | | |
42 | | #include <assimp/Subdivision.h> |
43 | | #include <assimp/SceneCombiner.h> |
44 | | #include <assimp/SpatialSort.h> |
45 | | #include <assimp/Vertex.h> |
46 | | #include <assimp/ai_assert.h> |
47 | | |
48 | | #include "PostProcessing/ProcessHelper.h" |
49 | | |
50 | | #include <stdio.h> |
51 | | |
52 | | #include <unordered_map> |
53 | | |
54 | | using namespace Assimp; |
55 | | |
56 | 0 | void mydummy() {} |
57 | | |
58 | | #ifdef _MSC_VER |
59 | | #pragma warning(disable : 4709) |
60 | | #endif // _MSC_VER |
61 | | // ------------------------------------------------------------------------------------------------ |
62 | | /** Subdivider stub class to implement the Catmull-Clarke subdivision algorithm. The |
63 | | * implementation is basing on recursive refinement. Directly evaluating the result is also |
64 | | * possible and much quicker, but it depends on lengthy matrix lookup tables. */ |
65 | | // ------------------------------------------------------------------------------------------------ |
66 | | class CatmullClarkSubdivider : public Subdivider { |
67 | | public: |
68 | | void Subdivide(aiMesh *mesh, aiMesh *&out, unsigned int num, bool discard_input); |
69 | | void Subdivide(aiMesh **smesh, size_t nmesh, |
70 | | aiMesh **out, unsigned int num, bool discard_input); |
71 | | |
72 | | // --------------------------------------------------------------------------- |
73 | | /** Intermediate description of an edge between two corners of a polygon*/ |
74 | | // --------------------------------------------------------------------------- |
75 | | struct Edge { |
76 | | Edge() : |
77 | 0 | ref(0) {} |
78 | | Vertex edge_point, midpoint; |
79 | | unsigned int ref; |
80 | | }; |
81 | | |
82 | | typedef std::vector<unsigned int> UIntVector; |
83 | | typedef std::unordered_map<uint64_t, Edge> EdgeMap; |
84 | | |
85 | | // --------------------------------------------------------------------------- |
86 | | // Hashing function to derive an index into an #EdgeMap from two given |
87 | | // 'unsigned int' vertex coordinates (!!distinct coordinates - same |
88 | | // vertex position == same index!!). |
89 | | // NOTE - this leads to rare hash collisions if a) sizeof(unsigned int)>4 |
90 | | // and (id[0]>2^32-1 or id[0]>2^32-1). |
91 | | // MAKE_EDGE_HASH() uses temporaries, so INIT_EDGE_HASH() needs to be put |
92 | | // at the head of every function which is about to use MAKE_EDGE_HASH(). |
93 | | // Reason is that the hash is that hash construction needs to hold the |
94 | | // invariant id0<id1 to identify an edge - else two hashes would refer |
95 | | // to the same edge. |
96 | | // --------------------------------------------------------------------------- |
97 | 0 | #define MAKE_EDGE_HASH(id0, id1) (eh_tmp0__ = id0, eh_tmp1__ = id1, \ |
98 | 0 | (eh_tmp0__ < eh_tmp1__ ? std::swap(eh_tmp0__, eh_tmp1__) : mydummy()), (uint64_t)eh_tmp0__ ^ ((uint64_t)eh_tmp1__ << 32u)) |
99 | | |
100 | | #define INIT_EDGE_HASH_TEMPORARIES() \ |
101 | 0 | unsigned int eh_tmp0__, eh_tmp1__; |
102 | | |
103 | | private: |
104 | | void InternSubdivide(const aiMesh *const *smesh, |
105 | | size_t nmesh, aiMesh **out, unsigned int num); |
106 | | }; |
107 | | |
108 | | // ------------------------------------------------------------------------------------------------ |
109 | | // Construct a subdivider of a specific type |
110 | 0 | Subdivider *Subdivider::Create(Algorithm algo) { |
111 | 0 | switch (algo) { |
112 | 0 | case CATMULL_CLARKE: |
113 | 0 | return new CatmullClarkSubdivider(); |
114 | 0 | }; |
115 | |
|
116 | 0 | ai_assert(false); |
117 | |
|
118 | 0 | return nullptr; // shouldn't happen |
119 | 0 | } |
120 | | |
121 | | // ------------------------------------------------------------------------------------------------ |
122 | | // Call the Catmull Clark subdivision algorithm for one mesh |
123 | | void CatmullClarkSubdivider::Subdivide( |
124 | | aiMesh *mesh, |
125 | | aiMesh *&out, |
126 | | unsigned int num, |
127 | 0 | bool discard_input) { |
128 | 0 | ai_assert(mesh != out); |
129 | |
|
130 | 0 | Subdivide(&mesh, 1, &out, num, discard_input); |
131 | 0 | } |
132 | | |
133 | | // ------------------------------------------------------------------------------------------------ |
134 | | // Call the Catmull Clark subdivision algorithm for multiple meshes |
135 | | void CatmullClarkSubdivider::Subdivide( |
136 | | aiMesh **smesh, |
137 | | size_t nmesh, |
138 | | aiMesh **out, |
139 | | unsigned int num, |
140 | 0 | bool discard_input) { |
141 | 0 | ai_assert(nullptr != smesh); |
142 | 0 | ai_assert(nullptr != out); |
143 | | |
144 | | // course, both regions may not overlap |
145 | 0 | ai_assert(smesh < out || smesh + nmesh > out + nmesh); |
146 | 0 | if (!num) { |
147 | | // No subdivision at all. Need to copy all the meshes .. argh. |
148 | 0 | if (discard_input) { |
149 | 0 | for (size_t s = 0; s < nmesh; ++s) { |
150 | 0 | out[s] = smesh[s]; |
151 | 0 | smesh[s] = nullptr; |
152 | 0 | } |
153 | 0 | } else { |
154 | 0 | for (size_t s = 0; s < nmesh; ++s) { |
155 | 0 | SceneCombiner::Copy(out + s, smesh[s]); |
156 | 0 | } |
157 | 0 | } |
158 | 0 | return; |
159 | 0 | } |
160 | | |
161 | 0 | std::vector<aiMesh *> inmeshes; |
162 | 0 | std::vector<aiMesh *> outmeshes; |
163 | 0 | std::vector<unsigned int> maptbl; |
164 | |
|
165 | 0 | inmeshes.reserve(nmesh); |
166 | 0 | outmeshes.reserve(nmesh); |
167 | 0 | maptbl.reserve(nmesh); |
168 | | |
169 | | // Remove pure line and point meshes from the working set to reduce the |
170 | | // number of edge cases the subdivider is forced to deal with. Line and |
171 | | // point meshes are simply passed through. |
172 | 0 | for (size_t s = 0; s < nmesh; ++s) { |
173 | 0 | aiMesh *i = smesh[s]; |
174 | | // FIX - mPrimitiveTypes might not yet be initialized |
175 | 0 | if (i->mPrimitiveTypes && (i->mPrimitiveTypes & (aiPrimitiveType_LINE | aiPrimitiveType_POINT)) == i->mPrimitiveTypes) { |
176 | 0 | ASSIMP_LOG_VERBOSE_DEBUG("Catmull-Clark Subdivider: Skipping pure line/point mesh"); |
177 | |
|
178 | 0 | if (discard_input) { |
179 | 0 | out[s] = i; |
180 | 0 | smesh[s] = nullptr; |
181 | 0 | } else { |
182 | 0 | SceneCombiner::Copy(out + s, i); |
183 | 0 | } |
184 | 0 | continue; |
185 | 0 | } |
186 | | |
187 | 0 | outmeshes.push_back(nullptr); |
188 | 0 | inmeshes.push_back(i); |
189 | 0 | maptbl.push_back(static_cast<unsigned int>(s)); |
190 | 0 | } |
191 | | |
192 | | // Do the actual subdivision on the preallocated storage. InternSubdivide |
193 | | // *always* assumes that enough storage is available, it does not bother |
194 | | // checking any ranges. |
195 | 0 | ai_assert(inmeshes.size() == outmeshes.size()); |
196 | 0 | ai_assert(inmeshes.size() == maptbl.size()); |
197 | 0 | if (inmeshes.empty()) { |
198 | 0 | ASSIMP_LOG_WARN("Catmull-Clark Subdivider: Pure point/line scene, I can't do anything"); |
199 | 0 | return; |
200 | 0 | } |
201 | 0 | InternSubdivide(&inmeshes.front(), inmeshes.size(), &outmeshes.front(), num); |
202 | 0 | for (unsigned int i = 0; i < maptbl.size(); ++i) { |
203 | 0 | ai_assert(nullptr != outmeshes[i]); |
204 | 0 | out[maptbl[i]] = outmeshes[i]; |
205 | 0 | } |
206 | |
|
207 | 0 | if (discard_input) { |
208 | 0 | for (size_t s = 0; s < nmesh; ++s) { |
209 | 0 | delete smesh[s]; |
210 | 0 | } |
211 | 0 | } |
212 | 0 | } |
213 | | |
214 | | // ------------------------------------------------------------------------------------------------ |
215 | | // Note - this is an implementation of the standard (recursive) Cm-Cl algorithm without further |
216 | | // optimizations (except we're using some nice LUTs). A description of the algorithm can be found |
217 | | // here: http://en.wikipedia.org/wiki/Catmull-Clark_subdivision_surface |
218 | | // |
219 | | // The code is mostly O(n), however parts are O(nlogn) which is therefore the algorithm's |
220 | | // expected total runtime complexity. The implementation is able to work in-place on the same |
221 | | // mesh arrays. Calling #InternSubdivide() directly is not encouraged. The code can operate |
222 | | // in-place unless 'smesh' and 'out' are equal (no strange overlaps or reorderings). |
223 | | // Previous data is replaced/deleted then. |
224 | | // ------------------------------------------------------------------------------------------------ |
225 | | void CatmullClarkSubdivider::InternSubdivide( |
226 | | const aiMesh *const *smesh, |
227 | | size_t nmesh, |
228 | | aiMesh **out, |
229 | 0 | unsigned int num) { |
230 | 0 | ai_assert(nullptr != smesh); |
231 | 0 | ai_assert(nullptr != out); |
232 | |
|
233 | 0 | INIT_EDGE_HASH_TEMPORARIES(); |
234 | | |
235 | | // no subdivision requested or end of recursive refinement |
236 | 0 | if (!num) { |
237 | 0 | return; |
238 | 0 | } |
239 | | |
240 | 0 | UIntVector maptbl; |
241 | 0 | SpatialSort spatial; |
242 | | |
243 | | // --------------------------------------------------------------------- |
244 | | // 0. Offset table to index all meshes continuously, generate a spatially |
245 | | // sorted representation of all vertices in all meshes. |
246 | | // --------------------------------------------------------------------- |
247 | 0 | typedef std::pair<unsigned int, unsigned int> IntPair; |
248 | 0 | std::vector<IntPair> moffsets(nmesh); |
249 | 0 | unsigned int totfaces = 0, totvert = 0; |
250 | 0 | for (size_t t = 0; t < nmesh; ++t) { |
251 | 0 | const aiMesh *mesh = smesh[t]; |
252 | |
|
253 | 0 | spatial.Append(mesh->mVertices, mesh->mNumVertices, sizeof(aiVector3D), false); |
254 | 0 | moffsets[t] = IntPair(totfaces, totvert); |
255 | |
|
256 | 0 | totfaces += mesh->mNumFaces; |
257 | 0 | totvert += mesh->mNumVertices; |
258 | 0 | } |
259 | |
|
260 | 0 | spatial.Finalize(); |
261 | 0 | const unsigned int num_unique = spatial.GenerateMappingTable(maptbl, ComputePositionEpsilon(smesh, nmesh)); |
262 | |
|
263 | 0 | #define FLATTEN_VERTEX_IDX(mesh_idx, vert_idx) (moffsets[mesh_idx].second + vert_idx) |
264 | 0 | #define FLATTEN_FACE_IDX(mesh_idx, face_idx) (moffsets[mesh_idx].first + face_idx) |
265 | | |
266 | | // --------------------------------------------------------------------- |
267 | | // 1. Compute the centroid point for all faces |
268 | | // --------------------------------------------------------------------- |
269 | 0 | std::vector<Vertex> centroids(totfaces); |
270 | 0 | unsigned int nfacesout = 0; |
271 | 0 | for (size_t t = 0, n = 0; t < nmesh; ++t) { |
272 | 0 | const aiMesh *mesh = smesh[t]; |
273 | 0 | for (unsigned int i = 0; i < mesh->mNumFaces; ++i, ++n) { |
274 | 0 | const aiFace &face = mesh->mFaces[i]; |
275 | 0 | Vertex &c = centroids[n]; |
276 | |
|
277 | 0 | for (unsigned int a = 0; a < face.mNumIndices; ++a) { |
278 | 0 | c += Vertex(mesh, face.mIndices[a]); |
279 | 0 | } |
280 | |
|
281 | 0 | c /= static_cast<float>(face.mNumIndices); |
282 | 0 | nfacesout += face.mNumIndices; |
283 | 0 | } |
284 | 0 | } |
285 | |
|
286 | 0 | { |
287 | | // we want edges to go away before the recursive calls so begin a new scope |
288 | 0 | EdgeMap edges; |
289 | | |
290 | | // --------------------------------------------------------------------- |
291 | | // 2. Set each edge point to be the average of all neighbouring |
292 | | // face points and original points. Every edge exists twice |
293 | | // if there is a neighboring face. |
294 | | // --------------------------------------------------------------------- |
295 | 0 | for (size_t t = 0; t < nmesh; ++t) { |
296 | 0 | const aiMesh *mesh = smesh[t]; |
297 | |
|
298 | 0 | for (unsigned int i = 0; i < mesh->mNumFaces; ++i) { |
299 | 0 | const aiFace &face = mesh->mFaces[i]; |
300 | |
|
301 | 0 | for (unsigned int p = 0; p < face.mNumIndices; ++p) { |
302 | 0 | const unsigned int id[] = { |
303 | 0 | face.mIndices[p], |
304 | 0 | face.mIndices[p == face.mNumIndices - 1 ? 0 : p + 1] |
305 | 0 | }; |
306 | 0 | const unsigned int mp[] = { |
307 | 0 | maptbl[FLATTEN_VERTEX_IDX(t, id[0])], |
308 | 0 | maptbl[FLATTEN_VERTEX_IDX(t, id[1])] |
309 | 0 | }; |
310 | |
|
311 | 0 | Edge &e = edges[MAKE_EDGE_HASH(mp[0], mp[1])]; |
312 | 0 | e.ref++; |
313 | 0 | if (e.ref <= 2) { |
314 | 0 | if (e.ref == 1) { // original points (end points) - add only once |
315 | 0 | e.edge_point = e.midpoint = Vertex(mesh, id[0]) + Vertex(mesh, id[1]); |
316 | 0 | e.midpoint *= 0.5f; |
317 | 0 | } |
318 | 0 | e.edge_point += centroids[FLATTEN_FACE_IDX(t, i)]; |
319 | 0 | } |
320 | 0 | } |
321 | 0 | } |
322 | 0 | } |
323 | | |
324 | | // --------------------------------------------------------------------- |
325 | | // 3. Normalize edge points |
326 | | // --------------------------------------------------------------------- |
327 | 0 | { |
328 | 0 | unsigned int bad_cnt = 0; |
329 | 0 | for (EdgeMap::iterator it = edges.begin(); it != edges.end(); ++it) { |
330 | 0 | if ((*it).second.ref < 2) { |
331 | 0 | ai_assert((*it).second.ref); |
332 | 0 | ++bad_cnt; |
333 | 0 | } |
334 | 0 | (*it).second.edge_point *= 1.f / ((*it).second.ref + 2.f); |
335 | 0 | } |
336 | |
|
337 | 0 | if (bad_cnt) { |
338 | | // Report the number of bad edges. bad edges are referenced by less than two |
339 | | // faces in the mesh. They occur at outer model boundaries in non-closed |
340 | | // shapes. |
341 | 0 | ASSIMP_LOG_VERBOSE_DEBUG("Catmull-Clark Subdivider: got ", bad_cnt, " bad edges touching only one face (totally ", |
342 | 0 | static_cast<unsigned int>(edges.size()), " edges). "); |
343 | 0 | } |
344 | 0 | } |
345 | | |
346 | | // --------------------------------------------------------------------- |
347 | | // 4. Compute a vertex-face adjacency table. We can't reuse the code |
348 | | // from VertexTriangleAdjacency because we need the table for multiple |
349 | | // meshes and out vertex indices need to be mapped to distinct values |
350 | | // first. |
351 | | // --------------------------------------------------------------------- |
352 | 0 | UIntVector faceadjac(nfacesout), cntadjfac(maptbl.size(), 0), ofsadjvec(maptbl.size() + 1, 0); |
353 | 0 | { |
354 | 0 | for (size_t t = 0; t < nmesh; ++t) { |
355 | 0 | const aiMesh *const minp = smesh[t]; |
356 | 0 | for (unsigned int i = 0; i < minp->mNumFaces; ++i) { |
357 | |
|
358 | 0 | const aiFace &f = minp->mFaces[i]; |
359 | 0 | for (unsigned int n = 0; n < f.mNumIndices; ++n) { |
360 | 0 | ++cntadjfac[maptbl[FLATTEN_VERTEX_IDX(t, f.mIndices[n])]]; |
361 | 0 | } |
362 | 0 | } |
363 | 0 | } |
364 | 0 | unsigned int cur = 0; |
365 | 0 | for (size_t i = 0; i < cntadjfac.size(); ++i) { |
366 | 0 | ofsadjvec[i + 1] = cur; |
367 | 0 | cur += cntadjfac[i]; |
368 | 0 | } |
369 | 0 | for (size_t t = 0; t < nmesh; ++t) { |
370 | 0 | const aiMesh *const minp = smesh[t]; |
371 | 0 | for (unsigned int i = 0; i < minp->mNumFaces; ++i) { |
372 | |
|
373 | 0 | const aiFace &f = minp->mFaces[i]; |
374 | 0 | for (unsigned int n = 0; n < f.mNumIndices; ++n) { |
375 | 0 | faceadjac[ofsadjvec[1 + maptbl[FLATTEN_VERTEX_IDX(t, f.mIndices[n])]]++] = FLATTEN_FACE_IDX(t, i); |
376 | 0 | } |
377 | 0 | } |
378 | 0 | } |
379 | | |
380 | | // check the other way round for consistency |
381 | 0 | #ifdef ASSIMP_BUILD_DEBUG |
382 | |
|
383 | 0 | for (size_t t = 0; t < ofsadjvec.size() - 1; ++t) { |
384 | 0 | for (unsigned int m = 0; m < cntadjfac[t]; ++m) { |
385 | 0 | const unsigned int fidx = faceadjac[ofsadjvec[t] + m]; |
386 | 0 | ai_assert(fidx < totfaces); |
387 | 0 | for (size_t n = 1; n < nmesh; ++n) { |
388 | |
|
389 | 0 | if (moffsets[n].first > fidx) { |
390 | 0 | const aiMesh *msh = smesh[--n]; |
391 | 0 | const aiFace &f = msh->mFaces[fidx - moffsets[n].first]; |
392 | |
|
393 | 0 | bool haveit = false; |
394 | 0 | for (unsigned int i = 0; i < f.mNumIndices; ++i) { |
395 | 0 | if (maptbl[FLATTEN_VERTEX_IDX(n, f.mIndices[i])] == (unsigned int)t) { |
396 | 0 | haveit = true; |
397 | 0 | break; |
398 | 0 | } |
399 | 0 | } |
400 | 0 | ai_assert(haveit); |
401 | 0 | if (!haveit) { |
402 | 0 | ASSIMP_LOG_VERBOSE_DEBUG("Catmull-Clark Subdivider: Index not used"); |
403 | 0 | } |
404 | 0 | break; |
405 | 0 | } |
406 | 0 | } |
407 | 0 | } |
408 | 0 | } |
409 | |
|
410 | 0 | #endif |
411 | 0 | } |
412 | |
|
413 | 0 | #define GET_ADJACENT_FACES_AND_CNT(vidx, fstartout, numout) \ |
414 | 0 | fstartout = &faceadjac[ofsadjvec[vidx]], numout = cntadjfac[vidx] |
415 | |
|
416 | 0 | typedef std::pair<bool, Vertex> TouchedOVertex; |
417 | 0 | std::vector<TouchedOVertex> new_points(num_unique, TouchedOVertex(false, Vertex())); |
418 | | // --------------------------------------------------------------------- |
419 | | // 5. Spawn a quad from each face point to the corresponding edge points |
420 | | // the original points being the fourth quad points. |
421 | | // --------------------------------------------------------------------- |
422 | 0 | for (size_t t = 0; t < nmesh; ++t) { |
423 | 0 | const aiMesh *const minp = smesh[t]; |
424 | 0 | aiMesh *const mout = out[t] = new aiMesh(); |
425 | |
|
426 | 0 | for (unsigned int a = 0; a < minp->mNumFaces; ++a) { |
427 | 0 | mout->mNumFaces += minp->mFaces[a].mNumIndices; |
428 | 0 | } |
429 | | |
430 | | // We need random access to the old face buffer, so reuse is not possible. |
431 | 0 | mout->mFaces = new aiFace[mout->mNumFaces]; |
432 | |
|
433 | 0 | mout->mNumVertices = mout->mNumFaces * 4; |
434 | 0 | mout->mVertices = new aiVector3D[mout->mNumVertices]; |
435 | | |
436 | | // quads only, keep material index |
437 | 0 | mout->mPrimitiveTypes = aiPrimitiveType_POLYGON; |
438 | 0 | mout->mMaterialIndex = minp->mMaterialIndex; |
439 | |
|
440 | 0 | if (minp->HasNormals()) { |
441 | 0 | mout->mNormals = new aiVector3D[mout->mNumVertices]; |
442 | 0 | } |
443 | |
|
444 | 0 | if (minp->HasTangentsAndBitangents()) { |
445 | 0 | mout->mTangents = new aiVector3D[mout->mNumVertices]; |
446 | 0 | mout->mBitangents = new aiVector3D[mout->mNumVertices]; |
447 | 0 | } |
448 | |
|
449 | 0 | for (unsigned int i = 0; minp->HasTextureCoords(i); ++i) { |
450 | 0 | mout->mTextureCoords[i] = new aiVector3D[mout->mNumVertices]; |
451 | 0 | mout->mNumUVComponents[i] = minp->mNumUVComponents[i]; |
452 | 0 | } |
453 | |
|
454 | 0 | for (unsigned int i = 0; minp->HasVertexColors(i); ++i) { |
455 | 0 | mout->mColors[i] = new aiColor4D[mout->mNumVertices]; |
456 | 0 | } |
457 | |
|
458 | 0 | mout->mNumVertices = mout->mNumFaces << 2u; |
459 | 0 | for (unsigned int i = 0, v = 0, n = 0; i < minp->mNumFaces; ++i) { |
460 | |
|
461 | 0 | const aiFace &face = minp->mFaces[i]; |
462 | 0 | for (unsigned int a = 0; a < face.mNumIndices; ++a) { |
463 | | |
464 | | // Get a clean new face. |
465 | 0 | aiFace &faceOut = mout->mFaces[n++]; |
466 | 0 | faceOut.mIndices = new unsigned int[faceOut.mNumIndices = 4]; |
467 | | |
468 | | // Spawn a new quadrilateral (ccw winding) for this original point between: |
469 | | // a) face centroid |
470 | 0 | centroids[FLATTEN_FACE_IDX(t, i)].SortBack(mout, faceOut.mIndices[0] = v++); |
471 | | |
472 | | // b) adjacent edge on the left, seen from the centroid |
473 | 0 | const Edge &e0 = edges[MAKE_EDGE_HASH(maptbl[FLATTEN_VERTEX_IDX(t, face.mIndices[a])], |
474 | 0 | maptbl[FLATTEN_VERTEX_IDX(t, face.mIndices[a == face.mNumIndices - 1 ? 0 : a + 1])])]; // fixme: replace with mod face.mNumIndices? |
475 | | |
476 | | // c) adjacent edge on the right, seen from the centroid |
477 | 0 | const Edge &e1 = edges[MAKE_EDGE_HASH(maptbl[FLATTEN_VERTEX_IDX(t, face.mIndices[a])], |
478 | 0 | maptbl[FLATTEN_VERTEX_IDX(t, face.mIndices[!a ? face.mNumIndices - 1 : a - 1])])]; // fixme: replace with mod face.mNumIndices? |
479 | |
|
480 | 0 | e0.edge_point.SortBack(mout, faceOut.mIndices[3] = v++); |
481 | 0 | e1.edge_point.SortBack(mout, faceOut.mIndices[1] = v++); |
482 | | |
483 | | // d= original point P with distinct index i |
484 | | // F := 0 |
485 | | // R := 0 |
486 | | // n := 0 |
487 | | // for each face f containing i |
488 | | // F := F+ centroid of f |
489 | | // R := R+ midpoint of edge of f from i to i+1 |
490 | | // n := n+1 |
491 | | // |
492 | | // (F+2R+(n-3)P)/n |
493 | 0 | const unsigned int org = maptbl[FLATTEN_VERTEX_IDX(t, face.mIndices[a])]; |
494 | 0 | TouchedOVertex &ov = new_points[org]; |
495 | |
|
496 | 0 | if (!ov.first) { |
497 | 0 | ov.first = true; |
498 | |
|
499 | 0 | const unsigned int *adj; |
500 | 0 | unsigned int cnt; |
501 | 0 | GET_ADJACENT_FACES_AND_CNT(org, adj, cnt); |
502 | |
|
503 | 0 | if (cnt < 3) { |
504 | 0 | ov.second = Vertex(minp, face.mIndices[a]); |
505 | 0 | } else { |
506 | |
|
507 | 0 | Vertex F, R; |
508 | 0 | for (unsigned int o = 0; o < cnt; ++o) { |
509 | 0 | ai_assert(adj[o] < totfaces); |
510 | 0 | F += centroids[adj[o]]; |
511 | | |
512 | | // adj[0] is a global face index - search the face in the mesh list |
513 | 0 | const aiMesh *mp = nullptr; |
514 | 0 | size_t nidx; |
515 | |
|
516 | 0 | if (adj[o] < moffsets[0].first) { |
517 | 0 | mp = smesh[nidx = 0]; |
518 | 0 | } else { |
519 | 0 | for (nidx = 1; nidx <= nmesh; ++nidx) { |
520 | 0 | if (nidx == nmesh || moffsets[nidx].first > adj[o]) { |
521 | 0 | mp = smesh[--nidx]; |
522 | 0 | break; |
523 | 0 | } |
524 | 0 | } |
525 | 0 | } |
526 | |
|
527 | 0 | if (mp == nullptr) { |
528 | 0 | continue; |
529 | 0 | } |
530 | | |
531 | 0 | ai_assert(adj[o] - moffsets[nidx].first < mp->mNumFaces); |
532 | 0 | const aiFace &f = mp->mFaces[adj[o] - moffsets[nidx].first]; |
533 | 0 | bool haveit = false; |
534 | | |
535 | | // find our original point in the face |
536 | 0 | for (unsigned int m = 0; m < f.mNumIndices; ++m) { |
537 | 0 | if (maptbl[FLATTEN_VERTEX_IDX(nidx, f.mIndices[m])] == org) { |
538 | | |
539 | | // add *both* edges. this way, we can be sure that we add |
540 | | // *all* adjacent edges to R. In a closed shape, every |
541 | | // edge is added twice - so we simply leave out the |
542 | | // factor 2.f in the amove formula and get the right |
543 | | // result. |
544 | |
|
545 | 0 | const Edge &c0 = edges[MAKE_EDGE_HASH(org, maptbl[FLATTEN_VERTEX_IDX( |
546 | 0 | nidx, f.mIndices[!m ? f.mNumIndices - 1 : m - 1])])]; |
547 | | // fixme: replace with mod face.mNumIndices? |
548 | |
|
549 | 0 | const Edge &c1 = edges[MAKE_EDGE_HASH(org, maptbl[FLATTEN_VERTEX_IDX( |
550 | 0 | nidx, f.mIndices[m == f.mNumIndices - 1 ? 0 : m + 1])])]; |
551 | | // fixme: replace with mod face.mNumIndices? |
552 | 0 | R += c0.midpoint + c1.midpoint; |
553 | |
|
554 | 0 | haveit = true; |
555 | 0 | break; |
556 | 0 | } |
557 | 0 | } |
558 | | |
559 | | // this invariant *must* hold if the vertex-to-face adjacency table is valid |
560 | 0 | ai_assert(haveit); |
561 | 0 | if (!haveit) { |
562 | 0 | ASSIMP_LOG_WARN("OBJ: no name for material library specified."); |
563 | 0 | } |
564 | 0 | } |
565 | |
|
566 | 0 | const float div = static_cast<float>(cnt), divsq = 1.f / (div * div); |
567 | 0 | ov.second = Vertex(minp, face.mIndices[a]) * ((div - 3.f) / div) + R * divsq + F * divsq; |
568 | 0 | } |
569 | 0 | } |
570 | 0 | ov.second.SortBack(mout, faceOut.mIndices[2] = v++); |
571 | 0 | } |
572 | 0 | } |
573 | 0 | } |
574 | 0 | } // end of scope for edges, freeing its memory |
575 | | |
576 | | // --------------------------------------------------------------------- |
577 | | // 7. Apply the next subdivision step. |
578 | | // --------------------------------------------------------------------- |
579 | 0 | if (num != 1) { |
580 | 0 | std::vector<aiMesh *> tmp(nmesh); |
581 | 0 | InternSubdivide(out, nmesh, &tmp.front(), num - 1); |
582 | 0 | for (size_t i = 0; i < nmesh; ++i) { |
583 | 0 | delete out[i]; |
584 | 0 | out[i] = tmp[i]; |
585 | 0 | } |
586 | 0 | } |
587 | 0 | } |