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