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