Branch data Line data Source code
1 : : // This file is part of meshoptimizer library; see meshoptimizer.h for version/license details
2 : : #include "meshoptimizer.h"
3 : :
4 : : #include <assert.h>
5 : : #include <float.h>
6 : : #include <math.h>
7 : : #include <string.h>
8 : :
9 : : #ifndef TRACE
10 : : #define TRACE 0
11 : : #endif
12 : :
13 : : #if TRACE
14 : : #include <stdio.h>
15 : : #endif
16 : :
17 : : // This work is based on:
18 : : // Michael Garland and Paul S. Heckbert. Surface simplification using quadric error metrics. 1997
19 : : // Michael Garland. Quadric-based polygonal surface simplification. 1999
20 : : // Peter Lindstrom. Out-of-Core Simplification of Large Polygonal Models. 2000
21 : : // Matthias Teschner, Bruno Heidelberger, Matthias Mueller, Danat Pomeranets, Markus Gross. Optimized Spatial Hashing for Collision Detection of Deformable Objects. 2003
22 : : // Peter Van Sandt, Yannis Chronis, Jignesh M. Patel. Efficiently Searching In-Memory Sorted Arrays: Revenge of the Interpolation Search? 2019
23 : : namespace meshopt
24 : : {
25 : :
26 : : struct EdgeAdjacency
27 : : {
28 : : unsigned int *counts;
29 : : unsigned int *offsets;
30 : : unsigned int *data;
31 : : };
32 : :
33 : 0 : static void buildEdgeAdjacency( EdgeAdjacency &adjacency, const unsigned int *indices, size_t index_count, size_t vertex_count, meshopt_Allocator &allocator )
34 : : {
35 : 0 : size_t face_count = index_count / 3;
36 : :
37 : : // allocate arrays
38 : 0 : adjacency.counts = allocator.allocate<unsigned int>( vertex_count );
39 : 0 : adjacency.offsets = allocator.allocate<unsigned int>( vertex_count );
40 : 0 : adjacency.data = allocator.allocate<unsigned int>( index_count );
41 : :
42 : : // fill edge counts
43 : 0 : memset( adjacency.counts, 0, vertex_count * sizeof( unsigned int ) );
44 : :
45 : 0 : for ( size_t i = 0; i < index_count; ++i )
46 : : {
47 : 0 : assert( indices[i] < vertex_count );
48 : :
49 : 0 : adjacency.counts[indices[i]]++;
50 : 0 : }
51 : :
52 : : // fill offset table
53 : 0 : unsigned int offset = 0;
54 : :
55 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
56 : : {
57 : 0 : adjacency.offsets[i] = offset;
58 : 0 : offset += adjacency.counts[i];
59 : 0 : }
60 : :
61 : 0 : assert( offset == index_count );
62 : :
63 : : // fill edge data
64 : 0 : for ( size_t i = 0; i < face_count; ++i )
65 : : {
66 : 0 : unsigned int a = indices[i * 3 + 0], b = indices[i * 3 + 1], c = indices[i * 3 + 2];
67 : :
68 : 0 : adjacency.data[adjacency.offsets[a]++] = b;
69 : 0 : adjacency.data[adjacency.offsets[b]++] = c;
70 : 0 : adjacency.data[adjacency.offsets[c]++] = a;
71 : 0 : }
72 : :
73 : : // fix offsets that have been disturbed by the previous pass
74 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
75 : : {
76 : 0 : assert( adjacency.offsets[i] >= adjacency.counts[i] );
77 : :
78 : 0 : adjacency.offsets[i] -= adjacency.counts[i];
79 : 0 : }
80 : 0 : }
81 : :
82 : : struct PositionHasher
83 : : {
84 : : const float *vertex_positions;
85 : : size_t vertex_stride_float;
86 : :
87 : 0 : size_t hash( unsigned int index ) const
88 : : {
89 : : // MurmurHash2
90 : 0 : const unsigned int m = 0x5bd1e995;
91 : 0 : const int r = 24;
92 : :
93 : 0 : unsigned int h = 0;
94 : 0 : const unsigned int *key = reinterpret_cast<const unsigned int *>( vertex_positions + index * vertex_stride_float );
95 : :
96 : 0 : for ( size_t i = 0; i < 3; ++i )
97 : : {
98 : 0 : unsigned int k = key[i];
99 : :
100 : 0 : k *= m;
101 : 0 : k ^= k >> r;
102 : 0 : k *= m;
103 : :
104 : 0 : h *= m;
105 : 0 : h ^= k;
106 : 0 : }
107 : :
108 : 0 : return h;
109 : : }
110 : :
111 : 0 : bool equal( unsigned int lhs, unsigned int rhs ) const
112 : : {
113 : 0 : return memcmp( vertex_positions + lhs * vertex_stride_float, vertex_positions + rhs * vertex_stride_float, sizeof( float ) * 3 ) == 0;
114 : : }
115 : : };
116 : :
117 : 0 : static size_t hashBuckets2( size_t count )
118 : : {
119 : 0 : size_t buckets = 1;
120 : 0 : while ( buckets < count )
121 : 0 : buckets *= 2;
122 : :
123 : 0 : return buckets;
124 : : }
125 : :
126 : : template <typename T, typename Hash>
127 : 0 : static T *hashLookup2( T *table, size_t buckets, const Hash &hash, const T &key, const T &empty )
128 : : {
129 : 0 : assert( buckets > 0 );
130 : 0 : assert( ( buckets & ( buckets - 1 ) ) == 0 );
131 : :
132 : 0 : size_t hashmod = buckets - 1;
133 : 0 : size_t bucket = hash.hash( key ) & hashmod;
134 : :
135 : 0 : for ( size_t probe = 0; probe <= hashmod; ++probe )
136 : : {
137 : 0 : T &item = table[bucket];
138 : :
139 : 0 : if ( item == empty )
140 : 0 : return &item;
141 : :
142 : 0 : if ( hash.equal( item, key ) )
143 : 0 : return &item;
144 : :
145 : : // hash collision, quadratic probing
146 : 0 : bucket = ( bucket + probe + 1 ) & hashmod;
147 : 0 : }
148 : :
149 : 0 : assert( false && "Hash table is full" ); // unreachable
150 : : return 0;
151 : 0 : }
152 : :
153 : 0 : static void buildPositionRemap( unsigned int *remap, unsigned int *wedge, const float *vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, meshopt_Allocator &allocator )
154 : : {
155 : 0 : PositionHasher hasher = {vertex_positions_data, vertex_positions_stride / sizeof( float )};
156 : :
157 : 0 : size_t table_size = hashBuckets2( vertex_count );
158 : 0 : unsigned int *table = allocator.allocate<unsigned int>( table_size );
159 : 0 : memset( table, -1, table_size * sizeof( unsigned int ) );
160 : :
161 : : // build forward remap: for each vertex, which other (canonical) vertex does it map to?
162 : : // we use position equivalence for this, and remap vertices to other existing vertices
163 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
164 : : {
165 : 0 : unsigned int index = unsigned( i );
166 : 0 : unsigned int *entry = hashLookup2( table, table_size, hasher, index, ~0u );
167 : :
168 : 0 : if ( *entry == ~0u )
169 : 0 : *entry = index;
170 : :
171 : 0 : remap[index] = *entry;
172 : 0 : }
173 : :
174 : : // build wedge table: for each vertex, which other vertex is the next wedge that also maps to the same vertex?
175 : : // entries in table form a (cyclic) wedge loop per vertex; for manifold vertices, wedge[i] == remap[i] == i
176 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
177 : 0 : wedge[i] = unsigned( i );
178 : :
179 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
180 : 0 : if ( remap[i] != i )
181 : : {
182 : 0 : unsigned int r = remap[i];
183 : :
184 : 0 : wedge[i] = wedge[r];
185 : 0 : wedge[r] = unsigned( i );
186 : 0 : }
187 : 0 : }
188 : :
189 : : enum VertexKind
190 : : {
191 : : Kind_Manifold, // not on an attribute seam, not on any boundary
192 : : Kind_Border, // not on an attribute seam, has exactly two open edges
193 : : Kind_Seam, // on an attribute seam with exactly two attribute seam edges
194 : : Kind_Complex, // none of the above; these vertices can move as long as all wedges move to the target vertex
195 : : Kind_Locked, // none of the above; these vertices can't move
196 : :
197 : : Kind_Count
198 : : };
199 : :
200 : : // manifold vertices can collapse onto anything
201 : : // border/seam vertices can only be collapsed onto border/seam respectively
202 : : // complex vertices can collapse onto complex/locked
203 : : // a rule of thumb is that collapsing kind A into kind B preserves the kind B in the target vertex
204 : : // for example, while we could collapse Complex into Manifold, this would mean the target vertex isn't Manifold anymore
205 : : const unsigned char kCanCollapse[Kind_Count][Kind_Count] =
206 : : {
207 : : {1, 1, 1, 1, 1},
208 : : {0, 1, 0, 0, 0},
209 : : {0, 0, 1, 0, 0},
210 : : {0, 0, 0, 1, 1},
211 : : {0, 0, 0, 0, 0},
212 : : };
213 : :
214 : : // if a vertex is manifold or seam, adjoining edges are guaranteed to have an opposite edge
215 : : // note that for seam edges, the opposite edge isn't present in the attribute-based topology
216 : : // but is present if you consider a position-only mesh variant
217 : : const unsigned char kHasOpposite[Kind_Count][Kind_Count] =
218 : : {
219 : : {1, 1, 1, 0, 1},
220 : : {1, 0, 1, 0, 0},
221 : : {1, 1, 1, 0, 1},
222 : : {0, 0, 0, 0, 0},
223 : : {1, 0, 1, 0, 0},
224 : : };
225 : :
226 : 0 : static bool hasEdge( const EdgeAdjacency &adjacency, unsigned int a, unsigned int b )
227 : : {
228 : 0 : unsigned int count = adjacency.counts[a];
229 : 0 : const unsigned int *data = adjacency.data + adjacency.offsets[a];
230 : :
231 : 0 : for ( size_t i = 0; i < count; ++i )
232 : 0 : if ( data[i] == b )
233 : 0 : return true;
234 : :
235 : 0 : return false;
236 : 0 : }
237 : :
238 : 0 : static unsigned int findWedgeEdge( const EdgeAdjacency &adjacency, const unsigned int *wedge, unsigned int a, unsigned int b )
239 : : {
240 : 0 : unsigned int v = a;
241 : :
242 : 0 : do
243 : : {
244 : 0 : if ( hasEdge( adjacency, v, b ) )
245 : 0 : return v;
246 : :
247 : 0 : v = wedge[v];
248 : 0 : }
249 : 0 : while ( v != a );
250 : :
251 : 0 : return ~0u;
252 : 0 : }
253 : :
254 : 0 : static size_t countOpenEdges( const EdgeAdjacency &adjacency, unsigned int vertex, unsigned int *last = 0 )
255 : : {
256 : 0 : size_t result = 0;
257 : :
258 : 0 : unsigned int count = adjacency.counts[vertex];
259 : 0 : const unsigned int *data = adjacency.data + adjacency.offsets[vertex];
260 : :
261 : 0 : for ( size_t i = 0; i < count; ++i )
262 : 0 : if ( !hasEdge( adjacency, data[i], vertex ) )
263 : : {
264 : 0 : result++;
265 : :
266 : 0 : if ( last )
267 : 0 : *last = data[i];
268 : 0 : }
269 : :
270 : 0 : return result;
271 : : }
272 : :
273 : 0 : static void classifyVertices( unsigned char *result, unsigned int *loop, size_t vertex_count, const EdgeAdjacency &adjacency, const unsigned int *remap, const unsigned int *wedge )
274 : : {
275 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
276 : 0 : loop[i] = ~0u;
277 : :
278 : : #if TRACE
279 : : size_t lockedstats[4] = {};
280 : : #define TRACELOCKED(i) lockedstats[i]++;
281 : : #else
282 : : #define TRACELOCKED(i) (void)0
283 : : #endif
284 : :
285 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
286 : : {
287 : 0 : if ( remap[i] == i )
288 : : {
289 : 0 : if ( wedge[i] == i )
290 : : {
291 : : // no attribute seam, need to check if it's manifold
292 : 0 : unsigned int v = 0;
293 : 0 : size_t edges = countOpenEdges( adjacency, unsigned( i ), &v );
294 : :
295 : : // note: we classify any vertices with no open edges as manifold
296 : : // this is technically incorrect - if 4 triangles share an edge, we'll classify vertices as manifold
297 : : // it's unclear if this is a problem in practice
298 : : // also note that we classify vertices as border if they have *one* open edge, not two
299 : : // this is because we only have half-edges - so a border vertex would have one incoming and one outgoing edge
300 : 0 : if ( edges == 0 )
301 : : {
302 : 0 : result[i] = Kind_Manifold;
303 : 0 : }
304 : 0 : else if ( edges == 1 )
305 : : {
306 : 0 : result[i] = Kind_Border;
307 : 0 : loop[i] = v;
308 : 0 : }
309 : : else
310 : : {
311 : 0 : result[i] = Kind_Locked;
312 : : TRACELOCKED( 0 );
313 : : }
314 : 0 : }
315 : 0 : else if ( wedge[wedge[i]] == i )
316 : : {
317 : : // attribute seam; need to distinguish between Seam and Locked
318 : 0 : unsigned int a = 0;
319 : 0 : size_t a_count = countOpenEdges( adjacency, unsigned( i ), &a );
320 : 0 : unsigned int b = 0;
321 : 0 : size_t b_count = countOpenEdges( adjacency, wedge[i], &b );
322 : :
323 : : // seam should have one open half-edge for each vertex, and the edges need to "connect" - point to the same vertex post-remap
324 : 0 : if ( a_count == 1 && b_count == 1 )
325 : : {
326 : 0 : unsigned int ao = findWedgeEdge( adjacency, wedge, a, wedge[i] );
327 : 0 : unsigned int bo = findWedgeEdge( adjacency, wedge, b, unsigned( i ) );
328 : :
329 : 0 : if ( ao != ~0u && bo != ~0u )
330 : : {
331 : 0 : result[i] = Kind_Seam;
332 : :
333 : 0 : loop[i] = a;
334 : 0 : loop[wedge[i]] = b;
335 : 0 : }
336 : : else
337 : : {
338 : 0 : result[i] = Kind_Locked;
339 : : TRACELOCKED( 1 );
340 : : }
341 : 0 : }
342 : : else
343 : : {
344 : 0 : result[i] = Kind_Locked;
345 : : TRACELOCKED( 2 );
346 : : }
347 : 0 : }
348 : : else
349 : : {
350 : : // more than one vertex maps to this one; we don't have classification available
351 : 0 : result[i] = Kind_Locked;
352 : : TRACELOCKED( 3 );
353 : : }
354 : 0 : }
355 : : else
356 : : {
357 : 0 : assert( remap[i] < i );
358 : :
359 : 0 : result[i] = result[remap[i]];
360 : : }
361 : 0 : }
362 : :
363 : : #if TRACE
364 : : printf( "locked: many open edges %d, disconnected seam %d, many seam edges %d, many wedges %d\n",
365 : : int( lockedstats[0] ), int( lockedstats[1] ), int( lockedstats[2] ), int( lockedstats[3] ) );
366 : : #endif
367 : 0 : }
368 : :
369 : : struct Vector3
370 : : {
371 : : float x, y, z;
372 : : };
373 : :
374 : 0 : static void rescalePositions( Vector3 *result, const float *vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride )
375 : : {
376 : 0 : size_t vertex_stride_float = vertex_positions_stride / sizeof( float );
377 : :
378 : 0 : float minv[3] = {FLT_MAX, FLT_MAX, FLT_MAX};
379 : 0 : float maxv[3] = {-FLT_MAX, -FLT_MAX, -FLT_MAX};
380 : :
381 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
382 : : {
383 : 0 : const float *v = vertex_positions_data + i * vertex_stride_float;
384 : :
385 : 0 : result[i].x = v[0];
386 : 0 : result[i].y = v[1];
387 : 0 : result[i].z = v[2];
388 : :
389 : 0 : for ( int j = 0; j < 3; ++j )
390 : : {
391 : 0 : float vj = v[j];
392 : :
393 : 0 : minv[j] = minv[j] > vj ? vj : minv[j];
394 : 0 : maxv[j] = maxv[j] < vj ? vj : maxv[j];
395 : 0 : }
396 : 0 : }
397 : :
398 : 0 : float extent = 0.f;
399 : :
400 : 0 : extent = ( maxv[0] - minv[0] ) < extent ? extent : ( maxv[0] - minv[0] );
401 : 0 : extent = ( maxv[1] - minv[1] ) < extent ? extent : ( maxv[1] - minv[1] );
402 : 0 : extent = ( maxv[2] - minv[2] ) < extent ? extent : ( maxv[2] - minv[2] );
403 : :
404 : 0 : float scale = extent == 0 ? 0.f : 1.f / extent;
405 : :
406 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
407 : : {
408 : 0 : result[i].x = ( result[i].x - minv[0] ) * scale;
409 : 0 : result[i].y = ( result[i].y - minv[1] ) * scale;
410 : 0 : result[i].z = ( result[i].z - minv[2] ) * scale;
411 : 0 : }
412 : 0 : }
413 : :
414 : : struct Quadric
415 : : {
416 : : float a00, a11, a22;
417 : : float a10, a20, a21;
418 : : float b0, b1, b2, c;
419 : : float w;
420 : : };
421 : :
422 : : struct Collapse
423 : : {
424 : : unsigned int v0;
425 : : unsigned int v1;
426 : :
427 : : union
428 : : {
429 : : unsigned int bidi;
430 : : float error;
431 : : unsigned int errorui;
432 : : };
433 : : };
434 : :
435 : 0 : static float normalize( Vector3 &v )
436 : : {
437 : 0 : float length = sqrtf( v.x * v.x + v.y * v.y + v.z * v.z );
438 : :
439 : 0 : if ( length > 0 )
440 : : {
441 : 0 : v.x /= length;
442 : 0 : v.y /= length;
443 : 0 : v.z /= length;
444 : 0 : }
445 : :
446 : 0 : return length;
447 : : }
448 : :
449 : 0 : static void quadricAdd( Quadric &Q, const Quadric &R )
450 : : {
451 : 0 : Q.a00 += R.a00;
452 : 0 : Q.a11 += R.a11;
453 : 0 : Q.a22 += R.a22;
454 : 0 : Q.a10 += R.a10;
455 : 0 : Q.a20 += R.a20;
456 : 0 : Q.a21 += R.a21;
457 : 0 : Q.b0 += R.b0;
458 : 0 : Q.b1 += R.b1;
459 : 0 : Q.b2 += R.b2;
460 : 0 : Q.c += R.c;
461 : 0 : Q.w += R.w;
462 : 0 : }
463 : :
464 : 0 : static float quadricError( const Quadric &Q, const Vector3 &v )
465 : : {
466 : 0 : float rx = Q.b0;
467 : 0 : float ry = Q.b1;
468 : 0 : float rz = Q.b2;
469 : :
470 : 0 : rx += Q.a10 * v.y;
471 : 0 : ry += Q.a21 * v.z;
472 : 0 : rz += Q.a20 * v.x;
473 : :
474 : 0 : rx *= 2;
475 : 0 : ry *= 2;
476 : 0 : rz *= 2;
477 : :
478 : 0 : rx += Q.a00 * v.x;
479 : 0 : ry += Q.a11 * v.y;
480 : 0 : rz += Q.a22 * v.z;
481 : :
482 : 0 : float r = Q.c;
483 : 0 : r += rx * v.x;
484 : 0 : r += ry * v.y;
485 : 0 : r += rz * v.z;
486 : :
487 : 0 : float s = Q.w == 0.f ? 0.f : 1.f / Q.w;
488 : :
489 : 0 : return fabsf( r ) * s;
490 : : }
491 : :
492 : 0 : static void quadricFromPlane( Quadric &Q, float a, float b, float c, float d, float w )
493 : : {
494 : 0 : float aw = a * w;
495 : 0 : float bw = b * w;
496 : 0 : float cw = c * w;
497 : 0 : float dw = d * w;
498 : :
499 : 0 : Q.a00 = a * aw;
500 : 0 : Q.a11 = b * bw;
501 : 0 : Q.a22 = c * cw;
502 : 0 : Q.a10 = a * bw;
503 : 0 : Q.a20 = a * cw;
504 : 0 : Q.a21 = b * cw;
505 : 0 : Q.b0 = a * dw;
506 : 0 : Q.b1 = b * dw;
507 : 0 : Q.b2 = c * dw;
508 : 0 : Q.c = d * dw;
509 : 0 : Q.w = w;
510 : 0 : }
511 : :
512 : 0 : static void quadricFromPoint( Quadric &Q, float x, float y, float z, float w )
513 : : {
514 : : // we need to encode (x - X) ^ 2 + (y - Y)^2 + (z - Z)^2 into the quadric
515 : 0 : Q.a00 = w;
516 : 0 : Q.a11 = w;
517 : 0 : Q.a22 = w;
518 : 0 : Q.a10 = 0.f;
519 : 0 : Q.a20 = 0.f;
520 : 0 : Q.a21 = 0.f;
521 : 0 : Q.b0 = -2.f * x * w;
522 : 0 : Q.b1 = -2.f * y * w;
523 : 0 : Q.b2 = -2.f * z * w;
524 : 0 : Q.c = ( x * x + y * y + z * z ) * w;
525 : 0 : Q.w = w;
526 : 0 : }
527 : :
528 : 0 : static void quadricFromTriangle( Quadric &Q, const Vector3 &p0, const Vector3 &p1, const Vector3 &p2, float weight )
529 : : {
530 : 0 : Vector3 p10 = {p1.x - p0.x, p1.y - p0.y, p1.z - p0.z};
531 : 0 : Vector3 p20 = {p2.x - p0.x, p2.y - p0.y, p2.z - p0.z};
532 : :
533 : : // normal = cross(p1 - p0, p2 - p0)
534 : 0 : Vector3 normal = {p10.y *p20.z - p10.z * p20.y, p10.z *p20.x - p10.x * p20.z, p10.x *p20.y - p10.y * p20.x};
535 : 0 : float area = normalize( normal );
536 : :
537 : 0 : float distance = normal.x * p0.x + normal.y * p0.y + normal.z * p0.z;
538 : :
539 : : // we use sqrtf(area) so that the error is scaled linearly; this tends to improve silhouettes
540 : 0 : quadricFromPlane( Q, normal.x, normal.y, normal.z, -distance, sqrtf( area ) * weight );
541 : 0 : }
542 : :
543 : 0 : static void quadricFromTriangleEdge( Quadric &Q, const Vector3 &p0, const Vector3 &p1, const Vector3 &p2, float weight )
544 : : {
545 : 0 : Vector3 p10 = {p1.x - p0.x, p1.y - p0.y, p1.z - p0.z};
546 : 0 : float length = normalize( p10 );
547 : :
548 : : // p20p = length of projection of p2-p0 onto normalize(p1 - p0)
549 : 0 : Vector3 p20 = {p2.x - p0.x, p2.y - p0.y, p2.z - p0.z};
550 : 0 : float p20p = p20.x * p10.x + p20.y * p10.y + p20.z * p10.z;
551 : :
552 : : // normal = altitude of triangle from point p2 onto edge p1-p0
553 : 0 : Vector3 normal = {p20.x - p10.x * p20p, p20.y - p10.y * p20p, p20.z - p10.z * p20p};
554 : 0 : normalize( normal );
555 : :
556 : 0 : float distance = normal.x * p0.x + normal.y * p0.y + normal.z * p0.z;
557 : :
558 : : // note: the weight is scaled linearly with edge length; this has to match the triangle weight
559 : 0 : quadricFromPlane( Q, normal.x, normal.y, normal.z, -distance, length * weight );
560 : 0 : }
561 : :
562 : 0 : static void fillFaceQuadrics( Quadric *vertex_quadrics, const unsigned int *indices, size_t index_count, const Vector3 *vertex_positions, const unsigned int *remap )
563 : : {
564 : 0 : for ( size_t i = 0; i < index_count; i += 3 )
565 : : {
566 : 0 : unsigned int i0 = indices[i + 0];
567 : 0 : unsigned int i1 = indices[i + 1];
568 : 0 : unsigned int i2 = indices[i + 2];
569 : :
570 : : Quadric Q;
571 : 0 : quadricFromTriangle( Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], 1.f );
572 : :
573 : 0 : quadricAdd( vertex_quadrics[remap[i0]], Q );
574 : 0 : quadricAdd( vertex_quadrics[remap[i1]], Q );
575 : 0 : quadricAdd( vertex_quadrics[remap[i2]], Q );
576 : 0 : }
577 : 0 : }
578 : :
579 : 0 : static void fillEdgeQuadrics( Quadric *vertex_quadrics, const unsigned int *indices, size_t index_count, const Vector3 *vertex_positions, const unsigned int *remap, const unsigned char *vertex_kind, const unsigned int *loop )
580 : : {
581 : 0 : for ( size_t i = 0; i < index_count; i += 3 )
582 : : {
583 : : static const int next[3] = {1, 2, 0};
584 : :
585 : 0 : for ( int e = 0; e < 3; ++e )
586 : : {
587 : 0 : unsigned int i0 = indices[i + e];
588 : 0 : unsigned int i1 = indices[i + next[e]];
589 : :
590 : 0 : unsigned char k0 = vertex_kind[i0];
591 : 0 : unsigned char k1 = vertex_kind[i1];
592 : :
593 : : // check that i0 and i1 are border/seam and are on the same edge loop
594 : : // loop[] tracks half edges so we only need to check i0->i1
595 : 0 : if ( k0 != k1 || ( k0 != Kind_Border && k0 != Kind_Seam ) || loop[i0] != i1 )
596 : 0 : continue;
597 : :
598 : 0 : unsigned int i2 = indices[i + next[next[e]]];
599 : :
600 : : // we try hard to maintain border edge geometry; seam edges can move more freely
601 : : // due to topological restrictions on collapses, seam quadrics slightly improves collapse structure but aren't critical
602 : 0 : const float kEdgeWeightSeam = 1.f;
603 : 0 : const float kEdgeWeightBorder = 10.f;
604 : :
605 : 0 : float edgeWeight = ( k0 == Kind_Seam ) ? kEdgeWeightSeam : kEdgeWeightBorder;
606 : :
607 : : Quadric Q;
608 : 0 : quadricFromTriangleEdge( Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], edgeWeight );
609 : :
610 : 0 : quadricAdd( vertex_quadrics[remap[i0]], Q );
611 : 0 : quadricAdd( vertex_quadrics[remap[i1]], Q );
612 : 0 : }
613 : 0 : }
614 : 0 : }
615 : :
616 : 0 : static size_t pickEdgeCollapses( Collapse *collapses, const unsigned int *indices, size_t index_count, const unsigned int *remap, const unsigned char *vertex_kind, const unsigned int *loop )
617 : : {
618 : 0 : size_t collapse_count = 0;
619 : :
620 : 0 : for ( size_t i = 0; i < index_count; i += 3 )
621 : : {
622 : : static const int next[3] = {1, 2, 0};
623 : :
624 : 0 : for ( int e = 0; e < 3; ++e )
625 : : {
626 : 0 : unsigned int i0 = indices[i + e];
627 : 0 : unsigned int i1 = indices[i + next[e]];
628 : :
629 : : // this can happen either when input has a zero-length edge, or when we perform collapses for complex
630 : : // topology w/seams and collapse a manifold vertex that connects to both wedges onto one of them
631 : : // we leave edges like this alone since they may be important for preserving mesh integrity
632 : 0 : if ( remap[i0] == remap[i1] )
633 : 0 : continue;
634 : :
635 : 0 : unsigned char k0 = vertex_kind[i0];
636 : 0 : unsigned char k1 = vertex_kind[i1];
637 : :
638 : : // the edge has to be collapsible in at least one direction
639 : 0 : if ( !( kCanCollapse[k0][k1] | kCanCollapse[k1][k0] ) )
640 : 0 : continue;
641 : :
642 : : // manifold and seam edges should occur twice (i0->i1 and i1->i0) - skip redundant edges
643 : 0 : if ( kHasOpposite[k0][k1] && remap[i1] > remap[i0] )
644 : 0 : continue;
645 : :
646 : : // two vertices are on a border or a seam, but there's no direct edge between them
647 : : // this indicates that they belong to two different edge loops and we should not collapse this edge
648 : : // loop[] tracks half edges so we only need to check i0->i1
649 : 0 : if ( k0 == k1 && ( k0 == Kind_Border || k0 == Kind_Seam ) && loop[i0] != i1 )
650 : 0 : continue;
651 : :
652 : : // edge can be collapsed in either direction - we will pick the one with minimum error
653 : : // note: we evaluate error later during collapse ranking, here we just tag the edge as bidirectional
654 : 0 : if ( kCanCollapse[k0][k1] & kCanCollapse[k1][k0] )
655 : : {
656 : 0 : Collapse c = {i0, i1, {/* bidi= */ 1}};
657 : 0 : collapses[collapse_count++] = c;
658 : 0 : }
659 : : else
660 : : {
661 : : // edge can only be collapsed in one direction
662 : 0 : unsigned int e0 = kCanCollapse[k0][k1] ? i0 : i1;
663 : 0 : unsigned int e1 = kCanCollapse[k0][k1] ? i1 : i0;
664 : :
665 : 0 : Collapse c = {e0, e1, {/* bidi= */ 0}};
666 : 0 : collapses[collapse_count++] = c;
667 : : }
668 : 0 : }
669 : 0 : }
670 : :
671 : 0 : return collapse_count;
672 : : }
673 : :
674 : 0 : static void rankEdgeCollapses( Collapse *collapses, size_t collapse_count, const Vector3 *vertex_positions, const Quadric *vertex_quadrics, const unsigned int *remap )
675 : : {
676 : 0 : for ( size_t i = 0; i < collapse_count; ++i )
677 : : {
678 : 0 : Collapse &c = collapses[i];
679 : :
680 : 0 : unsigned int i0 = c.v0;
681 : 0 : unsigned int i1 = c.v1;
682 : :
683 : : // most edges are bidirectional which means we need to evaluate errors for two collapses
684 : : // to keep this code branchless we just use the same edge for unidirectional edges
685 : 0 : unsigned int j0 = c.bidi ? i1 : i0;
686 : 0 : unsigned int j1 = c.bidi ? i0 : i1;
687 : :
688 : 0 : const Quadric &qi = vertex_quadrics[remap[i0]];
689 : 0 : const Quadric &qj = vertex_quadrics[remap[j0]];
690 : :
691 : 0 : float ei = quadricError( qi, vertex_positions[i1] );
692 : 0 : float ej = quadricError( qj, vertex_positions[j1] );
693 : :
694 : : // pick edge direction with minimal error
695 : 0 : c.v0 = ei <= ej ? i0 : j0;
696 : 0 : c.v1 = ei <= ej ? i1 : j1;
697 : 0 : c.error = ei <= ej ? ei : ej;
698 : 0 : }
699 : 0 : }
700 : :
701 : : #if TRACE > 1
702 : : static void dumpEdgeCollapses( const Collapse *collapses, size_t collapse_count, const unsigned char *vertex_kind )
703 : : {
704 : : size_t ckinds[Kind_Count][Kind_Count] = {};
705 : : float cerrors[Kind_Count][Kind_Count] = {};
706 : :
707 : : for ( int k0 = 0; k0 < Kind_Count; ++k0 )
708 : : for ( int k1 = 0; k1 < Kind_Count; ++k1 )
709 : : cerrors[k0][k1] = FLT_MAX;
710 : :
711 : : for ( size_t i = 0; i < collapse_count; ++i )
712 : : {
713 : : unsigned int i0 = collapses[i].v0;
714 : : unsigned int i1 = collapses[i].v1;
715 : :
716 : : unsigned char k0 = vertex_kind[i0];
717 : : unsigned char k1 = vertex_kind[i1];
718 : :
719 : : ckinds[k0][k1]++;
720 : : cerrors[k0][k1] = ( collapses[i].error < cerrors[k0][k1] ) ? collapses[i].error : cerrors[k0][k1];
721 : : }
722 : :
723 : : for ( int k0 = 0; k0 < Kind_Count; ++k0 )
724 : : for ( int k1 = 0; k1 < Kind_Count; ++k1 )
725 : : if ( ckinds[k0][k1] )
726 : : printf( "collapses %d -> %d: %d, min error %e\n", k0, k1, int( ckinds[k0][k1] ), cerrors[k0][k1] );
727 : : }
728 : :
729 : : static void dumpLockedCollapses( const unsigned int *indices, size_t index_count, const unsigned char *vertex_kind )
730 : : {
731 : : size_t locked_collapses[Kind_Count][Kind_Count] = {};
732 : :
733 : : for ( size_t i = 0; i < index_count; i += 3 )
734 : : {
735 : : static const int next[3] = {1, 2, 0};
736 : :
737 : : for ( int e = 0; e < 3; ++e )
738 : : {
739 : : unsigned int i0 = indices[i + e];
740 : : unsigned int i1 = indices[i + next[e]];
741 : :
742 : : unsigned char k0 = vertex_kind[i0];
743 : : unsigned char k1 = vertex_kind[i1];
744 : :
745 : : locked_collapses[k0][k1] += !kCanCollapse[k0][k1] && !kCanCollapse[k1][k0];
746 : : }
747 : : }
748 : :
749 : : for ( int k0 = 0; k0 < Kind_Count; ++k0 )
750 : : for ( int k1 = 0; k1 < Kind_Count; ++k1 )
751 : : if ( locked_collapses[k0][k1] )
752 : : printf( "locked collapses %d -> %d: %d\n", k0, k1, int( locked_collapses[k0][k1] ) );
753 : : }
754 : : #endif
755 : :
756 : 0 : static void sortEdgeCollapses( unsigned int *sort_order, const Collapse *collapses, size_t collapse_count )
757 : : {
758 : 0 : const int sort_bits = 11;
759 : :
760 : : // fill histogram for counting sort
761 : : unsigned int histogram[1 << sort_bits];
762 : 0 : memset( histogram, 0, sizeof( histogram ) );
763 : :
764 : 0 : for ( size_t i = 0; i < collapse_count; ++i )
765 : : {
766 : : // skip sign bit since error is non-negative
767 : 0 : unsigned int key = ( collapses[i].errorui << 1 ) >> ( 32 - sort_bits );
768 : :
769 : 0 : histogram[key]++;
770 : 0 : }
771 : :
772 : : // compute offsets based on histogram data
773 : 0 : size_t histogram_sum = 0;
774 : :
775 : 0 : for ( size_t i = 0; i < 1 << sort_bits; ++i )
776 : : {
777 : 0 : size_t count = histogram[i];
778 : 0 : histogram[i] = unsigned( histogram_sum );
779 : 0 : histogram_sum += count;
780 : 0 : }
781 : :
782 : 0 : assert( histogram_sum == collapse_count );
783 : :
784 : : // compute sort order based on offsets
785 : 0 : for ( size_t i = 0; i < collapse_count; ++i )
786 : : {
787 : : // skip sign bit since error is non-negative
788 : 0 : unsigned int key = ( collapses[i].errorui << 1 ) >> ( 32 - sort_bits );
789 : :
790 : 0 : sort_order[histogram[key]++] = unsigned( i );
791 : 0 : }
792 : 0 : }
793 : :
794 : 0 : static size_t performEdgeCollapses( unsigned int *collapse_remap, unsigned char *collapse_locked, Quadric *vertex_quadrics, const Collapse *collapses, size_t collapse_count, const unsigned int *collapse_order, const unsigned int *remap, const unsigned int *wedge, const unsigned char *vertex_kind, size_t triangle_collapse_goal, float error_goal, float error_limit )
795 : : {
796 : 0 : size_t edge_collapses = 0;
797 : 0 : size_t triangle_collapses = 0;
798 : :
799 : 0 : for ( size_t i = 0; i < collapse_count; ++i )
800 : : {
801 : 0 : const Collapse &c = collapses[collapse_order[i]];
802 : :
803 : 0 : if ( c.error > error_limit )
804 : 0 : break;
805 : :
806 : 0 : if ( c.error > error_goal && triangle_collapses > triangle_collapse_goal / 10 )
807 : 0 : break;
808 : :
809 : 0 : if ( triangle_collapses >= triangle_collapse_goal )
810 : 0 : break;
811 : :
812 : 0 : unsigned int i0 = c.v0;
813 : 0 : unsigned int i1 = c.v1;
814 : :
815 : 0 : unsigned int r0 = remap[i0];
816 : 0 : unsigned int r1 = remap[i1];
817 : :
818 : : // we don't collapse vertices that had source or target vertex involved in a collapse
819 : : // it's important to not move the vertices twice since it complicates the tracking/remapping logic
820 : : // it's important to not move other vertices towards a moved vertex to preserve error since we don't re-rank collapses mid-pass
821 : 0 : if ( collapse_locked[r0] | collapse_locked[r1] )
822 : 0 : continue;
823 : :
824 : 0 : assert( collapse_remap[r0] == r0 );
825 : 0 : assert( collapse_remap[r1] == r1 );
826 : :
827 : 0 : quadricAdd( vertex_quadrics[r1], vertex_quadrics[r0] );
828 : :
829 : 0 : if ( vertex_kind[i0] == Kind_Complex )
830 : : {
831 : 0 : unsigned int v = i0;
832 : :
833 : 0 : do
834 : : {
835 : 0 : collapse_remap[v] = r1;
836 : 0 : v = wedge[v];
837 : 0 : }
838 : 0 : while ( v != i0 );
839 : 0 : }
840 : 0 : else if ( vertex_kind[i0] == Kind_Seam )
841 : : {
842 : : // remap v0 to v1 and seam pair of v0 to seam pair of v1
843 : 0 : unsigned int s0 = wedge[i0];
844 : 0 : unsigned int s1 = wedge[i1];
845 : :
846 : 0 : assert( s0 != i0 && s1 != i1 );
847 : 0 : assert( wedge[s0] == i0 && wedge[s1] == i1 );
848 : :
849 : 0 : collapse_remap[i0] = i1;
850 : 0 : collapse_remap[s0] = s1;
851 : 0 : }
852 : : else
853 : : {
854 : 0 : assert( wedge[i0] == i0 );
855 : :
856 : 0 : collapse_remap[i0] = i1;
857 : : }
858 : :
859 : 0 : collapse_locked[r0] = 1;
860 : 0 : collapse_locked[r1] = 1;
861 : :
862 : : // border edges collapse 1 triangle, other edges collapse 2 or more
863 : 0 : triangle_collapses += ( vertex_kind[i0] == Kind_Border ) ? 1 : 2;
864 : 0 : edge_collapses++;
865 : 0 : }
866 : :
867 : 0 : return edge_collapses;
868 : : }
869 : :
870 : 0 : static size_t remapIndexBuffer( unsigned int *indices, size_t index_count, const unsigned int *collapse_remap )
871 : : {
872 : 0 : size_t write = 0;
873 : :
874 : 0 : for ( size_t i = 0; i < index_count; i += 3 )
875 : : {
876 : 0 : unsigned int v0 = collapse_remap[indices[i + 0]];
877 : 0 : unsigned int v1 = collapse_remap[indices[i + 1]];
878 : 0 : unsigned int v2 = collapse_remap[indices[i + 2]];
879 : :
880 : : // we never move the vertex twice during a single pass
881 : 0 : assert( collapse_remap[v0] == v0 );
882 : 0 : assert( collapse_remap[v1] == v1 );
883 : 0 : assert( collapse_remap[v2] == v2 );
884 : :
885 : 0 : if ( v0 != v1 && v0 != v2 && v1 != v2 )
886 : : {
887 : 0 : indices[write + 0] = v0;
888 : 0 : indices[write + 1] = v1;
889 : 0 : indices[write + 2] = v2;
890 : 0 : write += 3;
891 : 0 : }
892 : 0 : }
893 : :
894 : 0 : return write;
895 : : }
896 : :
897 : 0 : static void remapEdgeLoops( unsigned int *loop, size_t vertex_count, const unsigned int *collapse_remap )
898 : : {
899 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
900 : : {
901 : 0 : if ( loop[i] != ~0u )
902 : : {
903 : 0 : unsigned int l = loop[i];
904 : 0 : unsigned int r = collapse_remap[l];
905 : :
906 : : // i == r is a special case when the seam edge is collapsed in a direction opposite to where loop goes
907 : 0 : loop[i] = ( i == r ) ? loop[l] : r;
908 : 0 : }
909 : 0 : }
910 : 0 : }
911 : :
912 : : struct CellHasher
913 : : {
914 : : const unsigned int *vertex_ids;
915 : :
916 : 0 : size_t hash( unsigned int i ) const
917 : : {
918 : 0 : unsigned int h = vertex_ids[i];
919 : :
920 : : // MurmurHash2 finalizer
921 : 0 : h ^= h >> 13;
922 : 0 : h *= 0x5bd1e995;
923 : 0 : h ^= h >> 15;
924 : 0 : return h;
925 : : }
926 : :
927 : 0 : bool equal( unsigned int lhs, unsigned int rhs ) const
928 : : {
929 : 0 : return vertex_ids[lhs] == vertex_ids[rhs];
930 : : }
931 : : };
932 : :
933 : : struct IdHasher
934 : : {
935 : 0 : size_t hash( unsigned int id ) const
936 : : {
937 : 0 : unsigned int h = id;
938 : :
939 : : // MurmurHash2 finalizer
940 : 0 : h ^= h >> 13;
941 : 0 : h *= 0x5bd1e995;
942 : 0 : h ^= h >> 15;
943 : 0 : return h;
944 : : }
945 : :
946 : 0 : bool equal( unsigned int lhs, unsigned int rhs ) const
947 : : {
948 : 0 : return lhs == rhs;
949 : : }
950 : : };
951 : :
952 : : struct TriangleHasher
953 : : {
954 : : unsigned int *indices;
955 : :
956 : 0 : size_t hash( unsigned int i ) const
957 : : {
958 : 0 : const unsigned int *tri = indices + i * 3;
959 : :
960 : : // Optimized Spatial Hashing for Collision Detection of Deformable Objects
961 : 0 : return ( tri[0] * 73856093 ) ^ ( tri[1] * 19349663 ) ^ ( tri[2] * 83492791 );
962 : : }
963 : :
964 : 0 : bool equal( unsigned int lhs, unsigned int rhs ) const
965 : : {
966 : 0 : const unsigned int *lt = indices + lhs * 3;
967 : 0 : const unsigned int *rt = indices + rhs * 3;
968 : :
969 : 0 : return lt[0] == rt[0] && lt[1] == rt[1] && lt[2] == rt[2];
970 : : }
971 : : };
972 : :
973 : 0 : static void computeVertexIds( unsigned int *vertex_ids, const Vector3 *vertex_positions, size_t vertex_count, int grid_size )
974 : : {
975 : 0 : assert( grid_size >= 1 && grid_size <= 1024 );
976 : 0 : float cell_scale = float( grid_size - 1 );
977 : :
978 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
979 : : {
980 : 0 : const Vector3 &v = vertex_positions[i];
981 : :
982 : 0 : int xi = int( v.x * cell_scale + 0.5f );
983 : 0 : int yi = int( v.y * cell_scale + 0.5f );
984 : 0 : int zi = int( v.z * cell_scale + 0.5f );
985 : :
986 : 0 : vertex_ids[i] = ( xi << 20 ) | ( yi << 10 ) | zi;
987 : 0 : }
988 : 0 : }
989 : :
990 : 0 : static size_t countTriangles( const unsigned int *vertex_ids, const unsigned int *indices, size_t index_count )
991 : : {
992 : 0 : size_t result = 0;
993 : :
994 : 0 : for ( size_t i = 0; i < index_count; i += 3 )
995 : : {
996 : 0 : unsigned int id0 = vertex_ids[indices[i + 0]];
997 : 0 : unsigned int id1 = vertex_ids[indices[i + 1]];
998 : 0 : unsigned int id2 = vertex_ids[indices[i + 2]];
999 : :
1000 : 0 : result += ( id0 != id1 ) & ( id0 != id2 ) & ( id1 != id2 );
1001 : 0 : }
1002 : :
1003 : 0 : return result;
1004 : : }
1005 : :
1006 : 0 : static size_t fillVertexCells( unsigned int *table, size_t table_size, unsigned int *vertex_cells, const unsigned int *vertex_ids, size_t vertex_count )
1007 : : {
1008 : 0 : CellHasher hasher = {vertex_ids};
1009 : :
1010 : 0 : memset( table, -1, table_size * sizeof( unsigned int ) );
1011 : :
1012 : 0 : size_t result = 0;
1013 : :
1014 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
1015 : : {
1016 : 0 : unsigned int *entry = hashLookup2( table, table_size, hasher, unsigned( i ), ~0u );
1017 : :
1018 : 0 : if ( *entry == ~0u )
1019 : : {
1020 : 0 : *entry = unsigned( i );
1021 : 0 : vertex_cells[i] = unsigned( result++ );
1022 : 0 : }
1023 : : else
1024 : : {
1025 : 0 : vertex_cells[i] = vertex_cells[*entry];
1026 : : }
1027 : 0 : }
1028 : :
1029 : 0 : return result;
1030 : : }
1031 : :
1032 : 0 : static size_t countVertexCells( unsigned int *table, size_t table_size, const unsigned int *vertex_ids, size_t vertex_count )
1033 : : {
1034 : : IdHasher hasher;
1035 : :
1036 : 0 : memset( table, -1, table_size * sizeof( unsigned int ) );
1037 : :
1038 : 0 : size_t result = 0;
1039 : :
1040 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
1041 : : {
1042 : 0 : unsigned int id = vertex_ids[i];
1043 : 0 : unsigned int *entry = hashLookup2( table, table_size, hasher, id, ~0u );
1044 : :
1045 : 0 : result += ( *entry == ~0u );
1046 : 0 : *entry = id;
1047 : 0 : }
1048 : :
1049 : 0 : return result;
1050 : : }
1051 : :
1052 : 0 : static void fillCellQuadrics( Quadric *cell_quadrics, const unsigned int *indices, size_t index_count, const Vector3 *vertex_positions, const unsigned int *vertex_cells )
1053 : : {
1054 : 0 : for ( size_t i = 0; i < index_count; i += 3 )
1055 : : {
1056 : 0 : unsigned int i0 = indices[i + 0];
1057 : 0 : unsigned int i1 = indices[i + 1];
1058 : 0 : unsigned int i2 = indices[i + 2];
1059 : :
1060 : 0 : unsigned int c0 = vertex_cells[i0];
1061 : 0 : unsigned int c1 = vertex_cells[i1];
1062 : 0 : unsigned int c2 = vertex_cells[i2];
1063 : :
1064 : 0 : bool single_cell = ( c0 == c1 ) & ( c0 == c2 );
1065 : :
1066 : : Quadric Q;
1067 : 0 : quadricFromTriangle( Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], single_cell ? 3.f : 1.f );
1068 : :
1069 : 0 : if ( single_cell )
1070 : : {
1071 : 0 : quadricAdd( cell_quadrics[c0], Q );
1072 : 0 : }
1073 : : else
1074 : : {
1075 : 0 : quadricAdd( cell_quadrics[c0], Q );
1076 : 0 : quadricAdd( cell_quadrics[c1], Q );
1077 : 0 : quadricAdd( cell_quadrics[c2], Q );
1078 : : }
1079 : 0 : }
1080 : 0 : }
1081 : :
1082 : 0 : static void fillCellQuadrics( Quadric *cell_quadrics, const Vector3 *vertex_positions, size_t vertex_count, const unsigned int *vertex_cells )
1083 : : {
1084 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
1085 : : {
1086 : 0 : unsigned int c = vertex_cells[i];
1087 : 0 : const Vector3 &v = vertex_positions[i];
1088 : :
1089 : : Quadric Q;
1090 : 0 : quadricFromPoint( Q, v.x, v.y, v.z, 1.f );
1091 : :
1092 : 0 : quadricAdd( cell_quadrics[c], Q );
1093 : 0 : }
1094 : 0 : }
1095 : :
1096 : 0 : static void fillCellRemap( unsigned int *cell_remap, float *cell_errors, size_t cell_count, const unsigned int *vertex_cells, const Quadric *cell_quadrics, const Vector3 *vertex_positions, size_t vertex_count )
1097 : : {
1098 : 0 : memset( cell_remap, -1, cell_count * sizeof( unsigned int ) );
1099 : :
1100 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
1101 : : {
1102 : 0 : unsigned int cell = vertex_cells[i];
1103 : 0 : float error = quadricError( cell_quadrics[cell], vertex_positions[i] );
1104 : :
1105 : 0 : if ( cell_remap[cell] == ~0u || cell_errors[cell] > error )
1106 : : {
1107 : 0 : cell_remap[cell] = unsigned( i );
1108 : 0 : cell_errors[cell] = error;
1109 : 0 : }
1110 : 0 : }
1111 : 0 : }
1112 : :
1113 : 0 : static size_t filterTriangles( unsigned int *destination, unsigned int *tritable, size_t tritable_size, const unsigned int *indices, size_t index_count, const unsigned int *vertex_cells, const unsigned int *cell_remap )
1114 : : {
1115 : 0 : TriangleHasher hasher = {destination};
1116 : :
1117 : 0 : memset( tritable, -1, tritable_size * sizeof( unsigned int ) );
1118 : :
1119 : 0 : size_t result = 0;
1120 : :
1121 : 0 : for ( size_t i = 0; i < index_count; i += 3 )
1122 : : {
1123 : 0 : unsigned int c0 = vertex_cells[indices[i + 0]];
1124 : 0 : unsigned int c1 = vertex_cells[indices[i + 1]];
1125 : 0 : unsigned int c2 = vertex_cells[indices[i + 2]];
1126 : :
1127 : 0 : if ( c0 != c1 && c0 != c2 && c1 != c2 )
1128 : : {
1129 : 0 : unsigned int a = cell_remap[c0];
1130 : 0 : unsigned int b = cell_remap[c1];
1131 : 0 : unsigned int c = cell_remap[c2];
1132 : :
1133 : 0 : if ( b < a && b < c )
1134 : : {
1135 : 0 : unsigned int t = a;
1136 : 0 : a = b, b = c, c = t;
1137 : 0 : }
1138 : 0 : else if ( c < a && c < b )
1139 : : {
1140 : 0 : unsigned int t = c;
1141 : 0 : c = b, b = a, a = t;
1142 : 0 : }
1143 : :
1144 : 0 : destination[result * 3 + 0] = a;
1145 : 0 : destination[result * 3 + 1] = b;
1146 : 0 : destination[result * 3 + 2] = c;
1147 : :
1148 : 0 : unsigned int *entry = hashLookup2( tritable, tritable_size, hasher, unsigned( result ), ~0u );
1149 : :
1150 : 0 : if ( *entry == ~0u )
1151 : 0 : *entry = unsigned( result++ );
1152 : 0 : }
1153 : 0 : }
1154 : :
1155 : 0 : return result * 3;
1156 : : }
1157 : :
1158 : 0 : static float interpolate( float y, float x0, float y0, float x1, float y1, float x2, float y2 )
1159 : : {
1160 : : // three point interpolation from "revenge of interpolation search" paper
1161 : 0 : float num = ( y1 - y ) * ( x1 - x2 ) * ( x1 - x0 ) * ( y2 - y0 );
1162 : 0 : float den = ( y2 - y ) * ( x1 - x2 ) * ( y0 - y1 ) + ( y0 - y ) * ( x1 - x0 ) * ( y1 - y2 );
1163 : 0 : return x1 + num / den;
1164 : : }
1165 : :
1166 : : } // namespace meshopt
1167 : :
1168 : : #if TRACE
1169 : : unsigned char *meshopt_simplifyDebugKind = 0;
1170 : : unsigned int *meshopt_simplifyDebugLoop = 0;
1171 : : #endif
1172 : :
1173 : 0 : size_t meshopt_simplify( unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error )
1174 : : {
1175 : : using namespace meshopt;
1176 : :
1177 : 0 : assert( index_count % 3 == 0 );
1178 : 0 : assert( vertex_positions_stride > 0 && vertex_positions_stride <= 256 );
1179 : 0 : assert( vertex_positions_stride % sizeof( float ) == 0 );
1180 : 0 : assert( target_index_count <= index_count );
1181 : :
1182 : 0 : meshopt_Allocator allocator;
1183 : :
1184 : 0 : unsigned int *result = destination;
1185 : :
1186 : : // build adjacency information
1187 : 0 : EdgeAdjacency adjacency = {};
1188 : 0 : buildEdgeAdjacency( adjacency, indices, index_count, vertex_count, allocator );
1189 : :
1190 : : // build position remap that maps each vertex to the one with identical position
1191 : 0 : unsigned int *remap = allocator.allocate<unsigned int>( vertex_count );
1192 : 0 : unsigned int *wedge = allocator.allocate<unsigned int>( vertex_count );
1193 : 0 : buildPositionRemap( remap, wedge, vertex_positions_data, vertex_count, vertex_positions_stride, allocator );
1194 : :
1195 : : // classify vertices; vertex kind determines collapse rules, see kCanCollapse
1196 : 0 : unsigned char *vertex_kind = allocator.allocate<unsigned char>( vertex_count );
1197 : 0 : unsigned int *loop = allocator.allocate<unsigned int>( vertex_count );
1198 : 0 : classifyVertices( vertex_kind, loop, vertex_count, adjacency, remap, wedge );
1199 : :
1200 : : #if TRACE
1201 : : size_t unique_positions = 0;
1202 : : for ( size_t i = 0; i < vertex_count; ++i )
1203 : : unique_positions += remap[i] == i;
1204 : :
1205 : : printf( "position remap: %d vertices => %d positions\n", int( vertex_count ), int( unique_positions ) );
1206 : :
1207 : : size_t kinds[Kind_Count] = {};
1208 : : for ( size_t i = 0; i < vertex_count; ++i )
1209 : : kinds[vertex_kind[i]] += remap[i] == i;
1210 : :
1211 : : printf( "kinds: manifold %d, border %d, seam %d, complex %d, locked %d\n",
1212 : : int( kinds[Kind_Manifold] ), int( kinds[Kind_Border] ), int( kinds[Kind_Seam] ), int( kinds[Kind_Complex] ), int( kinds[Kind_Locked] ) );
1213 : : #endif
1214 : :
1215 : 0 : Vector3 *vertex_positions = allocator.allocate<Vector3>( vertex_count );
1216 : 0 : rescalePositions( vertex_positions, vertex_positions_data, vertex_count, vertex_positions_stride );
1217 : :
1218 : 0 : Quadric *vertex_quadrics = allocator.allocate<Quadric>( vertex_count );
1219 : 0 : memset( vertex_quadrics, 0, vertex_count * sizeof( Quadric ) );
1220 : :
1221 : 0 : fillFaceQuadrics( vertex_quadrics, indices, index_count, vertex_positions, remap );
1222 : 0 : fillEdgeQuadrics( vertex_quadrics, indices, index_count, vertex_positions, remap, vertex_kind, loop );
1223 : :
1224 : 0 : if ( result != indices )
1225 : 0 : memcpy( result, indices, index_count * sizeof( unsigned int ) );
1226 : :
1227 : : #if TRACE
1228 : : size_t pass_count = 0;
1229 : : float worst_error = 0;
1230 : : #endif
1231 : :
1232 : 0 : Collapse *edge_collapses = allocator.allocate<Collapse>( index_count );
1233 : 0 : unsigned int *collapse_order = allocator.allocate<unsigned int>( index_count );
1234 : 0 : unsigned int *collapse_remap = allocator.allocate<unsigned int>( vertex_count );
1235 : 0 : unsigned char *collapse_locked = allocator.allocate<unsigned char>( vertex_count );
1236 : :
1237 : 0 : size_t result_count = index_count;
1238 : :
1239 : : // target_error input is linear; we need to adjust it to match quadricError units
1240 : 0 : float error_limit = target_error * target_error;
1241 : :
1242 : 0 : while ( result_count > target_index_count )
1243 : : {
1244 : 0 : size_t edge_collapse_count = pickEdgeCollapses( edge_collapses, result, result_count, remap, vertex_kind, loop );
1245 : :
1246 : : // no edges can be collapsed any more due to topology restrictions
1247 : 0 : if ( edge_collapse_count == 0 )
1248 : 0 : break;
1249 : :
1250 : 0 : rankEdgeCollapses( edge_collapses, edge_collapse_count, vertex_positions, vertex_quadrics, remap );
1251 : :
1252 : : #if TRACE > 1
1253 : : dumpEdgeCollapses( edge_collapses, edge_collapse_count, vertex_kind );
1254 : : #endif
1255 : :
1256 : 0 : sortEdgeCollapses( collapse_order, edge_collapses, edge_collapse_count );
1257 : :
1258 : : // most collapses remove 2 triangles; use this to establish a bound on the pass in terms of error limit
1259 : : // note that edge_collapse_goal is an estimate; triangle_collapse_goal will be used to actually limit collapses
1260 : 0 : size_t triangle_collapse_goal = ( result_count - target_index_count ) / 3;
1261 : 0 : size_t edge_collapse_goal = triangle_collapse_goal / 2;
1262 : :
1263 : : // we limit the error in each pass based on the error of optimal last collapse; since many collapses will be locked
1264 : : // as they will share vertices with other successfull collapses, we need to increase the acceptable error by this factor
1265 : 0 : const float kPassErrorBound = 1.5f;
1266 : :
1267 : 0 : float error_goal = edge_collapse_goal < edge_collapse_count ? edge_collapses[collapse_order[edge_collapse_goal]].error * kPassErrorBound : FLT_MAX;
1268 : :
1269 : 0 : for ( size_t i = 0; i < vertex_count; ++i )
1270 : 0 : collapse_remap[i] = unsigned( i );
1271 : :
1272 : 0 : memset( collapse_locked, 0, vertex_count );
1273 : :
1274 : 0 : size_t collapses = performEdgeCollapses( collapse_remap, collapse_locked, vertex_quadrics, edge_collapses, edge_collapse_count, collapse_order, remap, wedge, vertex_kind, triangle_collapse_goal, error_goal, error_limit );
1275 : :
1276 : : // no edges can be collapsed any more due to hitting the error limit or triangle collapse limit
1277 : 0 : if ( collapses == 0 )
1278 : 0 : break;
1279 : :
1280 : 0 : remapEdgeLoops( loop, vertex_count, collapse_remap );
1281 : :
1282 : 0 : size_t new_count = remapIndexBuffer( result, result_count, collapse_remap );
1283 : 0 : assert( new_count < result_count );
1284 : :
1285 : : #if TRACE
1286 : : float pass_error = 0.f;
1287 : : for ( size_t i = 0; i < edge_collapse_count; ++i )
1288 : : {
1289 : : Collapse &c = edge_collapses[collapse_order[i]];
1290 : :
1291 : : if ( collapse_remap[c.v0] == c.v1 )
1292 : : pass_error = c.error;
1293 : : }
1294 : :
1295 : : pass_count++;
1296 : : worst_error = ( worst_error < pass_error ) ? pass_error : worst_error;
1297 : :
1298 : : printf( "pass %d: triangles: %d -> %d, collapses: %d/%d (goal: %d), error: %e (limit %e goal %e)\n", int( pass_count ), int( result_count / 3 ), int( new_count / 3 ), int( collapses ), int( edge_collapse_count ), int( edge_collapse_goal ), pass_error, error_limit, error_goal );
1299 : : #endif
1300 : :
1301 : 0 : result_count = new_count;
1302 : : }
1303 : :
1304 : : #if TRACE
1305 : : printf( "passes: %d, worst error: %e\n", int( pass_count ), worst_error );
1306 : : #endif
1307 : :
1308 : : #if TRACE > 1
1309 : : dumpLockedCollapses( result, result_count, vertex_kind );
1310 : : #endif
1311 : :
1312 : : #if TRACE
1313 : : if ( meshopt_simplifyDebugKind )
1314 : : memcpy( meshopt_simplifyDebugKind, vertex_kind, vertex_count );
1315 : :
1316 : : if ( meshopt_simplifyDebugLoop )
1317 : : memcpy( meshopt_simplifyDebugLoop, loop, vertex_count * sizeof( unsigned int ) );
1318 : : #endif
1319 : :
1320 : 0 : return result_count;
1321 : 0 : }
1322 : :
1323 : 0 : size_t meshopt_simplifySloppy( unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count )
1324 : : {
1325 : : using namespace meshopt;
1326 : :
1327 : 0 : assert( index_count % 3 == 0 );
1328 : 0 : assert( vertex_positions_stride > 0 && vertex_positions_stride <= 256 );
1329 : 0 : assert( vertex_positions_stride % sizeof( float ) == 0 );
1330 : 0 : assert( target_index_count <= index_count );
1331 : :
1332 : : // we expect to get ~2 triangles/vertex in the output
1333 : 0 : size_t target_cell_count = target_index_count / 6;
1334 : :
1335 : 0 : if ( target_cell_count == 0 )
1336 : 0 : return 0;
1337 : :
1338 : 0 : meshopt_Allocator allocator;
1339 : :
1340 : 0 : Vector3 *vertex_positions = allocator.allocate<Vector3>( vertex_count );
1341 : 0 : rescalePositions( vertex_positions, vertex_positions_data, vertex_count, vertex_positions_stride );
1342 : :
1343 : : // find the optimal grid size using guided binary search
1344 : : #if TRACE
1345 : : printf( "source: %d vertices, %d triangles\n", int( vertex_count ), int( index_count / 3 ) );
1346 : : printf( "target: %d cells, %d triangles\n", int( target_cell_count ), int( target_index_count / 3 ) );
1347 : : #endif
1348 : :
1349 : 0 : unsigned int *vertex_ids = allocator.allocate<unsigned int>( vertex_count );
1350 : :
1351 : 0 : const int kInterpolationPasses = 5;
1352 : :
1353 : : // invariant: # of triangles in min_grid <= target_count
1354 : 0 : int min_grid = 0;
1355 : 0 : int max_grid = 1025;
1356 : 0 : size_t min_triangles = 0;
1357 : 0 : size_t max_triangles = index_count / 3;
1358 : :
1359 : : // instead of starting in the middle, let's guess as to what the answer might be! triangle count usually grows as a square of grid size...
1360 : 0 : int next_grid_size = int( sqrtf( float( target_cell_count ) ) + 0.5f );
1361 : :
1362 : 0 : for ( int pass = 0; pass < 10 + kInterpolationPasses; ++pass )
1363 : : {
1364 : 0 : assert( min_triangles < target_index_count / 3 );
1365 : 0 : assert( max_grid - min_grid > 1 );
1366 : :
1367 : : // we clamp the prediction of the grid size to make sure that the search converges
1368 : 0 : int grid_size = next_grid_size;
1369 : 0 : grid_size = ( grid_size <= min_grid ) ? min_grid + 1 : ( grid_size >= max_grid ) ? max_grid - 1 : grid_size;
1370 : :
1371 : 0 : computeVertexIds( vertex_ids, vertex_positions, vertex_count, grid_size );
1372 : 0 : size_t triangles = countTriangles( vertex_ids, indices, index_count );
1373 : :
1374 : : #if TRACE
1375 : : printf( "pass %d (%s): grid size %d, triangles %d, %s\n",
1376 : : pass, ( pass == 0 ) ? "guess" : ( pass <= kInterpolationPasses ) ? "lerp" : "binary",
1377 : : grid_size, int( triangles ),
1378 : : ( triangles <= target_index_count / 3 ) ? "under" : "over" );
1379 : : #endif
1380 : :
1381 : 0 : float tip = interpolate( float( target_index_count / 3 ), float( min_grid ), float( min_triangles ), float( grid_size ), float( triangles ), float( max_grid ), float( max_triangles ) );
1382 : :
1383 : 0 : if ( triangles <= target_index_count / 3 )
1384 : : {
1385 : 0 : min_grid = grid_size;
1386 : 0 : min_triangles = triangles;
1387 : 0 : }
1388 : : else
1389 : : {
1390 : 0 : max_grid = grid_size;
1391 : 0 : max_triangles = triangles;
1392 : : }
1393 : :
1394 : 0 : if ( triangles == target_index_count / 3 || max_grid - min_grid <= 1 )
1395 : 0 : break;
1396 : :
1397 : : // we start by using interpolation search - it usually converges faster
1398 : : // however, interpolation search has a worst case of O(N) so we switch to binary search after a few iterations which converges in O(logN)
1399 : 0 : next_grid_size = ( pass < kInterpolationPasses ) ? int( tip + 0.5f ) : ( min_grid + max_grid ) / 2;
1400 : 0 : }
1401 : :
1402 : 0 : if ( min_triangles == 0 )
1403 : 0 : return 0;
1404 : :
1405 : : // build vertex->cell association by mapping all vertices with the same quantized position to the same cell
1406 : 0 : size_t table_size = hashBuckets2( vertex_count );
1407 : 0 : unsigned int *table = allocator.allocate<unsigned int>( table_size );
1408 : :
1409 : 0 : unsigned int *vertex_cells = allocator.allocate<unsigned int>( vertex_count );
1410 : :
1411 : 0 : computeVertexIds( vertex_ids, vertex_positions, vertex_count, min_grid );
1412 : 0 : size_t cell_count = fillVertexCells( table, table_size, vertex_cells, vertex_ids, vertex_count );
1413 : :
1414 : : // build a quadric for each target cell
1415 : 0 : Quadric *cell_quadrics = allocator.allocate<Quadric>( cell_count );
1416 : 0 : memset( cell_quadrics, 0, cell_count * sizeof( Quadric ) );
1417 : :
1418 : 0 : fillCellQuadrics( cell_quadrics, indices, index_count, vertex_positions, vertex_cells );
1419 : :
1420 : : // for each target cell, find the vertex with the minimal error
1421 : 0 : unsigned int *cell_remap = allocator.allocate<unsigned int>( cell_count );
1422 : 0 : float *cell_errors = allocator.allocate<float>( cell_count );
1423 : :
1424 : 0 : fillCellRemap( cell_remap, cell_errors, cell_count, vertex_cells, cell_quadrics, vertex_positions, vertex_count );
1425 : :
1426 : : // collapse triangles!
1427 : : // note that we need to filter out triangles that we've already output because we very frequently generate redundant triangles between cells :(
1428 : 0 : size_t tritable_size = hashBuckets2( min_triangles );
1429 : 0 : unsigned int *tritable = allocator.allocate<unsigned int>( tritable_size );
1430 : :
1431 : 0 : size_t write = filterTriangles( destination, tritable, tritable_size, indices, index_count, vertex_cells, cell_remap );
1432 : 0 : assert( write <= target_index_count );
1433 : :
1434 : : #if TRACE
1435 : : printf( "result: %d cells, %d triangles (%d unfiltered)\n", int( cell_count ), int( write / 3 ), int( min_triangles ) );
1436 : : #endif
1437 : :
1438 : 0 : return write;
1439 : 0 : }
1440 : :
1441 : 0 : size_t meshopt_simplifyPoints( unsigned int *destination, const float *vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_vertex_count )
1442 : : {
1443 : : using namespace meshopt;
1444 : :
1445 : 0 : assert( vertex_positions_stride > 0 && vertex_positions_stride <= 256 );
1446 : 0 : assert( vertex_positions_stride % sizeof( float ) == 0 );
1447 : 0 : assert( target_vertex_count <= vertex_count );
1448 : :
1449 : 0 : size_t target_cell_count = target_vertex_count;
1450 : :
1451 : 0 : if ( target_cell_count == 0 )
1452 : 0 : return 0;
1453 : :
1454 : 0 : meshopt_Allocator allocator;
1455 : :
1456 : 0 : Vector3 *vertex_positions = allocator.allocate<Vector3>( vertex_count );
1457 : 0 : rescalePositions( vertex_positions, vertex_positions_data, vertex_count, vertex_positions_stride );
1458 : :
1459 : : // find the optimal grid size using guided binary search
1460 : : #if TRACE
1461 : : printf( "source: %d vertices\n", int( vertex_count ) );
1462 : : printf( "target: %d cells\n", int( target_cell_count ) );
1463 : : #endif
1464 : :
1465 : 0 : unsigned int *vertex_ids = allocator.allocate<unsigned int>( vertex_count );
1466 : :
1467 : 0 : size_t table_size = hashBuckets2( vertex_count );
1468 : 0 : unsigned int *table = allocator.allocate<unsigned int>( table_size );
1469 : :
1470 : 0 : const int kInterpolationPasses = 5;
1471 : :
1472 : : // invariant: # of vertices in min_grid <= target_count
1473 : 0 : int min_grid = 0;
1474 : 0 : int max_grid = 1025;
1475 : 0 : size_t min_vertices = 0;
1476 : 0 : size_t max_vertices = vertex_count;
1477 : :
1478 : : // instead of starting in the middle, let's guess as to what the answer might be! triangle count usually grows as a square of grid size...
1479 : 0 : int next_grid_size = int( sqrtf( float( target_cell_count ) ) + 0.5f );
1480 : :
1481 : 0 : for ( int pass = 0; pass < 10 + kInterpolationPasses; ++pass )
1482 : : {
1483 : 0 : assert( min_vertices < target_vertex_count );
1484 : 0 : assert( max_grid - min_grid > 1 );
1485 : :
1486 : : // we clamp the prediction of the grid size to make sure that the search converges
1487 : 0 : int grid_size = next_grid_size;
1488 : 0 : grid_size = ( grid_size <= min_grid ) ? min_grid + 1 : ( grid_size >= max_grid ) ? max_grid - 1 : grid_size;
1489 : :
1490 : 0 : computeVertexIds( vertex_ids, vertex_positions, vertex_count, grid_size );
1491 : 0 : size_t vertices = countVertexCells( table, table_size, vertex_ids, vertex_count );
1492 : :
1493 : : #if TRACE
1494 : : printf( "pass %d (%s): grid size %d, vertices %d, %s\n",
1495 : : pass, ( pass == 0 ) ? "guess" : ( pass <= kInterpolationPasses ) ? "lerp" : "binary",
1496 : : grid_size, int( vertices ),
1497 : : ( vertices <= target_vertex_count ) ? "under" : "over" );
1498 : : #endif
1499 : :
1500 : 0 : float tip = interpolate( float( target_vertex_count ), float( min_grid ), float( min_vertices ), float( grid_size ), float( vertices ), float( max_grid ), float( max_vertices ) );
1501 : :
1502 : 0 : if ( vertices <= target_vertex_count )
1503 : : {
1504 : 0 : min_grid = grid_size;
1505 : 0 : min_vertices = vertices;
1506 : 0 : }
1507 : : else
1508 : : {
1509 : 0 : max_grid = grid_size;
1510 : 0 : max_vertices = vertices;
1511 : : }
1512 : :
1513 : 0 : if ( vertices == target_vertex_count || max_grid - min_grid <= 1 )
1514 : 0 : break;
1515 : :
1516 : : // we start by using interpolation search - it usually converges faster
1517 : : // however, interpolation search has a worst case of O(N) so we switch to binary search after a few iterations which converges in O(logN)
1518 : 0 : next_grid_size = ( pass < kInterpolationPasses ) ? int( tip + 0.5f ) : ( min_grid + max_grid ) / 2;
1519 : 0 : }
1520 : :
1521 : 0 : if ( min_vertices == 0 )
1522 : 0 : return 0;
1523 : :
1524 : : // build vertex->cell association by mapping all vertices with the same quantized position to the same cell
1525 : 0 : unsigned int *vertex_cells = allocator.allocate<unsigned int>( vertex_count );
1526 : :
1527 : 0 : computeVertexIds( vertex_ids, vertex_positions, vertex_count, min_grid );
1528 : 0 : size_t cell_count = fillVertexCells( table, table_size, vertex_cells, vertex_ids, vertex_count );
1529 : :
1530 : : // build a quadric for each target cell
1531 : 0 : Quadric *cell_quadrics = allocator.allocate<Quadric>( cell_count );
1532 : 0 : memset( cell_quadrics, 0, cell_count * sizeof( Quadric ) );
1533 : :
1534 : 0 : fillCellQuadrics( cell_quadrics, vertex_positions, vertex_count, vertex_cells );
1535 : :
1536 : : // for each target cell, find the vertex with the minimal error
1537 : 0 : unsigned int *cell_remap = allocator.allocate<unsigned int>( cell_count );
1538 : 0 : float *cell_errors = allocator.allocate<float>( cell_count );
1539 : :
1540 : 0 : fillCellRemap( cell_remap, cell_errors, cell_count, vertex_cells, cell_quadrics, vertex_positions, vertex_count );
1541 : :
1542 : : // copy results to the output
1543 : 0 : assert( cell_count <= target_vertex_count );
1544 : 0 : memcpy( destination, cell_remap, sizeof( unsigned int ) * cell_count );
1545 : :
1546 : : #if TRACE
1547 : : printf( "result: %d cells\n", int( cell_count ) );
1548 : : #endif
1549 : :
1550 : 0 : return cell_count;
1551 : 0 : }
|