Path: blob/master/thirdparty/meshoptimizer/partition.cpp
9902 views
// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details1#include "meshoptimizer.h"23#include <assert.h>4#include <math.h>5#include <string.h>67// This work is based on:8// Takio Kurita. An efficient agglomerative clustering algorithm using a heap. 19919namespace meshopt10{1112struct ClusterAdjacency13{14unsigned int* offsets;15unsigned int* clusters;16unsigned int* shared;17};1819static 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)20{21(void)vertex_count;22(void)total_index_count;2324size_t cluster_start = 0;25size_t cluster_write = 0;2627for (size_t i = 0; i < cluster_count; ++i)28{29offsets[i] = unsigned(cluster_write);3031// copy cluster indices, skipping duplicates32for (size_t j = 0; j < cluster_index_counts[i]; ++j)33{34unsigned int v = cluster_indices[cluster_start + j];35assert(v < vertex_count);3637data[cluster_write] = v;38cluster_write += 1 - used[v];39used[v] = 1;40}4142// reset used flags for the next cluster43for (size_t j = offsets[i]; j < cluster_write; ++j)44used[data[j]] = 0;4546cluster_start += cluster_index_counts[i];47}4849assert(cluster_start == total_index_count);50assert(cluster_write <= total_index_count);51offsets[cluster_count] = unsigned(cluster_write);52}5354static void computeClusterBounds(float* cluster_bounds, const unsigned int* cluster_indices, const unsigned int* cluster_offsets, size_t cluster_count, const float* vertex_positions, size_t vertex_positions_stride)55{56size_t vertex_stride_float = vertex_positions_stride / sizeof(float);5758for (size_t i = 0; i < cluster_count; ++i)59{60float center[3] = {0, 0, 0};6162// approximate center of the cluster by averaging all vertex positions63for (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)64{65const float* p = vertex_positions + cluster_indices[j] * vertex_stride_float;6667center[0] += p[0];68center[1] += p[1];69center[2] += p[2];70}7172// note: technically clusters can't be empty per meshopt_partitionCluster but we check for a division by zero in case that changes73if (size_t cluster_size = cluster_offsets[i + 1] - cluster_offsets[i])74{75center[0] /= float(cluster_size);76center[1] /= float(cluster_size);77center[2] /= float(cluster_size);78}7980// compute radius of the bounding sphere for each cluster81float radiussq = 0;8283for (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)84{85const float* p = vertex_positions + cluster_indices[j] * vertex_stride_float;8687float 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]);8889radiussq = radiussq < d2 ? d2 : radiussq;90}9192cluster_bounds[i * 4 + 0] = center[0];93cluster_bounds[i * 4 + 1] = center[1];94cluster_bounds[i * 4 + 2] = center[2];95cluster_bounds[i * 4 + 3] = sqrtf(radiussq);96}97}9899static 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)100{101unsigned int* ref_offsets = allocator.allocate<unsigned int>(vertex_count + 1);102103// compute number of clusters referenced by each vertex104memset(ref_offsets, 0, vertex_count * sizeof(unsigned int));105106for (size_t i = 0; i < cluster_count; ++i)107{108for (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)109ref_offsets[cluster_indices[j]]++;110}111112// compute (worst-case) number of adjacent clusters for each cluster113size_t total_adjacency = 0;114115for (size_t i = 0; i < cluster_count; ++i)116{117size_t count = 0;118119// worst case is every vertex has a disjoint cluster list120for (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)121count += ref_offsets[cluster_indices[j]] - 1;122123// ... but only every other cluster can be adjacent in the end124total_adjacency += count < cluster_count - 1 ? count : cluster_count - 1;125}126127// we can now allocate adjacency buffers128adjacency.offsets = allocator.allocate<unsigned int>(cluster_count + 1);129adjacency.clusters = allocator.allocate<unsigned int>(total_adjacency);130adjacency.shared = allocator.allocate<unsigned int>(total_adjacency);131132// convert ref counts to offsets133size_t total_refs = 0;134135for (size_t i = 0; i < vertex_count; ++i)136{137size_t count = ref_offsets[i];138ref_offsets[i] = unsigned(total_refs);139total_refs += count;140}141142unsigned int* ref_data = allocator.allocate<unsigned int>(total_refs);143144// fill cluster refs for each vertex145for (size_t i = 0; i < cluster_count; ++i)146{147for (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)148ref_data[ref_offsets[cluster_indices[j]]++] = unsigned(i);149}150151// after the previous pass, ref_offsets contain the end of the data for each vertex; shift it forward to get the start152memmove(ref_offsets + 1, ref_offsets, vertex_count * sizeof(unsigned int));153ref_offsets[0] = 0;154155// fill cluster adjacency for each cluster...156adjacency.offsets[0] = 0;157158for (size_t i = 0; i < cluster_count; ++i)159{160unsigned int* adj = adjacency.clusters + adjacency.offsets[i];161unsigned int* shd = adjacency.shared + adjacency.offsets[i];162size_t count = 0;163164for (size_t j = cluster_offsets[i]; j < cluster_offsets[i + 1]; ++j)165{166unsigned int v = cluster_indices[j];167168// merge the entire cluster list of each vertex into current list169for (size_t k = ref_offsets[v]; k < ref_offsets[v + 1]; ++k)170{171unsigned int c = ref_data[k];172assert(c < cluster_count);173174if (c == unsigned(i))175continue;176177// if the cluster is already in the list, increment the shared count178bool found = false;179for (size_t l = 0; l < count; ++l)180if (adj[l] == c)181{182found = true;183shd[l]++;184break;185}186187// .. or append a new cluster188if (!found)189{190adj[count] = c;191shd[count] = 1;192count++;193}194}195}196197// mark the end of the adjacency list; the next cluster will start there as well198adjacency.offsets[i + 1] = adjacency.offsets[i] + unsigned(count);199}200201assert(adjacency.offsets[cluster_count] <= total_adjacency);202203// ref_offsets can't be deallocated as it was allocated before adjacency204allocator.deallocate(ref_data);205}206207struct ClusterGroup208{209int group;210int next;211unsigned int size; // 0 unless root212unsigned int vertices;213};214215struct GroupOrder216{217unsigned int id;218int order;219};220221static void heapPush(GroupOrder* heap, size_t size, GroupOrder item)222{223// insert a new element at the end (breaks heap invariant)224heap[size++] = item;225226// bubble up the new element to its correct position227size_t i = size - 1;228while (i > 0 && heap[i].order < heap[(i - 1) / 2].order)229{230size_t p = (i - 1) / 2;231232GroupOrder temp = heap[i];233heap[i] = heap[p];234heap[p] = temp;235i = p;236}237}238239static GroupOrder heapPop(GroupOrder* heap, size_t size)240{241assert(size > 0);242GroupOrder top = heap[0];243244// move the last element to the top (breaks heap invariant)245heap[0] = heap[--size];246247// bubble down the new top element to its correct position248size_t i = 0;249while (i * 2 + 1 < size)250{251// find the smallest child252size_t j = i * 2 + 1;253j += (j + 1 < size && heap[j + 1].order < heap[j].order);254255// if the parent is already smaller than both children, we're done256if (heap[j].order >= heap[i].order)257break;258259// otherwise, swap the parent and child and continue260GroupOrder temp = heap[i];261heap[i] = heap[j];262heap[j] = temp;263i = j;264}265266return top;267}268269static unsigned int countShared(const ClusterGroup* groups, int group1, int group2, const ClusterAdjacency& adjacency)270{271unsigned int total = 0;272273for (int i1 = group1; i1 >= 0; i1 = groups[i1].next)274for (int i2 = group2; i2 >= 0; i2 = groups[i2].next)275{276for (unsigned int adj = adjacency.offsets[i1]; adj < adjacency.offsets[i1 + 1]; ++adj)277if (adjacency.clusters[adj] == unsigned(i2))278{279total += adjacency.shared[adj];280break;281}282}283284return total;285}286287static void mergeBounds(float* target, const float* source)288{289float r1 = target[3], r2 = source[3];290float dx = source[0] - target[0], dy = source[1] - target[1], dz = source[2] - target[2];291float d = sqrtf(dx * dx + dy * dy + dz * dz);292293if (d + r1 < r2)294{295memcpy(target, source, 4 * sizeof(float));296return;297}298299if (d + r2 > r1)300{301float k = d > 0 ? (d + r2 - r1) / (2 * d) : 0.f;302303target[0] += dx * k;304target[1] += dy * k;305target[2] += dz * k;306target[3] = (d + r2 + r1) / 2;307}308}309310static float boundsScore(const float* target, const float* source)311{312float r1 = target[3], r2 = source[3];313float dx = source[0] - target[0], dy = source[1] - target[1], dz = source[2] - target[2];314float d = sqrtf(dx * dx + dy * dy + dz * dz);315316float mr = d + r1 < r2 ? r2 : (d + r2 < r1 ? r1 : (d + r2 + r1) / 2);317318return mr > 0 ? r1 / mr : 0.f;319}320321static int pickGroupToMerge(const ClusterGroup* groups, int id, const ClusterAdjacency& adjacency, size_t max_partition_size, const float* cluster_bounds)322{323assert(groups[id].size > 0);324325float group_rsqrt = 1.f / sqrtf(float(int(groups[id].vertices)));326327int best_group = -1;328float best_score = 0;329330for (int ci = id; ci >= 0; ci = groups[ci].next)331{332for (unsigned int adj = adjacency.offsets[ci]; adj != adjacency.offsets[ci + 1]; ++adj)333{334int other = groups[adjacency.clusters[adj]].group;335if (other < 0)336continue;337338assert(groups[other].size > 0);339if (groups[id].size + groups[other].size > max_partition_size)340continue;341342unsigned int shared = countShared(groups, id, other, adjacency);343float other_rsqrt = 1.f / sqrtf(float(int(groups[other].vertices)));344345// normalize shared count by the expected boundary of each group (+ keeps scoring symmetric)346float score = float(int(shared)) * (group_rsqrt + other_rsqrt);347348// incorporate spatial score to favor merging nearby groups349if (cluster_bounds)350score *= 1.f + 0.4f * boundsScore(&cluster_bounds[id * 4], &cluster_bounds[other * 4]);351352if (score > best_score)353{354best_group = other;355best_score = score;356}357}358}359360return best_group;361}362363} // namespace meshopt364365size_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)366{367using namespace meshopt;368369assert((vertex_positions == NULL || vertex_positions_stride >= 12) && vertex_positions_stride <= 256);370assert(vertex_positions_stride % sizeof(float) == 0);371assert(target_partition_size > 0);372373size_t max_partition_size = target_partition_size + target_partition_size * 3 / 8;374375meshopt_Allocator allocator;376377unsigned char* used = allocator.allocate<unsigned char>(vertex_count);378memset(used, 0, vertex_count);379380unsigned int* cluster_newindices = allocator.allocate<unsigned int>(total_index_count);381unsigned int* cluster_offsets = allocator.allocate<unsigned int>(cluster_count + 1);382383// make new cluster index list that filters out duplicate indices384filterClusterIndices(cluster_newindices, cluster_offsets, cluster_indices, cluster_index_counts, cluster_count, used, vertex_count, total_index_count);385cluster_indices = cluster_newindices;386387// compute bounding sphere for each cluster if positions are provided388float* cluster_bounds = NULL;389390if (vertex_positions)391{392cluster_bounds = allocator.allocate<float>(cluster_count * 4);393computeClusterBounds(cluster_bounds, cluster_indices, cluster_offsets, cluster_count, vertex_positions, vertex_positions_stride);394}395396// build cluster adjacency along with edge weights (shared vertex count)397ClusterAdjacency adjacency = {};398buildClusterAdjacency(adjacency, cluster_indices, cluster_offsets, cluster_count, vertex_count, allocator);399400ClusterGroup* groups = allocator.allocate<ClusterGroup>(cluster_count);401402GroupOrder* order = allocator.allocate<GroupOrder>(cluster_count);403size_t pending = 0;404405// create a singleton group for each cluster and order them by priority406for (size_t i = 0; i < cluster_count; ++i)407{408groups[i].group = int(i);409groups[i].next = -1;410groups[i].size = 1;411groups[i].vertices = cluster_offsets[i + 1] - cluster_offsets[i];412assert(groups[i].vertices > 0);413414GroupOrder item = {};415item.id = unsigned(i);416item.order = groups[i].vertices;417418heapPush(order, pending++, item);419}420421// iteratively merge the smallest group with the best group422while (pending)423{424GroupOrder top = heapPop(order, pending--);425426// this group was merged into another group earlier427if (groups[top.id].size == 0)428continue;429430// disassociate clusters from the group to prevent them from being merged again; we will re-associate them if the group is reinserted431for (int i = top.id; i >= 0; i = groups[i].next)432{433assert(groups[i].group == int(top.id));434groups[i].group = -1;435}436437// the group is large enough, emit as is438if (groups[top.id].size >= target_partition_size)439continue;440441int best_group = pickGroupToMerge(groups, top.id, adjacency, max_partition_size, cluster_bounds);442443// we can't grow the group any more, emit as is444if (best_group == -1)445continue;446447// compute shared vertices to adjust the total vertices estimate after merging448unsigned int shared = countShared(groups, top.id, best_group, adjacency);449450// combine groups by linking them together451assert(groups[best_group].size > 0);452453for (int i = top.id; i >= 0; i = groups[i].next)454if (groups[i].next < 0)455{456groups[i].next = best_group;457break;458}459460// update group sizes; note, the vertex update is a O(1) approximation which avoids recomputing the true size461groups[top.id].size += groups[best_group].size;462groups[top.id].vertices += groups[best_group].vertices;463groups[top.id].vertices = (groups[top.id].vertices > shared) ? groups[top.id].vertices - shared : 1;464465groups[best_group].size = 0;466groups[best_group].vertices = 0;467468// merge bounding spheres if bounds are available469if (cluster_bounds)470{471mergeBounds(&cluster_bounds[top.id * 4], &cluster_bounds[best_group * 4]);472memset(&cluster_bounds[best_group * 4], 0, 4 * sizeof(float));473}474475// re-associate all clusters back to the merged group476for (int i = top.id; i >= 0; i = groups[i].next)477groups[i].group = int(top.id);478479top.order = groups[top.id].vertices;480heapPush(order, pending++, top);481}482483size_t next_group = 0;484485for (size_t i = 0; i < cluster_count; ++i)486{487if (groups[i].size == 0)488continue;489490for (int j = int(i); j >= 0; j = groups[j].next)491destination[j] = unsigned(next_group);492493next_group++;494}495496assert(next_group <= cluster_count);497return next_group;498}499500501