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