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