Blender V4.3
mesh_compare.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BLI_array.hh"
6#include "BLI_math_base.h"
7#include "BLI_ordered_edge.hh"
8#include "BLI_span.hh"
9
10#include "BKE_attribute.hh"
11#include "BKE_attribute_math.hh"
12#include "BKE_mesh.hh"
13#include "BKE_mesh_mapping.hh"
14
15#include "BKE_mesh_compare.hh"
16
18
19enum class MeshMismatch : int8_t {
20 NumVerts, /* The number of vertices is different. */
21 NumEdges, /* The number of edges is different. */
22 NumCorners, /* The number of corners is different. */
23 NumFaces, /* The number of faces is different. */
24 VertexAttributes, /* Some values of the vertex attributes are different. */
25 EdgeAttributes, /* Some values of the edge attributes are different. */
26 CornerAttributes, /* Some values of the corner attributes are different. */
27 FaceAttributes, /* Some values of the face attributes are different. */
28 EdgeTopology, /* The edge topology is different. */
29 FaceTopology, /* The face topology is different. */
30 Attributes, /* The sets of attribute ids are different. */
31 AttributeTypes, /* Some attributes with the same name have different types. */
32 Indices, /* The meshes are the same up to a change of indices. */
33};
34
35const char *mismatch_to_string(const MeshMismatch &mismatch)
36{
37 switch (mismatch) {
39 return "The number of vertices is different";
41 return "The number of edges is different";
43 return "The number of corners is different";
45 return "The number of faces is different";
47 return "Some values of the vertex attributes are different";
49 return "Some values of the edge attributes are different";
51 return "Some values of the corner attributes are different";
53 return "Some values of the face attributes are different";
55 return "The edge topology is different";
57 return "The face topology is different";
59 return "The sets of attribute ids are different";
61 return "Some attributes with the same name have different types";
63 return "The meshes are the same up to a change of indices";
64 }
66 return "";
67}
68
70 private:
71 void calculate_inverse_map(const Span<int> map, MutableSpan<int> inverted_map)
72 {
73 for (const int i : map.index_range()) {
74 inverted_map[map[i]] = i;
75 }
76 }
77
78 public:
85
86 IndexMapping(const int64_t domain_size)
87 {
88 to_sorted1 = Array<int>(domain_size);
89 to_sorted2 = Array<int>(domain_size);
90 from_sorted1 = Array<int>(domain_size);
91 from_sorted2 = Array<int>(domain_size);
92 set_ids = Array<int>(domain_size);
93 set_sizes = Array<int>(domain_size);
94 std::iota(from_sorted1.begin(), from_sorted1.end(), 0);
95 std::iota(from_sorted2.begin(), from_sorted2.end(), 0);
96 std::iota(to_sorted1.begin(), to_sorted1.end(), 0);
97 std::iota(to_sorted2.begin(), to_sorted2.end(), 0);
98 set_ids.fill(0);
100 }
101
106 {
107 calculate_inverse_map(from_sorted1, to_sorted1);
108 calculate_inverse_map(from_sorted2, to_sorted2);
109 }
110};
111
116template<typename T>
117static void sort_indices(MutableSpan<int> indices, const Span<T> values, const int component_i)
118{
119 /* We need to have an appropriate comparison function, depending on the type. */
120 std::stable_sort(indices.begin(), indices.end(), [&](int i1, int i2) {
121 const T value1 = values[i1];
122 const T value2 = values[i2];
123 if constexpr (is_same_any_v<T, int, float, bool, int8_t, OrderedEdge>) {
124 /* These types are already comparable. */
125 return value1 < value2;
126 }
128 return value1[component_i] < value2[component_i];
129 }
130 if constexpr (std::is_same_v<T, math::Quaternion>) {
131 const float4 value1_quat = float4(value1);
132 const float4 value2_quat = float4(value2);
133 return value1_quat[component_i] < value2_quat[component_i];
134 }
135 if constexpr (std::is_same_v<T, float4x4>) {
136 return value1.base_ptr()[component_i] < value2.base_ptr()[component_i];
137 }
138 if constexpr (std::is_same_v<T, int2>) {
139 for (int i = 0; i < 2; i++) {
140 if (value1[i] != value2[i]) {
141 return value1[i] < value2[i];
142 }
143 }
144 return false;
145 }
146 if constexpr (std::is_same_v<T, ColorGeometry4b>) {
147 for (int i = 0; i < 4; i++) {
148 if (value1[i] != value2[i]) {
149 return value1[i] < value2[i];
150 }
151 }
152 return false;
153 }
154
156 return false;
157 });
158}
159
164 const Span<int> values,
165 const Span<int> values_to_sorted,
166 const Span<int> set_ids)
167{
168 std::stable_sort(indices.begin(), indices.end(), [&](int i1, int i2) {
169 return set_ids[values_to_sorted[values[i1]]] < set_ids[values_to_sorted[values[i2]]];
170 });
171}
172
173/* Sort the elements in each set based on the attribute values. */
174template<typename T>
175static void sort_per_set_based_on_attributes(const Span<int> set_sizes,
176 MutableSpan<int> sorted_to_domain1,
177 MutableSpan<int> sorted_to_domain2,
178 const Span<T> values1,
179 const Span<T> values2,
180 const int component_i)
181{
182 int i = 0;
183 while (i < set_sizes.size()) {
184 const int set_size = set_sizes[i];
185 if (set_size == 1) {
186 /* No need to sort anymore. */
187 i += 1;
188 continue;
189 }
190
191 sort_indices(sorted_to_domain1.slice(IndexRange(i, set_size)), values1, component_i);
192 sort_indices(sorted_to_domain2.slice(IndexRange(i, set_size)), values2, component_i);
193 i += set_size;
194 }
195}
196
197/* Sort the elements in each set based on the set ids of the values. */
198static void sort_per_set_with_id_maps(const Span<int> set_sizes,
199 const Span<int> values1,
200 const Span<int> values2,
201 const Span<int> values1_to_sorted,
202 const Span<int> values2_to_sorted,
203 const Span<int> value_set_ids,
204 MutableSpan<int> sorted_to_domain1,
205 MutableSpan<int> sorted_to_domain2)
206{
207 int i = 0;
208 while (i < sorted_to_domain1.size()) {
209 const int set_size = set_sizes[i];
210 if (set_size == 1) {
211 /* No need to sort anymore. */
212 i += 1;
213 continue;
214 }
215
216 sort_indices_with_id_maps(sorted_to_domain1.slice(IndexRange(i, set_size)),
217 values1,
218 values1_to_sorted,
219 value_set_ids);
220 sort_indices_with_id_maps(sorted_to_domain2.slice(IndexRange(i, set_size)),
221 values2,
222 values2_to_sorted,
223 value_set_ids);
224 i += set_size;
225 }
226}
227
232template<typename T>
233static bool values_different(const T value1,
234 const T value2,
235 const float threshold,
236 const int component_i)
237{
238 if constexpr (is_same_any_v<T, int, int2, bool, int8_t, OrderedEdge, ColorGeometry4b>) {
239 /* These types already have a good implementation. */
240 return value1 != value2;
241 }
242 /* The other types are based on floats. */
243 if constexpr (std::is_same_v<T, float>) {
244 return compare_threshold_relative(value1, value2, threshold);
245 }
246 if constexpr (is_same_any_v<T, float2, float3, ColorGeometry4f>) {
247 return compare_threshold_relative(value1[component_i], value2[component_i], threshold);
248 }
249 if constexpr (std::is_same_v<T, math::Quaternion>) {
250 const float4 value1_f = float4(value1);
251 const float4 value2_f = float4(value2);
252 return compare_threshold_relative(value1_f[component_i], value2_f[component_i], threshold);
253 }
254 if constexpr (std::is_same_v<T, float4x4>) {
256 value1.base_ptr()[component_i], value2.base_ptr()[component_i], threshold);
257 }
259}
260
266template<typename T>
268 const Span<T> values1,
269 const Span<T> values2,
270 const Span<int> sorted_to_values1,
271 MutableSpan<int> sorted_to_values2,
272 const float threshold,
273 const int component_i)
274{
275 /* Due to the way the sorting works, there could be a slightly bigger difference. */
276 const float value_threshold = 5 * threshold;
277 if (set_ids.is_empty()) {
278 return true;
279 }
280 T previous = values1[0];
281 int set_id = 0;
282 for (const int i : values1.index_range()) {
283 const T value1 = values1[sorted_to_values1[i]];
284 const T value2 = values2[sorted_to_values2[i]];
285 if (values_different(value1, value2, value_threshold, component_i)) {
286 /* They should be the same after sorting. */
287 return false;
288 }
289 if ((values_different(previous, value1, value_threshold, component_i) &&
290 values_different(previous, value2, value_threshold, component_i)) ||
291 set_ids[i] == i)
292 {
293 /* Different value, or this was already a different set. */
294 set_id = i;
295 previous = value1;
296 }
297 set_ids[i] = set_id;
298 }
299
300 return true;
301}
302
309 const Span<int> domain_to_values1,
310 const Span<int> domain_to_values2,
311 const Span<int> values1_to_sorted,
312 const Span<int> values2_to_sorted,
313 const Span<int> value_set_ids,
314 const Span<int> sorted_to_domain1,
315 const Span<int> sorted_to_domain2)
316{
317 if (set_ids.is_empty()) {
318 return true;
319 }
320 int previous = value_set_ids[values1_to_sorted[domain_to_values1[sorted_to_domain1[0]]]];
321 int set_id = 0;
322 for (const int i : sorted_to_domain1.index_range()) {
323 const int value_id1 =
324 value_set_ids[values1_to_sorted[domain_to_values1[sorted_to_domain1[i]]]];
325 const int value_id2 =
326 value_set_ids[values2_to_sorted[domain_to_values2[sorted_to_domain2[i]]]];
327 if (value_id1 != value_id2) {
328 /* They should be the same after sorting. */
329 return false;
330 }
331 if (value_id1 != previous || set_ids[i] == i) {
332 /* Different value, or this was already a different set. */
333 set_id = i;
334 previous = value_id1;
335 }
336 set_ids[i] = set_id;
337 }
338
339 return true;
340}
341
345static void update_set_sizes(const Span<int> set_ids, MutableSpan<int> set_sizes)
346{
347 int i = set_ids.size() - 1;
348 while (i >= 0) {
349 /* The id of a set is the index of its first element, so the size can be computed as the index
350 * of the last element minus the id (== index of first element) + 1. */
351 int set_size = i - set_ids[i] + 1;
352 /* Set the set size for each element in the set. */
353 for (int k = i - set_size + 1; k <= i; k++) {
354 set_sizes[k] = set_size;
355 }
356 i -= set_size;
357 }
358}
359
360static void edges_from_vertex_sets(const Span<int2> edges,
361 const Span<int> verts_to_sorted,
362 const Span<int> vertex_set_ids,
364{
365 for (const int i : r_edges.index_range()) {
366 const int2 e = edges[i];
367 r_edges[i] = OrderedEdge(vertex_set_ids[verts_to_sorted[e.x]],
368 vertex_set_ids[verts_to_sorted[e.y]]);
369 }
370}
371
375static bool sort_edges(const Span<int2> edges1,
376 const Span<int2> edges2,
377 const IndexMapping &verts,
378 IndexMapping &edges)
379{
380 /* Need `NoInitialization()` because OrderedEdge is not default constructible. */
381 Array<OrderedEdge> ordered_edges1(edges1.size(), NoInitialization());
382 Array<OrderedEdge> ordered_edges2(edges2.size(), NoInitialization());
383 edges_from_vertex_sets(edges1, verts.to_sorted1, verts.set_ids, ordered_edges1);
384 edges_from_vertex_sets(edges2, verts.to_sorted2, verts.set_ids, ordered_edges2);
385 sort_per_set_based_on_attributes(edges.set_sizes,
386 edges.from_sorted1,
387 edges.from_sorted2,
388 ordered_edges1.as_span(),
389 ordered_edges2.as_span(),
390 0);
391 const bool edges_match = update_set_ids(edges.set_ids,
392 ordered_edges1.as_span(),
393 ordered_edges2.as_span(),
394 edges.from_sorted1,
395 edges.from_sorted2,
396 0,
397 0);
398 if (!edges_match) {
399 return false;
400 }
401 update_set_sizes(edges.set_ids, edges.set_sizes);
402 return true;
403}
404
408static bool sort_corners_based_on_domain(const Span<int> corner_domain1,
409 const Span<int> corner_domain2,
410 const IndexMapping &domain,
411 IndexMapping &corners)
412{
413 sort_per_set_with_id_maps(corners.set_sizes,
414 corner_domain1,
415 corner_domain2,
416 domain.to_sorted1,
417 domain.to_sorted2,
418 domain.set_ids,
419 corners.from_sorted1,
420 corners.from_sorted2);
421 const bool corners_line_up = update_set_ids_with_id_maps(corners.set_ids,
422 corner_domain1,
423 corner_domain2,
424 domain.to_sorted1,
425 domain.to_sorted2,
426 domain.set_ids,
427 corners.from_sorted1,
428 corners.from_sorted2);
429 if (!corners_line_up) {
430 return false;
431 }
432 update_set_sizes(corners.set_ids, corners.set_sizes);
433 return true;
434}
435
436static void calc_smallest_corner_ids(const Span<int> face_offsets,
437 const Span<int> corners_to_sorted,
438 const Span<int> corner_set_ids,
439 MutableSpan<int> smallest_corner_ids)
440{
441 for (const int face_i : smallest_corner_ids.index_range()) {
442 const int face_start = face_offsets[face_i];
443 const int face_end = face_offsets[face_i + 1];
444 int smallest = corner_set_ids[corners_to_sorted[face_start]];
445 const IndexRange corners = IndexRange(face_start, face_end - face_start);
446 for (const int corner_i : corners.drop_front(1)) {
447 const int corner_id = corner_set_ids[corners_to_sorted[corner_i]];
448 if (corner_id < smallest) {
449 smallest = corner_id;
450 }
451 }
452 smallest_corner_ids[face_i] = smallest;
453 }
454}
455
459static bool sort_faces_based_on_corners(const IndexMapping &corners,
460 const Span<int> face_offsets1,
461 const Span<int> face_offsets2,
462 IndexMapping &faces)
463{
464 /* The smallest corner set id, per face. */
465 Array<int> smallest_corner_ids1(faces.from_sorted1.size());
466 Array<int> smallest_corner_ids2(faces.from_sorted2.size());
468 face_offsets1, corners.to_sorted1, corners.set_ids, smallest_corner_ids1);
470 face_offsets2, corners.to_sorted2, corners.set_ids, smallest_corner_ids2);
471 sort_per_set_based_on_attributes(faces.set_sizes,
472 faces.from_sorted1,
473 faces.from_sorted2,
474 smallest_corner_ids1.as_span(),
475 smallest_corner_ids2.as_span(),
476 0);
477 const bool faces_line_up = update_set_ids(faces.set_ids,
478 smallest_corner_ids1.as_span(),
479 smallest_corner_ids2.as_span(),
480 faces.from_sorted1,
481 faces.from_sorted2,
482 0,
483 0);
484 if (!faces_line_up) {
485 return false;
486 }
487 update_set_sizes(faces.set_ids, faces.set_sizes);
488 return true;
489}
490
491/*
492 * The uv selection / pin layers are ignored in the comparisons because
493 * the original flags they replace were ignored as well. Because of the
494 * lazy creation of these layers it would need careful handling of the
495 * test files to compare these layers. For now it has been decided to
496 * skip them.
497 */
498static bool ignored_attribute(const StringRef id)
499{
500 return attribute_name_is_anonymous(id) || id.startswith(".vs.") || id.startswith(".es.") ||
501 id.startswith(".pn.");
502}
503
510static std::optional<MeshMismatch> verify_attributes_compatible(
511 const AttributeAccessor &mesh1_attributes, const AttributeAccessor &mesh2_attributes)
512{
513 Set<StringRefNull> mesh1_attribute_ids = mesh1_attributes.all_ids();
514 Set<StringRefNull> mesh2_attribute_ids = mesh2_attributes.all_ids();
515 mesh1_attribute_ids.remove_if(ignored_attribute);
516 mesh2_attribute_ids.remove_if(ignored_attribute);
517
518 if (mesh1_attribute_ids != mesh2_attribute_ids) {
519 /* Disabled for now due to tests not being up to date. */
520 // return MeshMismatch::Attributes;
521 }
522 for (const StringRef id : mesh1_attribute_ids) {
523 GAttributeReader reader1 = mesh1_attributes.lookup(id);
524 GAttributeReader reader2 = mesh2_attributes.lookup(id);
525 if (!reader1 || !reader2) {
526 /* Necessary because of previous disabled return. */
527 continue;
528 }
529 if (reader1.domain != reader2.domain || reader1.varray.type() != reader2.varray.type()) {
530 return MeshMismatch::AttributeTypes;
531 }
532 }
533 return std::nullopt;
534}
535
541static std::optional<MeshMismatch> sort_domain_using_attributes(
542 const AttributeAccessor &mesh1_attributes,
543 const AttributeAccessor &mesh2_attributes,
544 const AttrDomain domain,
545 const Span<StringRef> excluded_attributes,
546 IndexMapping &maps,
547 const float threshold)
548{
549
550 /* We only need the ids from one mesh, since we know they have the same attributes. */
551 Set<StringRefNull> attribute_ids = mesh1_attributes.all_ids();
552 for (const StringRef name : excluded_attributes) {
553 attribute_ids.remove_as(name);
554 }
555 attribute_ids.remove_if(ignored_attribute);
556
557 for (const StringRef id : attribute_ids) {
558 if (!mesh2_attributes.contains(id)) {
559 /* Only needed right now since some test meshes don't have the same attributes. */
560 return MeshMismatch::Attributes;
561 }
562 GAttributeReader reader1 = mesh1_attributes.lookup(id);
563 GAttributeReader reader2 = mesh2_attributes.lookup(id);
564
565 if (reader1.domain != domain) {
566 /* We only look at attributes of the given domain. */
567 continue;
568 }
569
570 std::optional<MeshMismatch> mismatch = {};
571
572 attribute_math::convert_to_static_type(reader1.varray.type(), [&](auto dummy) {
573 using T = decltype(dummy);
574 const VArraySpan<T> values1 = reader1.varray.typed<T>();
575 const VArraySpan<T> values2 = reader2.varray.typed<T>();
576
577 /* Because sorting of float vectors is not very stable, we do a separate sort per component,
578 * re-computing the set ids each time. */
579 int num_loops = 1;
580 if constexpr (std::is_same_v<T, float2>) {
581 num_loops = 2;
582 }
583 else if constexpr (std::is_same_v<T, float3>) {
584 num_loops = 3;
585 }
586 else if constexpr (is_same_any_v<T, math::Quaternion, ColorGeometry4f>) {
587 num_loops = 4;
588 }
589 else if constexpr (is_same_any_v<T, float4x4>) {
590 num_loops = 16;
591 }
592 for (const int component_i : IndexRange(num_loops)) {
594 maps.set_sizes, maps.from_sorted1, maps.from_sorted2, values1, values2, component_i);
595 const bool attributes_line_up = update_set_ids(maps.set_ids,
596 values1,
597 values2,
598 maps.from_sorted1,
599 maps.from_sorted2,
600 threshold,
601 component_i);
602 if (!attributes_line_up) {
603 switch (domain) {
604 case AttrDomain::Point:
605 mismatch = MeshMismatch::VertexAttributes;
606 return;
607 case AttrDomain::Edge:
608 mismatch = MeshMismatch::EdgeAttributes;
609 return;
610 case AttrDomain::Corner:
611 mismatch = MeshMismatch::CornerAttributes;
612 return;
613 case AttrDomain::Face:
614 mismatch = MeshMismatch::FaceAttributes;
615 return;
616 default:
618 break;
619 }
620 return;
621 }
622 update_set_sizes(maps.set_ids, maps.set_sizes);
623 }
624 });
625
626 if (mismatch) {
627 return mismatch;
628 }
629 }
630 return std::nullopt;
631}
632
633/* When all checks are done, it's possible that some set sizes are still not one e.g, when you have
634 * two loose verts at the same position they are indistinguishable. This makes all the set ID's one
635 * by choosing a match. If possible, the match is chosen such that they have the same unsorted
636 * index.
637 */
638static void make_set_sizes_one(IndexMapping &indices)
639{
640 for (const int sorted_i : indices.set_sizes.index_range()) {
641 if (indices.set_sizes[sorted_i] == 1) {
642 continue;
643 }
644 int match = sorted_i;
645 for (const int other_index :
646 IndexRange(indices.set_ids[sorted_i], indices.set_sizes[sorted_i]))
647 {
648 if (indices.from_sorted1[sorted_i] == indices.from_sorted2[other_index]) {
649 match = other_index;
650 break;
651 }
652 }
653 std::swap(indices.from_sorted2[sorted_i], indices.from_sorted2[match]);
654 for (const int other_set_i :
655 IndexRange(indices.set_ids[sorted_i], indices.set_sizes[sorted_i]))
656 {
657 /* New first element, since this one is now in a new set. */
658 indices.set_ids[other_set_i] = sorted_i + 1;
659 indices.set_sizes[other_set_i] -= 1;
660 }
661 indices.set_ids[sorted_i] = sorted_i;
662 indices.set_sizes[sorted_i] = 1;
663 }
664}
665
666static bool all_set_sizes_one(const Span<int> set_sizes)
667{
668 for (const int size : set_sizes) {
669 if (size != 1) {
670 return false;
671 }
672 }
673 return true;
674}
675
688static std::optional<MeshMismatch> construct_vertex_mapping(const Mesh &mesh1,
689 const Mesh &mesh2,
691 IndexMapping &edges)
692{
693 if (all_set_sizes_one(verts.set_sizes)) {
694 /* The vertices are already in one-to-one correspondence. */
695 return std::nullopt;
696 }
697
698 /* Since we are not yet able to distinguish all vertices based on their attributes alone, we
699 * need to use the edge topology. */
700 Array<int> vert_to_edge_offsets1;
701 Array<int> vert_to_edge_indices1;
702 const GroupedSpan<int> vert_to_edge_map1 = mesh::build_vert_to_edge_map(
703 mesh1.edges(), mesh1.verts_num, vert_to_edge_offsets1, vert_to_edge_indices1);
704 Array<int> vert_to_edge_offsets2;
705 Array<int> vert_to_edge_indices2;
706 const GroupedSpan<int> vert_to_edge_map2 = mesh::build_vert_to_edge_map(
707 mesh2.edges(), mesh2.verts_num, vert_to_edge_offsets2, vert_to_edge_indices2);
708
709 for (const int sorted_i : verts.from_sorted1.index_range()) {
710 const int vert1 = verts.from_sorted1[sorted_i];
711 Vector<int> matching_verts;
712 const Span<int> edges1 = vert_to_edge_map1[vert1];
713 /* Try to find all matching vertices. We know that it will be in the same vertex set, if it
714 * exists. */
715 for (const int index_in_set : IndexRange(verts.set_sizes[sorted_i])) {
716 /* The set id is the index of its first element. */
717 const int vert2 = verts.from_sorted2[verts.set_ids[sorted_i] + index_in_set];
718 const Span<int> edges2 = vert_to_edge_map2[vert2];
719 if (edges1.size() != edges2.size()) {
720 continue;
721 }
722 bool vertex_matches = true;
723 for (const int edge1 : edges1) {
724 bool found_matching_edge = false;
725 for (const int edge2 : edges2) {
726 if (edges.set_ids[edges.to_sorted1[edge1]] == edges.set_ids[edges.to_sorted2[edge2]]) {
727 found_matching_edge = true;
728 break;
729 }
730 }
731 if (!found_matching_edge) {
732 vertex_matches = false;
733 break;
734 }
735 }
736 if (vertex_matches) {
737 matching_verts.append(index_in_set);
738 }
739 }
740
741 if (matching_verts.is_empty()) {
742 return MeshMismatch::EdgeTopology;
743 }
744
745 /* Update the maps. */
746
747 /* In principle, we should make sure that there is exactly one matching vertex. If the mesh is
748 * of good enough quality, that will always be the case. In other cases we just assume that any
749 * choice will be valid. Otherwise, the logic becomes a lot more difficult. Because we want to
750 * test for mesh equality as well, we try to pick the matching vert with the same index. */
751 int index_in_set = matching_verts.first();
752 for (const int other_index_in_set : matching_verts) {
753 const int other_sorted_index = verts.set_ids[sorted_i] + other_index_in_set;
754 if (verts.from_sorted1[sorted_i] == verts.from_sorted2[other_sorted_index]) {
755 index_in_set = other_index_in_set;
756 break;
757 }
758 }
759 std::swap(verts.from_sorted2[sorted_i],
760 verts.from_sorted2[verts.set_ids[sorted_i] + index_in_set]);
761 for (const int other_set_i : IndexRange(verts.set_ids[sorted_i], verts.set_sizes[sorted_i])) {
762 /* New first element, since this one is now in a new set. */
763 verts.set_ids[other_set_i] = sorted_i + 1;
764 verts.set_sizes[other_set_i] -= 1;
765 }
766 verts.set_ids[sorted_i] = sorted_i;
767 verts.set_sizes[sorted_i] = 1;
768 }
769
771
772 verts.recalculate_inverse_maps();
773
774 /* The bijective mapping is now given by composing `verts.to_sorted1` with `verts.from_sorted2`,
775 * or vice versa. Since we don't actually need the mapping (we just care that it exists), we
776 * don't construct it here. */
777
778 return std::nullopt;
779}
780
781std::optional<MeshMismatch> compare_meshes(const Mesh &mesh1,
782 const Mesh &mesh2,
783 const float threshold)
784{
785
786 /* These will be assumed implicitly later on. */
787 if (mesh1.verts_num != mesh2.verts_num) {
788 return MeshMismatch::NumVerts;
789 }
790 if (mesh1.edges_num != mesh2.edges_num) {
791 return MeshMismatch::NumEdges;
792 }
793 if (mesh1.corners_num != mesh2.corners_num) {
794 return MeshMismatch::NumCorners;
795 }
796 if (mesh1.faces_num != mesh2.faces_num) {
797 return MeshMismatch::NumFaces;
798 }
799
800 std::optional<MeshMismatch> mismatch = {};
801
802 const AttributeAccessor mesh1_attributes = mesh1.attributes();
803 const AttributeAccessor mesh2_attributes = mesh2.attributes();
804 mismatch = verify_attributes_compatible(mesh1_attributes, mesh2_attributes);
805 if (mismatch) {
806 return mismatch;
807 }
808
811 mesh1_attributes, mesh2_attributes, AttrDomain::Point, {}, verts, threshold);
812 if (mismatch) {
813 return mismatch;
814 };
815
816 /* We need the maps going the other way as well. */
817 verts.recalculate_inverse_maps();
818
819 IndexMapping edges(mesh1.edges_num);
820 if (!sort_edges(mesh1.edges(), mesh2.edges(), verts, edges)) {
821 return MeshMismatch::EdgeTopology;
822 }
823
825 mesh1_attributes, mesh2_attributes, AttrDomain::Edge, {".edge_verts"}, edges, threshold);
826 if (mismatch) {
827 return mismatch;
828 };
829
830 /* We need the maps going the other way as well. */
831 edges.recalculate_inverse_maps();
832
833 IndexMapping corners(mesh1.corners_num);
834 if (!sort_corners_based_on_domain(mesh1.corner_verts(), mesh2.corner_verts(), verts, corners)) {
835 return MeshMismatch::FaceTopology;
836 }
837
838 if (!sort_corners_based_on_domain(mesh1.corner_edges(), mesh2.corner_edges(), edges, corners)) {
839 return MeshMismatch::FaceTopology;
840 }
841
842 mismatch = sort_domain_using_attributes(mesh1_attributes,
843 mesh2_attributes,
844 AttrDomain::Corner,
845 {".corner_vert", ".corner_edge"},
846 corners,
847 threshold);
848 if (mismatch) {
849 return mismatch;
850 };
851
852 /* We need the maps going the other way as well. */
853 corners.recalculate_inverse_maps();
854
856 if (!sort_faces_based_on_corners(corners, mesh1.face_offsets(), mesh2.face_offsets(), faces)) {
857 return MeshMismatch::FaceTopology;
858 }
859
861 mesh1_attributes, mesh2_attributes, AttrDomain::Face, {}, faces, threshold);
862 if (mismatch) {
863 return mismatch;
864 };
865
866 mismatch = construct_vertex_mapping(mesh1, mesh2, verts, edges);
867 if (mismatch) {
868 return mismatch;
869 }
870
871 /* Now we double check that the other topology maps agree with this vertex mapping. */
872
873 if (!sort_edges(mesh1.edges(), mesh2.edges(), verts, edges)) {
874 return MeshMismatch::EdgeTopology;
875 }
876
877 make_set_sizes_one(edges);
878
879 edges.recalculate_inverse_maps();
880
881 if (!sort_corners_based_on_domain(mesh1.corner_verts(), mesh2.corner_verts(), verts, corners)) {
882 return MeshMismatch::FaceTopology;
883 }
884
885 if (!sort_corners_based_on_domain(mesh1.corner_edges(), mesh2.corner_edges(), edges, corners)) {
886 return MeshMismatch::FaceTopology;
887 }
888
889 make_set_sizes_one(corners);
890
891 corners.recalculate_inverse_maps();
892
893 if (!sort_faces_based_on_corners(corners, mesh1.face_offsets(), mesh2.face_offsets(), faces)) {
894 return MeshMismatch::FaceTopology;
895 }
896
897 make_set_sizes_one(faces);
898
899 /* The meshes are isomorphic, we now just need to determine if they are equal i.e., the indices
900 * are the same. */
901 for (const int sorted_i : verts.from_sorted1.index_range()) {
902 if (verts.from_sorted1[sorted_i] != verts.from_sorted2[sorted_i]) {
903 return MeshMismatch::Indices;
904 }
905 }
906 /* Skip the test for edges, since a lot of tests actually have different edge indices.
907 *TODO: remove this once those tests have been updated. */
908 for (const int sorted_i : corners.from_sorted1.index_range()) {
909 if (corners.from_sorted1[sorted_i] != corners.from_sorted2[sorted_i]) {
910 return MeshMismatch::Indices;
911 }
912 }
913 for (const int sorted_i : faces.from_sorted1.index_range()) {
914 if (faces.from_sorted1[sorted_i] != faces.from_sorted2[sorted_i]) {
915 return MeshMismatch::Indices;
916 }
917 }
918
919 /* No mismatches found. */
920 return std::nullopt;
921}
922
923} // namespace blender::bke::compare_meshes
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
MINLINE bool compare_threshold_relative(float value1, float value2, float thresh)
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
AttributeSet attributes
int64_t size() const
Definition BLI_array.hh:245
Span< T > as_span() const
Definition BLI_array.hh:232
const T * end() const
Definition BLI_array.hh:314
void fill(const T &value) const
Definition BLI_array.hh:261
const T * begin() const
Definition BLI_array.hh:310
constexpr int64_t size() const
Definition BLI_span.hh:494
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:574
constexpr bool is_empty() const
Definition BLI_span.hh:510
constexpr IndexRange index_range() const
Definition BLI_span.hh:671
bool remove_as(const ForwardKey &key)
Definition BLI_set.hh:370
int64_t remove_if(Predicate &&predicate)
Definition BLI_set.hh:496
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
void append(const T &value)
bool is_empty() const
const T & first() const
GAttributeReader lookup(const StringRef attribute_id) const
Set< StringRefNull > all_ids() const
bool contains(const StringRef attribute_id) const
IndexMapping(const int64_t domain_size)
static float verts[][3]
static char faces[256]
static bool sort_faces_based_on_corners(const IndexMapping &corners, const Span< int > face_offsets1, const Span< int > face_offsets2, IndexMapping &faces)
static std::optional< MeshMismatch > construct_vertex_mapping(const Mesh &mesh1, const Mesh &mesh2, IndexMapping &verts, IndexMapping &edges)
const char * mismatch_to_string(const MeshMismatch &mismatch)
static void edges_from_vertex_sets(const Span< int2 > edges, const Span< int > verts_to_sorted, const Span< int > vertex_set_ids, MutableSpan< OrderedEdge > r_edges)
static std::optional< MeshMismatch > sort_domain_using_attributes(const AttributeAccessor &mesh1_attributes, const AttributeAccessor &mesh2_attributes, const AttrDomain domain, const Span< StringRef > excluded_attributes, IndexMapping &maps, const float threshold)
static void update_set_sizes(const Span< int > set_ids, MutableSpan< int > set_sizes)
static bool all_set_sizes_one(const Span< int > set_sizes)
static bool update_set_ids_with_id_maps(MutableSpan< int > set_ids, const Span< int > domain_to_values1, const Span< int > domain_to_values2, const Span< int > values1_to_sorted, const Span< int > values2_to_sorted, const Span< int > value_set_ids, const Span< int > sorted_to_domain1, const Span< int > sorted_to_domain2)
static bool sort_corners_based_on_domain(const Span< int > corner_domain1, const Span< int > corner_domain2, const IndexMapping &domain, IndexMapping &corners)
static void sort_indices_with_id_maps(MutableSpan< int > indices, const Span< int > values, const Span< int > values_to_sorted, const Span< int > set_ids)
static void calc_smallest_corner_ids(const Span< int > face_offsets, const Span< int > corners_to_sorted, const Span< int > corner_set_ids, MutableSpan< int > smallest_corner_ids)
static bool sort_edges(const Span< int2 > edges1, const Span< int2 > edges2, const IndexMapping &verts, IndexMapping &edges)
static void make_set_sizes_one(IndexMapping &indices)
static bool ignored_attribute(const StringRef id)
static bool update_set_ids(MutableSpan< int > set_ids, const Span< T > values1, const Span< T > values2, const Span< int > sorted_to_values1, MutableSpan< int > sorted_to_values2, const float threshold, const int component_i)
static bool values_different(const T value1, const T value2, const float threshold, const int component_i)
std::optional< MeshMismatch > compare_meshes(const Mesh &mesh1, const Mesh &mesh2, float threshold)
Checks if the two meshes are different, returning the type of mismatch if any. Changes in index order...
static void sort_indices(MutableSpan< int > indices, const Span< T > values, const int component_i)
static std::optional< MeshMismatch > verify_attributes_compatible(const AttributeAccessor &mesh1_attributes, const AttributeAccessor &mesh2_attributes)
static void sort_per_set_with_id_maps(const Span< int > set_sizes, const Span< int > values1, const Span< int > values2, const Span< int > values1_to_sorted, const Span< int > values2_to_sorted, const Span< int > value_set_ids, MutableSpan< int > sorted_to_domain1, MutableSpan< int > sorted_to_domain2)
static void sort_per_set_based_on_attributes(const Span< int > set_sizes, MutableSpan< int > sorted_to_domain1, MutableSpan< int > sorted_to_domain2, const Span< T > values1, const Span< T > values2, const int component_i)
bool attribute_name_is_anonymous(const StringRef name)
constexpr bool is_same_any_v
VecBase< float, 4 > float4
__int64 int64_t
Definition stdint.h:89
signed char int8_t
Definition stdint.h:75
int corners_num
int edges_num
int faces_num
int verts_num