@@ -657,12 +657,13 @@ void simplifyComplete(const Mesh& mesh)
657657
658658void simplifyClusters (const Mesh& mesh, float threshold = 0 .2f )
659659{
660- // note: we use clusters that are larger than normal to give simplifier room to work; in practice you'd use cluster groups merged from smaller clusters and build a cluster DAG
661- const size_t max_vertices = 255 ;
662- const size_t max_triangles = 512 ;
660+ const size_t max_vertices = 64 ;
661+ const size_t max_triangles = 64 ;
662+ const size_t target_group_size = 8 ;
663663
664664 double start = timestamp ();
665665
666+ // build clusters (meshlets) out of the mesh
666667 size_t max_meshlets = meshopt_buildMeshletsBound (mesh.indices .size (), max_vertices, max_triangles);
667668 std::vector<meshopt_Meshlet> meshlets (max_meshlets);
668669 std::vector<unsigned int > meshlet_vertices (max_meshlets * max_vertices);
@@ -672,6 +673,44 @@ void simplifyClusters(const Mesh& mesh, float threshold = 0.2f)
672673
673674 double middle = timestamp ();
674675
676+ // partition clusters in groups; each group will be simplified separately and the boundaries between groups will be preserved
677+ std::vector<unsigned int > cluster_indices;
678+ cluster_indices.reserve (mesh.indices .size ()); // slight underestimate, vector should realloc once
679+ std::vector<unsigned int > cluster_sizes (meshlets.size ());
680+
681+ for (size_t i = 0 ; i < meshlets.size (); ++i)
682+ {
683+ const meshopt_Meshlet& m = meshlets[i];
684+
685+ for (size_t j = 0 ; j < m.triangle_count * 3 ; ++j)
686+ cluster_indices.push_back (meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + j]]);
687+
688+ cluster_sizes[i] = m.triangle_count * 3 ;
689+ }
690+
691+ // makes sure clusters are partitioned using position-only adjacency
692+ meshopt_generateShadowIndexBuffer (&cluster_indices[0 ], &cluster_indices[0 ], cluster_indices.size (), &mesh.vertices [0 ].px , mesh.vertices .size (), sizeof (float ) * 3 , sizeof (Vertex));
693+
694+ std::vector<unsigned int > partition (meshlets.size ());
695+ size_t partition_count = meshopt_partitionClusters (&partition[0 ], &cluster_indices[0 ], cluster_indices.size (), &cluster_sizes[0 ], cluster_sizes.size (), mesh.vertices .size (), target_group_size);
696+
697+ // convert partitions to linked lists to make it easier to iterate over (vectors of vectors would work too)
698+ std::vector<int > partnext (meshlets.size (), -1 );
699+ std::vector<int > partlast (partition_count, -1 );
700+
701+ for (size_t i = 0 ; i < meshlets.size (); ++i)
702+ {
703+ unsigned int part = partition[i];
704+
705+ if (partlast[part] >= 0 )
706+ partnext[partlast[part]] = int (i);
707+
708+ partlast[part] = int (i);
709+ partnext[i] = -1 ;
710+ }
711+
712+ double parttime = timestamp ();
713+
675714 float scale = meshopt_simplifyScale (&mesh.vertices [0 ].px , mesh.vertices .size (), sizeof (Vertex));
676715
677716 std::vector<unsigned int > lod;
@@ -681,33 +720,47 @@ void simplifyClusters(const Mesh& mesh, float threshold = 0.2f)
681720
682721 for (size_t i = 0 ; i < meshlets.size (); ++i)
683722 {
684- const meshopt_Meshlet& m = meshlets[i];
723+ if (partlast[partition[i]] < 0 )
724+ continue ; // part of a group that was already processed
685725
686- size_t cluster_offset = lod.size ();
726+ // mark group as processed
727+ partlast[partition[i]] = -1 ;
687728
688- for (size_t j = 0 ; j < m.triangle_count * 3 ; ++j)
689- lod.push_back (meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + j]]);
729+ size_t group_offset = lod.size ();
730+
731+ for (int j = int (i); j >= 0 ; j = partnext[j])
732+ {
733+ const meshopt_Meshlet& m = meshlets[j];
734+
735+ for (size_t k = 0 ; k < m.triangle_count * 3 ; ++k)
736+ lod.push_back (meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + k]]);
737+ }
738+
739+ size_t group_triangles = (lod.size () - group_offset) / 3 ;
690740
741+ // simplify the group, preserving the border vertices
742+ // note: this technically also locks the exterior border; a full mesh analysis (see nanite.cpp / lockBoundary) would work better for some meshes
691743 unsigned int options = meshopt_SimplifyLockBorder | meshopt_SimplifySparse | meshopt_SimplifyErrorAbsolute;
692744
693- float cluster_target_error = 1e-2f * scale;
694- size_t cluster_target = size_t (float (m. triangle_count ) * threshold) * 3 ;
695- float cluster_error = 0 .f ;
696- size_t cluster_size = meshopt_simplify (&lod[cluster_offset ], &lod[cluster_offset ], m. triangle_count * 3 , &mesh.vertices [0 ].px , mesh.vertices .size (), sizeof (Vertex), cluster_target, cluster_target_error , options, &cluster_error );
745+ float group_target_error = 1e-2f * scale;
746+ size_t group_target = size_t (float (group_triangles ) * threshold) * 3 ;
747+ float group_error = 0 .f ;
748+ size_t group_size = meshopt_simplify (&lod[group_offset ], &lod[group_offset ], group_triangles * 3 , &mesh.vertices [0 ].px , mesh.vertices .size (), sizeof (Vertex), group_target, group_target_error , options, &group_error );
697749
698- error = cluster_error > error ? cluster_error : error;
750+ error = group_error > error ? group_error : error;
699751
700- // simplified cluster is available in lod[cluster_offset..cluster_offset + cluster_size ]
701- lod.resize (cluster_offset + cluster_size );
752+ // simplified group is available in lod[group_offset..group_offset + group_size ]
753+ lod.resize (group_offset + group_size );
702754 }
703755
704756 double end = timestamp ();
705757
706- printf (" %-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec, clusterized in %.2f msec\n " ,
758+ printf (" %-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec, clusterized in %.2f msec, partitioned in %.2f msec (%d clusters in %d groups) \n " ,
707759 " SimplifyN" , // N for Nanite
708760 int (mesh.indices .size () / 3 ), int (lod.size () / 3 ),
709761 error / scale * 100 ,
710- (end - middle) * 1000 , (middle - start) * 1000 );
762+ (end - parttime) * 1000 , (middle - start) * 1000 , (parttime - middle) * 1000 ,
763+ int (meshlets.size ()), int (partition_count));
711764}
712765
713766void optimize (const Mesh& mesh, const char * name, void (*optf)(Mesh& mesh))
@@ -1402,7 +1455,7 @@ void processDev(const char* path)
14021455 if (!loadMesh (mesh, path))
14031456 return ;
14041457
1405- meshlets (mesh);
1458+ simplifyClusters (mesh, 0 . 2f );
14061459}
14071460
14081461void processNanite (const char * path)
0 commit comments