Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/meshoptimizer/partition.cpp
20778 views
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 <math.h>
6
#include <string.h>
7
8
// This work is based on:
9
// Takio Kurita. An efficient agglomerative clustering algorithm using a heap. 1991
10
namespace meshopt
11
{
12
13
// To avoid excessive recursion for malformed inputs, we switch to bisection after some depth
14
const int kMergeDepthCutoff = 40;
15
16
struct ClusterAdjacency
17
{
18
unsigned int* offsets;
19
unsigned int* clusters;
20
unsigned int* shared;
21
};
22
23
static void filterClusterIndices(unsigned int* data, unsigned int* offsets, const unsigned int* cluster_indices, const unsigned int* cluster_index_counts, size_t cluster_count, unsigned char* used, size_t vertex_count, size_t total_index_count)
24
{
25
(void)vertex_count;
26
(void)total_index_count;
27
28
size_t cluster_start = 0;
29
size_t cluster_write = 0;
30
31
for (size_t i = 0; i < cluster_count; ++i)
32
{
33
offsets[i] = unsigned(cluster_write);
34
35
// copy cluster indices, skipping duplicates
36
for (size_t j = 0; j < cluster_index_counts[i]; ++j)
37
{
38
unsigned int v = cluster_indices[cluster_start + j];
39
assert(v < vertex_count);
40
41
data[cluster_write] = v;
42
cluster_write += 1 - used[v];
43
used[v] = 1;
44
}
45
46
// reset used flags for the next cluster
47
for (size_t j = offsets[i]; j < cluster_write; ++j)
48
used[data[j]] = 0;
49
50
cluster_start += cluster_index_counts[i];
51
}
52
53
assert(cluster_start == total_index_count);
54
assert(cluster_write <= total_index_count);
55
offsets[cluster_count] = unsigned(cluster_write);
56
}
57
58
static float computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_positions_stride, float* out_center)
59
{
60
size_t vertex_stride_float = vertex_positions_stride / sizeof(float);
61
62
float center[3] = {0, 0, 0};
63
64
// approximate center of the cluster by averaging all vertex positions
65
for (size_t j = 0; j < index_count; ++j)
66
{
67
const float* p = vertex_positions + indices[j] * vertex_stride_float;
68
69
center[0] += p[0];
70
center[1] += p[1];
71
center[2] += p[2];
72
}
73
74
// note: technically clusters can't be empty per meshopt_partitionCluster but we check for a division by zero in case that changes
75
if (index_count)
76
{
77
center[0] /= float(index_count);
78
center[1] /= float(index_count);
79
center[2] /= float(index_count);
80
}
81
82
// compute radius of the bounding sphere for each cluster
83
float radiussq = 0;
84
85
for (size_t j = 0; j < index_count; ++j)
86
{
87
const float* p = vertex_positions + indices[j] * vertex_stride_float;
88
89
float d2 = (p[0] - center[0]) * (p[0] - center[0]) + (p[1] - center[1]) * (p[1] - center[1]) + (p[2] - center[2]) * (p[2] - center[2]);
90
91
radiussq = radiussq < d2 ? d2 : radiussq;
92
}
93
94
memcpy(out_center, center, sizeof(center));
95
return sqrtf(radiussq);
96
}
97
98
static void buildClusterAdjacency(ClusterAdjacency& adjacency, const unsigned int* cluster_indices, const unsigned int* cluster_offsets, size_t cluster_count, size_t vertex_count, meshopt_Allocator& allocator)
99
{
100
unsigned int* ref_offsets = allocator.allocate<unsigned int>(vertex_count + 1);
101
102
// compute number of clusters referenced by each vertex
103
memset(ref_offsets, 0, vertex_count * sizeof(unsigned int));
104
105
for (size_t i = 0; i < cluster_count; ++i)
106
{
107
for (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)
108
ref_offsets[cluster_indices[j]]++;
109
}
110
111
// compute (worst-case) number of adjacent clusters for each cluster
112
size_t total_adjacency = 0;
113
114
for (size_t i = 0; i < cluster_count; ++i)
115
{
116
size_t count = 0;
117
118
// worst case is every vertex has a disjoint cluster list
119
for (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)
120
count += ref_offsets[cluster_indices[j]] - 1;
121
122
// ... but only every other cluster can be adjacent in the end
123
total_adjacency += count < cluster_count - 1 ? count : cluster_count - 1;
124
}
125
126
// we can now allocate adjacency buffers
127
adjacency.offsets = allocator.allocate<unsigned int>(cluster_count + 1);
128
adjacency.clusters = allocator.allocate<unsigned int>(total_adjacency);
129
adjacency.shared = allocator.allocate<unsigned int>(total_adjacency);
130
131
// convert ref counts to offsets
132
size_t total_refs = 0;
133
134
for (size_t i = 0; i < vertex_count; ++i)
135
{
136
size_t count = ref_offsets[i];
137
ref_offsets[i] = unsigned(total_refs);
138
total_refs += count;
139
}
140
141
unsigned int* ref_data = allocator.allocate<unsigned int>(total_refs);
142
143
// fill cluster refs for each vertex
144
for (size_t i = 0; i < cluster_count; ++i)
145
{
146
for (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)
147
ref_data[ref_offsets[cluster_indices[j]]++] = unsigned(i);
148
}
149
150
// after the previous pass, ref_offsets contain the end of the data for each vertex; shift it forward to get the start
151
memmove(ref_offsets + 1, ref_offsets, vertex_count * sizeof(unsigned int));
152
ref_offsets[0] = 0;
153
154
// fill cluster adjacency for each cluster...
155
adjacency.offsets[0] = 0;
156
157
for (size_t i = 0; i < cluster_count; ++i)
158
{
159
unsigned int* adj = adjacency.clusters + adjacency.offsets[i];
160
unsigned int* shd = adjacency.shared + adjacency.offsets[i];
161
size_t count = 0;
162
163
for (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)
164
{
165
unsigned int v = cluster_indices[j];
166
167
// merge the entire cluster list of each vertex into current list
168
for (size_t k = ref_offsets[v]; k < ref_offsets[v + 1]; ++k)
169
{
170
unsigned int c = ref_data[k];
171
assert(c < cluster_count);
172
173
if (c == unsigned(i))
174
continue;
175
176
// if the cluster is already in the list, increment the shared count
177
bool found = false;
178
for (size_t l = 0; l < count; ++l)
179
if (adj[l] == c)
180
{
181
found = true;
182
shd[l]++;
183
break;
184
}
185
186
// .. or append a new cluster
187
if (!found)
188
{
189
adj[count] = c;
190
shd[count] = 1;
191
count++;
192
}
193
}
194
}
195
196
// mark the end of the adjacency list; the next cluster will start there as well
197
adjacency.offsets[i + 1] = adjacency.offsets[i] + unsigned(count);
198
}
199
200
assert(adjacency.offsets[cluster_count] <= total_adjacency);
201
202
// ref_offsets can't be deallocated as it was allocated before adjacency
203
allocator.deallocate(ref_data);
204
}
205
206
struct ClusterGroup
207
{
208
int group;
209
int next;
210
unsigned int size; // 0 unless root
211
unsigned int vertices;
212
213
float center[3];
214
float radius;
215
};
216
217
struct GroupOrder
218
{
219
unsigned int id;
220
int order;
221
};
222
223
static void heapPush(GroupOrder* heap, size_t size, GroupOrder item)
224
{
225
// insert a new element at the end (breaks heap invariant)
226
heap[size++] = item;
227
228
// bubble up the new element to its correct position
229
size_t i = size - 1;
230
while (i > 0 && heap[i].order < heap[(i - 1) / 2].order)
231
{
232
size_t p = (i - 1) / 2;
233
234
GroupOrder temp = heap[i];
235
heap[i] = heap[p];
236
heap[p] = temp;
237
i = p;
238
}
239
}
240
241
static GroupOrder heapPop(GroupOrder* heap, size_t size)
242
{
243
assert(size > 0);
244
GroupOrder top = heap[0];
245
246
// move the last element to the top (breaks heap invariant)
247
heap[0] = heap[--size];
248
249
// bubble down the new top element to its correct position
250
size_t i = 0;
251
while (i * 2 + 1 < size)
252
{
253
// find the smallest child
254
size_t j = i * 2 + 1;
255
j += (j + 1 < size && heap[j + 1].order < heap[j].order);
256
257
// if the parent is already smaller than both children, we're done
258
if (heap[j].order >= heap[i].order)
259
break;
260
261
// otherwise, swap the parent and child and continue
262
GroupOrder temp = heap[i];
263
heap[i] = heap[j];
264
heap[j] = temp;
265
i = j;
266
}
267
268
return top;
269
}
270
271
static unsigned int countShared(const ClusterGroup* groups, int group1, int group2, const ClusterAdjacency& adjacency)
272
{
273
unsigned int total = 0;
274
275
for (int i1 = group1; i1 >= 0; i1 = groups[i1].next)
276
for (int i2 = group2; i2 >= 0; i2 = groups[i2].next)
277
{
278
for (unsigned int adj = adjacency.offsets[i1]; adj < adjacency.offsets[i1 + 1]; ++adj)
279
if (adjacency.clusters[adj] == unsigned(i2))
280
{
281
total += adjacency.shared[adj];
282
break;
283
}
284
}
285
286
return total;
287
}
288
289
static void mergeBounds(ClusterGroup& target, const ClusterGroup& source)
290
{
291
float r1 = target.radius, r2 = source.radius;
292
float dx = source.center[0] - target.center[0], dy = source.center[1] - target.center[1], dz = source.center[2] - target.center[2];
293
float d = sqrtf(dx * dx + dy * dy + dz * dz);
294
295
if (d + r1 < r2)
296
{
297
target.center[0] = source.center[0];
298
target.center[1] = source.center[1];
299
target.center[2] = source.center[2];
300
target.radius = source.radius;
301
return;
302
}
303
304
if (d + r2 > r1)
305
{
306
float k = d > 0 ? (d + r2 - r1) / (2 * d) : 0.f;
307
308
target.center[0] += dx * k;
309
target.center[1] += dy * k;
310
target.center[2] += dz * k;
311
target.radius = (d + r2 + r1) / 2;
312
}
313
}
314
315
static float boundsScore(const ClusterGroup& target, const ClusterGroup& source)
316
{
317
float r1 = target.radius, r2 = source.radius;
318
float dx = source.center[0] - target.center[0], dy = source.center[1] - target.center[1], dz = source.center[2] - target.center[2];
319
float d = sqrtf(dx * dx + dy * dy + dz * dz);
320
321
float mr = d + r1 < r2 ? r2 : (d + r2 < r1 ? r1 : (d + r2 + r1) / 2);
322
323
return mr > 0 ? r1 / mr : 0.f;
324
}
325
326
static int pickGroupToMerge(const ClusterGroup* groups, int id, const ClusterAdjacency& adjacency, size_t max_partition_size, bool use_bounds)
327
{
328
assert(groups[id].size > 0);
329
330
float group_rsqrt = 1.f / sqrtf(float(int(groups[id].vertices)));
331
332
int best_group = -1;
333
float best_score = 0;
334
335
for (int ci = id; ci >= 0; ci = groups[ci].next)
336
{
337
for (unsigned int adj = adjacency.offsets[ci]; adj != adjacency.offsets[ci + 1]; ++adj)
338
{
339
int other = groups[adjacency.clusters[adj]].group;
340
if (other < 0)
341
continue;
342
343
assert(groups[other].size > 0);
344
if (groups[id].size + groups[other].size > max_partition_size)
345
continue;
346
347
unsigned int shared = countShared(groups, id, other, adjacency);
348
float other_rsqrt = 1.f / sqrtf(float(int(groups[other].vertices)));
349
350
// normalize shared count by the expected boundary of each group (+ keeps scoring symmetric)
351
float score = float(int(shared)) * (group_rsqrt + other_rsqrt);
352
353
// incorporate spatial score to favor merging nearby groups
354
if (use_bounds)
355
score *= 1.f + 0.4f * boundsScore(groups[id], groups[other]);
356
357
if (score > best_score)
358
{
359
best_group = other;
360
best_score = score;
361
}
362
}
363
}
364
365
return best_group;
366
}
367
368
static void mergeLeaf(ClusterGroup* groups, unsigned int* order, size_t count, size_t target_partition_size, size_t max_partition_size)
369
{
370
for (size_t i = 0; i < count; ++i)
371
{
372
unsigned int id = order[i];
373
if (groups[id].size == 0 || groups[id].size >= target_partition_size)
374
continue;
375
376
float best_score = -1.f;
377
int best_group = -1;
378
379
for (size_t j = 0; j < count; ++j)
380
{
381
unsigned int other = order[j];
382
if (id == other || groups[other].size == 0)
383
continue;
384
385
if (groups[id].size + groups[other].size > max_partition_size)
386
continue;
387
388
// favor merging nearby groups
389
float score = boundsScore(groups[id], groups[other]);
390
391
if (score > best_score)
392
{
393
best_score = score;
394
best_group = other;
395
}
396
}
397
398
// merge id *into* best_group; that way, we may merge more groups into the same best_group, maximizing the chance of reaching target
399
if (best_group != -1)
400
{
401
// combine groups by linking them together
402
unsigned int tail = best_group;
403
while (groups[tail].next >= 0)
404
tail = groups[tail].next;
405
406
groups[tail].next = id;
407
408
// update group sizes; note, we omit vertices update for simplicity as it's not used for spatial merge
409
groups[best_group].size += groups[id].size;
410
groups[id].size = 0;
411
412
// merge bounding spheres
413
mergeBounds(groups[best_group], groups[id]);
414
groups[id].radius = 0.f;
415
}
416
}
417
}
418
419
static size_t mergePartition(unsigned int* order, size_t count, const ClusterGroup* groups, int axis, float pivot)
420
{
421
size_t m = 0;
422
423
// invariant: elements in range [0, m) are < pivot, elements in range [m, i) are >= pivot
424
for (size_t i = 0; i < count; ++i)
425
{
426
float v = groups[order[i]].center[axis];
427
428
// swap(m, i) unconditionally
429
unsigned int t = order[m];
430
order[m] = order[i];
431
order[i] = t;
432
433
// when v >= pivot, we swap i with m without advancing it, preserving invariants
434
m += v < pivot;
435
}
436
437
return m;
438
}
439
440
static void mergeSpatial(ClusterGroup* groups, unsigned int* order, size_t count, size_t target_partition_size, size_t max_partition_size, size_t leaf_size, int depth)
441
{
442
size_t total = 0;
443
for (size_t i = 0; i < count; ++i)
444
total += groups[order[i]].size;
445
446
if (total <= max_partition_size || count <= leaf_size)
447
return mergeLeaf(groups, order, count, target_partition_size, max_partition_size);
448
449
float mean[3] = {};
450
float vars[3] = {};
451
float runc = 1, runs = 1;
452
453
// gather statistics on the points in the subtree using Welford's algorithm
454
for (size_t i = 0; i < count; ++i, runc += 1.f, runs = 1.f / runc)
455
{
456
const float* point = groups[order[i]].center;
457
458
for (int k = 0; k < 3; ++k)
459
{
460
float delta = point[k] - mean[k];
461
mean[k] += delta * runs;
462
vars[k] += delta * (point[k] - mean[k]);
463
}
464
}
465
466
// split axis is one where the variance is largest
467
int axis = (vars[0] >= vars[1] && vars[0] >= vars[2]) ? 0 : (vars[1] >= vars[2] ? 1 : 2);
468
469
float split = mean[axis];
470
size_t middle = mergePartition(order, count, groups, axis, split);
471
472
// enforce balance for degenerate partitions
473
// this also ensures recursion depth is bounded on pathological inputs
474
if (middle <= leaf_size / 2 || count - middle <= leaf_size / 2 || depth >= kMergeDepthCutoff)
475
middle = count / 2;
476
477
// recursion depth is logarithmic and bounded due to max depth check above
478
mergeSpatial(groups, order, middle, target_partition_size, max_partition_size, leaf_size, depth + 1);
479
mergeSpatial(groups, order + middle, count - middle, target_partition_size, max_partition_size, leaf_size, depth + 1);
480
}
481
482
} // namespace meshopt
483
484
size_t meshopt_partitionClusters(unsigned int* destination, const unsigned int* cluster_indices, size_t total_index_count, const unsigned int* cluster_index_counts, size_t cluster_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_partition_size)
485
{
486
using namespace meshopt;
487
488
assert((vertex_positions == NULL || vertex_positions_stride >= 12) && vertex_positions_stride <= 256);
489
assert(vertex_positions_stride % sizeof(float) == 0);
490
assert(target_partition_size > 0);
491
492
size_t max_partition_size = target_partition_size + target_partition_size / 3;
493
494
meshopt_Allocator allocator;
495
496
unsigned char* used = allocator.allocate<unsigned char>(vertex_count);
497
memset(used, 0, vertex_count);
498
499
unsigned int* cluster_newindices = allocator.allocate<unsigned int>(total_index_count);
500
unsigned int* cluster_offsets = allocator.allocate<unsigned int>(cluster_count + 1);
501
502
// make new cluster index list that filters out duplicate indices
503
filterClusterIndices(cluster_newindices, cluster_offsets, cluster_indices, cluster_index_counts, cluster_count, used, vertex_count, total_index_count);
504
cluster_indices = cluster_newindices;
505
506
// build cluster adjacency along with edge weights (shared vertex count)
507
ClusterAdjacency adjacency = {};
508
buildClusterAdjacency(adjacency, cluster_indices, cluster_offsets, cluster_count, vertex_count, allocator);
509
510
ClusterGroup* groups = allocator.allocate<ClusterGroup>(cluster_count);
511
memset(groups, 0, sizeof(ClusterGroup) * cluster_count);
512
513
GroupOrder* order = allocator.allocate<GroupOrder>(cluster_count);
514
size_t pending = 0;
515
516
// create a singleton group for each cluster and order them by priority
517
for (size_t i = 0; i < cluster_count; ++i)
518
{
519
groups[i].group = int(i);
520
groups[i].next = -1;
521
groups[i].size = 1;
522
groups[i].vertices = cluster_offsets[i + 1] - cluster_offsets[i];
523
assert(groups[i].vertices > 0);
524
525
// compute bounding sphere for each cluster if positions are provided
526
if (vertex_positions)
527
groups[i].radius = computeClusterBounds(cluster_indices + cluster_offsets[i], cluster_offsets[i + 1] - cluster_offsets[i], vertex_positions, vertex_positions_stride, groups[i].center);
528
529
GroupOrder item = {};
530
item.id = unsigned(i);
531
item.order = groups[i].vertices;
532
533
heapPush(order, pending++, item);
534
}
535
536
// iteratively merge the smallest group with the best group
537
while (pending)
538
{
539
GroupOrder top = heapPop(order, pending--);
540
541
// this group was merged into another group earlier
542
if (groups[top.id].size == 0)
543
continue;
544
545
// disassociate clusters from the group to prevent them from being merged again; we will re-associate them if the group is reinserted
546
for (int i = top.id; i >= 0; i = groups[i].next)
547
{
548
assert(groups[i].group == int(top.id));
549
groups[i].group = -1;
550
}
551
552
// the group is large enough, emit as is
553
if (groups[top.id].size >= target_partition_size)
554
continue;
555
556
int best_group = pickGroupToMerge(groups, top.id, adjacency, max_partition_size, /* use_bounds= */ vertex_positions);
557
558
// we can't grow the group any more, emit as is
559
if (best_group == -1)
560
continue;
561
562
// compute shared vertices to adjust the total vertices estimate after merging
563
unsigned int shared = countShared(groups, top.id, best_group, adjacency);
564
565
// combine groups by linking them together
566
unsigned int tail = top.id;
567
while (groups[tail].next >= 0)
568
tail = groups[tail].next;
569
570
groups[tail].next = best_group;
571
572
// update group sizes; note, the vertex update is a O(1) approximation which avoids recomputing the true size
573
groups[top.id].size += groups[best_group].size;
574
groups[top.id].vertices += groups[best_group].vertices;
575
groups[top.id].vertices = (groups[top.id].vertices > shared) ? groups[top.id].vertices - shared : 1;
576
577
groups[best_group].size = 0;
578
groups[best_group].vertices = 0;
579
580
// merge bounding spheres if bounds are available
581
if (vertex_positions)
582
{
583
mergeBounds(groups[top.id], groups[best_group]);
584
groups[best_group].radius = 0;
585
}
586
587
// re-associate all clusters back to the merged group
588
for (int i = top.id; i >= 0; i = groups[i].next)
589
groups[i].group = int(top.id);
590
591
top.order = groups[top.id].vertices;
592
heapPush(order, pending++, top);
593
}
594
595
// if vertex positions are provided, we do a final pass to see if we can merge small groups based on spatial locality alone
596
if (vertex_positions)
597
{
598
unsigned int* merge_order = reinterpret_cast<unsigned int*>(order);
599
size_t merge_offset = 0;
600
601
for (size_t i = 0; i < cluster_count; ++i)
602
if (groups[i].size)
603
merge_order[merge_offset++] = unsigned(i);
604
605
mergeSpatial(groups, merge_order, merge_offset, target_partition_size, max_partition_size, /* leaf_size= */ 8, 0);
606
}
607
608
// output each remaining group
609
size_t next_group = 0;
610
611
for (size_t i = 0; i < cluster_count; ++i)
612
{
613
if (groups[i].size == 0)
614
continue;
615
616
for (int j = int(i); j >= 0; j = groups[j].next)
617
destination[j] = unsigned(next_group);
618
619
next_group++;
620
}
621
622
assert(next_group <= cluster_count);
623
return next_group;
624
}
625
626