Blender V4.5
geometry_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 <algorithm>
6#include <numeric>
7
8#include "BLI_array.hh"
9#include "BLI_math_base.h"
10#include "BLI_ordered_edge.hh"
11#include "BLI_span.hh"
12
14#include "BKE_attribute.hh"
15#include "BKE_attribute_math.hh"
16#include "BKE_mesh.hh"
17#include "BKE_mesh_mapping.hh"
18
20
22
23enum class GeoMismatch : int8_t {
24 NumPoints, /* The number of points is different. */
25 NumEdges, /* The number of edges is different. */
26 NumCorners, /* The number of corners is different. */
27 NumFaces, /* The number of faces is different. */
28 NumCurves, /* The number of curves is different. */
29 PointAttributes, /* Some values of the point attributes are different. */
30 EdgeAttributes, /* Some values of the edge attributes are different. */
31 CornerAttributes, /* Some values of the corner attributes are different. */
32 FaceAttributes, /* Some values of the face attributes are different. */
33 CurveAttributes, /* Some values of the curve attributes are different. */
34 EdgeTopology, /* The edge topology is different. */
35 FaceTopology, /* The face topology is different. */
36 CurveTopology, /* The curve topology is different. */
37 Attributes, /* The sets of attribute ids are different. */
38 AttributeTypes, /* Some attributes with the same name have different types. */
39 Indices, /* The geometries are the same up to a change of indices. */
40};
41
42const char *mismatch_to_string(const GeoMismatch &mismatch)
43{
44 switch (mismatch) {
46 return "The number of points is different";
48 return "The number of edges is different";
50 return "The number of corners is different";
52 return "The number of faces is different";
54 return "The number of curves is different";
56 return "Some values of the point attributes are different";
58 return "Some values of the edge attributes are different";
60 return "Some values of the corner attributes are different";
62 return "Some values of the face attributes are different";
64 return "Some values of the curve attributes are different";
66 return "The edge topology is different";
68 return "The face topology is different";
70 return "The curve topology is different";
72 return "The sets of attribute ids are different";
74 return "Some attributes with the same name have different types";
76 return "The geometries are the same up to a change of indices";
77 }
79 return "";
80}
81
83 private:
84 void calculate_inverse_map(const Span<int> map, MutableSpan<int> inverted_map)
85 {
86 for (const int i : map.index_range()) {
87 inverted_map[map[i]] = i;
88 }
89 }
90
91 public:
98
99 IndexMapping(const int64_t domain_size)
100 {
101 to_sorted1 = Array<int>(domain_size);
102 to_sorted2 = Array<int>(domain_size);
103 from_sorted1 = Array<int>(domain_size);
104 from_sorted2 = Array<int>(domain_size);
105 set_ids = Array<int>(domain_size);
106 set_sizes = Array<int>(domain_size);
107 std::iota(from_sorted1.begin(), from_sorted1.end(), 0);
108 std::iota(from_sorted2.begin(), from_sorted2.end(), 0);
109 std::iota(to_sorted1.begin(), to_sorted1.end(), 0);
110 std::iota(to_sorted2.begin(), to_sorted2.end(), 0);
111 set_ids.fill(0);
112 set_sizes.fill(set_ids.size());
113 }
114
119 {
120 calculate_inverse_map(from_sorted1, to_sorted1);
121 calculate_inverse_map(from_sorted2, to_sorted2);
122 }
123};
124
129template<typename T>
130static void sort_indices(MutableSpan<int> indices, const Span<T> values, const int component_i)
131{
132 /* We need to have an appropriate comparison function, depending on the type. */
133 std::stable_sort(indices.begin(), indices.end(), [&](int i1, int i2) {
134 const T value1 = values[i1];
135 const T value2 = values[i2];
136 if constexpr (is_same_any_v<T, int, float, bool, int8_t, OrderedEdge>) {
137 /* These types are already comparable. */
138 return value1 < value2;
139 }
141 return value1[component_i] < value2[component_i];
142 }
143 if constexpr (std::is_same_v<T, math::Quaternion>) {
144 const float4 value1_quat = float4(value1);
145 const float4 value2_quat = float4(value2);
146 return value1_quat[component_i] < value2_quat[component_i];
147 }
148 if constexpr (std::is_same_v<T, float4x4>) {
149 return value1.base_ptr()[component_i] < value2.base_ptr()[component_i];
150 }
151 if constexpr (std::is_same_v<T, int2>) {
152 for (int i = 0; i < 2; i++) {
153 if (value1[i] != value2[i]) {
154 return value1[i] < value2[i];
155 }
156 }
157 return false;
158 }
159 if constexpr (std::is_same_v<T, ColorGeometry4b>) {
160 for (int i = 0; i < 4; i++) {
161 if (value1[i] != value2[i]) {
162 return value1[i] < value2[i];
163 }
164 }
165 return false;
166 }
167
169 return false;
170 });
171}
172
177 const Span<int> values,
178 const Span<int> values_to_sorted,
179 const Span<int> set_ids)
180{
181 std::stable_sort(indices.begin(), indices.end(), [&](int i1, int i2) {
182 return set_ids[values_to_sorted[values[i1]]] < set_ids[values_to_sorted[values[i2]]];
183 });
184}
185
186/* Sort the elements in each set based on the attribute values. */
187template<typename T>
188static void sort_per_set_based_on_attributes(const Span<int> set_sizes,
189 MutableSpan<int> sorted_to_domain1,
190 MutableSpan<int> sorted_to_domain2,
191 const Span<T> values1,
192 const Span<T> values2,
193 const int component_i)
194{
195 int i = 0;
196 while (i < set_sizes.size()) {
197 const int set_size = set_sizes[i];
198 if (set_size == 1) {
199 /* No need to sort anymore. */
200 i += 1;
201 continue;
202 }
203
204 sort_indices(sorted_to_domain1.slice(IndexRange(i, set_size)), values1, component_i);
205 sort_indices(sorted_to_domain2.slice(IndexRange(i, set_size)), values2, component_i);
206 i += set_size;
207 }
208}
209
210/* Sort the elements in each set based on the set ids of the values. */
211static void sort_per_set_with_id_maps(const Span<int> set_sizes,
212 const Span<int> values1,
213 const Span<int> values2,
214 const Span<int> values1_to_sorted,
215 const Span<int> values2_to_sorted,
216 const Span<int> value_set_ids,
217 MutableSpan<int> sorted_to_domain1,
218 MutableSpan<int> sorted_to_domain2)
219{
220 int i = 0;
221 while (i < sorted_to_domain1.size()) {
222 const int set_size = set_sizes[i];
223 if (set_size == 1) {
224 /* No need to sort anymore. */
225 i += 1;
226 continue;
227 }
228
229 sort_indices_with_id_maps(sorted_to_domain1.slice(IndexRange(i, set_size)),
230 values1,
231 values1_to_sorted,
232 value_set_ids);
233 sort_indices_with_id_maps(sorted_to_domain2.slice(IndexRange(i, set_size)),
234 values2,
235 values2_to_sorted,
236 value_set_ids);
237 i += set_size;
238 }
239}
240
245template<typename T>
246static bool values_different(const T value1,
247 const T value2,
248 const float threshold,
249 const int component_i)
250{
252 /* These types already have a good implementation. */
253 return value1 != value2;
254 }
255 /* The other types are based on floats. */
256 if constexpr (std::is_same_v<T, float>) {
257 return compare_threshold_relative(value1, value2, threshold);
258 }
260 return compare_threshold_relative(value1[component_i], value2[component_i], threshold);
261 }
262 if constexpr (std::is_same_v<T, math::Quaternion>) {
263 const float4 value1_f = float4(value1);
264 const float4 value2_f = float4(value2);
265 return compare_threshold_relative(value1_f[component_i], value2_f[component_i], threshold);
266 }
267 if constexpr (std::is_same_v<T, float4x4>) {
269 value1.base_ptr()[component_i], value2.base_ptr()[component_i], threshold);
270 }
272}
273
279template<typename T>
281 const Span<T> values1,
282 const Span<T> values2,
283 const Span<int> sorted_to_values1,
284 MutableSpan<int> sorted_to_values2,
285 const float threshold,
286 const int component_i)
287{
288 /* Due to the way the sorting works, there could be a slightly bigger difference. */
289 const float value_threshold = 5 * threshold;
290 if (set_ids.is_empty()) {
291 return true;
292 }
293 T previous = values1[0];
294 int set_id = 0;
295 for (const int i : values1.index_range()) {
296 const T value1 = values1[sorted_to_values1[i]];
297 const T value2 = values2[sorted_to_values2[i]];
298 if (values_different(value1, value2, value_threshold, component_i)) {
299 /* They should be the same after sorting. */
300 return false;
301 }
302 if ((values_different(previous, value1, value_threshold, component_i) &&
303 values_different(previous, value2, value_threshold, component_i)) ||
304 set_ids[i] == i)
305 {
306 /* Different value, or this was already a different set. */
307 set_id = i;
308 previous = value1;
309 }
310 set_ids[i] = set_id;
311 }
312
313 return true;
314}
315
322 const Span<int> domain_to_values1,
323 const Span<int> domain_to_values2,
324 const Span<int> values1_to_sorted,
325 const Span<int> values2_to_sorted,
326 const Span<int> value_set_ids,
327 const Span<int> sorted_to_domain1,
328 const Span<int> sorted_to_domain2)
329{
330 if (set_ids.is_empty()) {
331 return true;
332 }
333 int previous = value_set_ids[values1_to_sorted[domain_to_values1[sorted_to_domain1[0]]]];
334 int set_id = 0;
335 for (const int i : sorted_to_domain1.index_range()) {
336 const int value_id1 =
337 value_set_ids[values1_to_sorted[domain_to_values1[sorted_to_domain1[i]]]];
338 const int value_id2 =
339 value_set_ids[values2_to_sorted[domain_to_values2[sorted_to_domain2[i]]]];
340 if (value_id1 != value_id2) {
341 /* They should be the same after sorting. */
342 return false;
343 }
344 if (value_id1 != previous || set_ids[i] == i) {
345 /* Different value, or this was already a different set. */
346 set_id = i;
347 previous = value_id1;
348 }
349 set_ids[i] = set_id;
350 }
351
352 return true;
353}
354
358static void update_set_sizes(const Span<int> set_ids, MutableSpan<int> set_sizes)
359{
360 int i = set_ids.size() - 1;
361 while (i >= 0) {
362 /* The id of a set is the index of its first element, so the size can be computed as the index
363 * of the last element minus the id (== index of first element) + 1. */
364 int set_size = i - set_ids[i] + 1;
365 /* Set the set size for each element in the set. */
366 for (int k = i - set_size + 1; k <= i; k++) {
367 set_sizes[k] = set_size;
368 }
369 i -= set_size;
370 }
371}
372
373static void edges_from_vert_sets(const Span<int2> edges,
374 const Span<int> verts_to_sorted,
375 const Span<int> vert_set_ids,
377{
378 for (const int i : r_edges.index_range()) {
379 const int2 e = edges[i];
380 r_edges[i] = OrderedEdge(vert_set_ids[verts_to_sorted[e.x]],
381 vert_set_ids[verts_to_sorted[e.y]]);
382 }
383}
384
388static bool sort_edges(const Span<int2> edges1,
389 const Span<int2> edges2,
390 const IndexMapping &verts,
391 IndexMapping &edges)
392{
393 /* Need `NoInitialization()` because OrderedEdge is not default constructible. */
394 Array<OrderedEdge> ordered_edges1(edges1.size(), NoInitialization());
395 Array<OrderedEdge> ordered_edges2(edges2.size(), NoInitialization());
396 edges_from_vert_sets(edges1, verts.to_sorted1, verts.set_ids, ordered_edges1);
397 edges_from_vert_sets(edges2, verts.to_sorted2, verts.set_ids, ordered_edges2);
399 edges.from_sorted1,
400 edges.from_sorted2,
401 ordered_edges1.as_span(),
402 ordered_edges2.as_span(),
403 0);
404 const bool edges_match = update_set_ids(edges.set_ids,
405 ordered_edges1.as_span(),
406 ordered_edges2.as_span(),
407 edges.from_sorted1,
408 edges.from_sorted2,
409 0,
410 0);
411 if (!edges_match) {
412 return false;
413 }
414 update_set_sizes(edges.set_ids, edges.set_sizes);
415 return true;
416}
417
421static bool sort_corners_based_on_domain(const Span<int> corner_domain1,
422 const Span<int> corner_domain2,
423 const IndexMapping &domain,
424 IndexMapping &corners)
425{
426 sort_per_set_with_id_maps(corners.set_sizes,
427 corner_domain1,
428 corner_domain2,
429 domain.to_sorted1,
430 domain.to_sorted2,
431 domain.set_ids,
432 corners.from_sorted1,
433 corners.from_sorted2);
434 const bool corners_line_up = update_set_ids_with_id_maps(corners.set_ids,
435 corner_domain1,
436 corner_domain2,
437 domain.to_sorted1,
438 domain.to_sorted2,
439 domain.set_ids,
440 corners.from_sorted1,
441 corners.from_sorted2);
442 if (!corners_line_up) {
443 return false;
444 }
445 update_set_sizes(corners.set_ids, corners.set_sizes);
446 return true;
447}
448
449static void calc_smallest_corner_ids(const Span<int> face_offsets,
450 const Span<int> corners_to_sorted,
451 const Span<int> corner_set_ids,
452 MutableSpan<int> smallest_corner_ids)
453{
454 for (const int face_i : smallest_corner_ids.index_range()) {
455 const int face_start = face_offsets[face_i];
456 const int face_end = face_offsets[face_i + 1];
457 int smallest = corner_set_ids[corners_to_sorted[face_start]];
458 const IndexRange corners = IndexRange(face_start, face_end - face_start);
459 for (const int corner_i : corners.drop_front(1)) {
460 const int corner_id = corner_set_ids[corners_to_sorted[corner_i]];
461 smallest = std::min(corner_id, smallest);
462 }
463 smallest_corner_ids[face_i] = smallest;
464 }
465}
466
470static bool sort_faces_based_on_corners(const IndexMapping &corners,
471 const Span<int> face_offsets1,
472 const Span<int> face_offsets2,
474{
475 /* The smallest corner set id, per face. */
476 Array<int> smallest_corner_ids1(faces.from_sorted1.size());
477 Array<int> smallest_corner_ids2(faces.from_sorted2.size());
479 face_offsets1, corners.to_sorted1, corners.set_ids, smallest_corner_ids1);
481 face_offsets2, corners.to_sorted2, corners.set_ids, smallest_corner_ids2);
483 faces.from_sorted1,
484 faces.from_sorted2,
485 smallest_corner_ids1.as_span(),
486 smallest_corner_ids2.as_span(),
487 0);
488 const bool faces_line_up = update_set_ids(faces.set_ids,
489 smallest_corner_ids1.as_span(),
490 smallest_corner_ids2.as_span(),
491 faces.from_sorted1,
492 faces.from_sorted2,
493 0,
494 0);
495 if (!faces_line_up) {
496 return false;
497 }
498 update_set_sizes(faces.set_ids, faces.set_sizes);
499 return true;
500}
501
502/*
503 * The uv selection / pin layers are ignored in the comparisons because
504 * the original flags they replace were ignored as well. Because of the
505 * lazy creation of these layers it would need careful handling of the
506 * test files to compare these layers. For now it has been decided to
507 * skip them.
508 */
509static bool ignored_attribute(const StringRef id)
510{
511 return attribute_name_is_anonymous(id) || id.startswith(".vs.") || id.startswith(".es.") ||
512 id.startswith(".pn.");
513}
514
521static std::optional<GeoMismatch> verify_attributes_compatible(
522 const AttributeAccessor &attributes1, const AttributeAccessor &attributes2)
523{
524 Set<StringRefNull> attribute_ids1 = attributes1.all_ids();
525 Set<StringRefNull> attribute_ids2 = attributes2.all_ids();
526 attribute_ids1.remove_if(ignored_attribute);
527 attribute_ids2.remove_if(ignored_attribute);
528
529 if (attribute_ids1 != attribute_ids2) {
530 /* Disabled for now due to tests not being up to date. */
531 // return GeoMismatch::Attributes;
532 }
533 for (const StringRef id : attribute_ids1) {
534 GAttributeReader reader1 = attributes1.lookup(id);
535 GAttributeReader reader2 = attributes2.lookup(id);
536 if (!reader1 || !reader2) {
537 /* Necessary because of previous disabled return. */
538 continue;
539 }
540 if (reader1.domain != reader2.domain || reader1.varray.type() != reader2.varray.type()) {
542 }
543 }
544 return std::nullopt;
545}
546
552static std::optional<GeoMismatch> sort_domain_using_attributes(
553 const AttributeAccessor &attributes1,
554 const AttributeAccessor &attributes2,
555 const AttrDomain domain,
556 const Span<StringRef> excluded_attributes,
557 IndexMapping &maps,
558 const float threshold)
559{
560
561 /* We only need the ids from one geometry, since we know they have the same attributes. */
562 Set<StringRefNull> attribute_ids = attributes1.all_ids();
563 for (const StringRef name : excluded_attributes) {
564 attribute_ids.remove_as(name);
565 }
566 attribute_ids.remove_if(ignored_attribute);
567
568 for (const StringRef id : attribute_ids) {
569 if (!attributes2.contains(id)) {
570 /* Only needed right now since some test meshes don't have the same attributes. */
572 }
573 GAttributeReader reader1 = attributes1.lookup(id);
574 GAttributeReader reader2 = attributes2.lookup(id);
575
576 if (reader1.domain != domain) {
577 /* We only look at attributes of the given domain. */
578 continue;
579 }
580
581 std::optional<GeoMismatch> mismatch = {};
582
583 attribute_math::convert_to_static_type(reader1.varray.type(), [&](auto dummy) {
584 using T = decltype(dummy);
585 const VArraySpan<T> values1 = reader1.varray.typed<T>();
586 const VArraySpan<T> values2 = reader2.varray.typed<T>();
587
588 /* Because sorting of float vectors is not very stable, we do a separate sort per component,
589 * re-computing the set ids each time. */
590 int num_loops = 1;
591 if constexpr (std::is_same_v<T, float2>) {
592 num_loops = 2;
593 }
594 else if constexpr (std::is_same_v<T, float3>) {
595 num_loops = 3;
596 }
598 num_loops = 4;
599 }
600 else if constexpr (is_same_any_v<T, float4x4>) {
601 num_loops = 16;
602 }
603 for (const int component_i : IndexRange(num_loops)) {
605 maps.set_sizes, maps.from_sorted1, maps.from_sorted2, values1, values2, component_i);
606 const bool attributes_line_up = update_set_ids(maps.set_ids,
607 values1,
608 values2,
609 maps.from_sorted1,
610 maps.from_sorted2,
611 threshold,
612 component_i);
613 if (!attributes_line_up) {
614 switch (domain) {
617 return;
618 case AttrDomain::Edge:
620 return;
623 return;
624 case AttrDomain::Face:
626 return;
629 return;
630 default:
632 break;
633 }
634 return;
635 }
637 }
638 });
639
640 if (mismatch) {
641 return mismatch;
642 }
643 }
644 return std::nullopt;
645}
646
647/* When all checks are done, it's possible that some set sizes are still not one e.g, when you have
648 * two loose verts at the same position they are indistinguishable. This makes all the set ID's one
649 * by choosing a match. If possible, the match is chosen such that they have the same unsorted
650 * index.
651 */
653{
654 for (const int sorted_i : indices.set_sizes.index_range()) {
655 if (indices.set_sizes[sorted_i] == 1) {
656 continue;
657 }
658 int match = sorted_i;
659 for (const int other_index :
660 IndexRange(indices.set_ids[sorted_i], indices.set_sizes[sorted_i]))
661 {
662 if (indices.from_sorted1[sorted_i] == indices.from_sorted2[other_index]) {
663 match = other_index;
664 break;
665 }
666 }
667 std::swap(indices.from_sorted2[sorted_i], indices.from_sorted2[match]);
668 for (const int other_set_i :
669 IndexRange(indices.set_ids[sorted_i], indices.set_sizes[sorted_i]))
670 {
671 /* New first element, since this one is now in a new set. */
672 indices.set_ids[other_set_i] = sorted_i + 1;
673 indices.set_sizes[other_set_i] -= 1;
674 }
675 indices.set_ids[sorted_i] = sorted_i;
676 indices.set_sizes[sorted_i] = 1;
677 }
678}
679
680static bool all_set_sizes_one(const Span<int> set_sizes)
681{
682 for (const int size : set_sizes) {
683 if (size != 1) {
684 return false;
685 }
686 }
687 return true;
688}
689
702static std::optional<GeoMismatch> construct_vert_mapping(const Mesh &mesh1,
703 const Mesh &mesh2,
705 IndexMapping &edges)
706{
707 if (all_set_sizes_one(verts.set_sizes)) {
708 /* The vertices are already in one-to-one correspondence. */
709 return std::nullopt;
710 }
711
712 /* Since we are not yet able to distinguish all vertices based on their attributes alone, we
713 * need to use the edge topology. */
714 Array<int> vert_to_edge_offsets1;
715 Array<int> vert_to_edge_indices1;
716 const GroupedSpan<int> vert_to_edge_map1 = mesh::build_vert_to_edge_map(
717 mesh1.edges(), mesh1.verts_num, vert_to_edge_offsets1, vert_to_edge_indices1);
718 Array<int> vert_to_edge_offsets2;
719 Array<int> vert_to_edge_indices2;
720 const GroupedSpan<int> vert_to_edge_map2 = mesh::build_vert_to_edge_map(
721 mesh2.edges(), mesh2.verts_num, vert_to_edge_offsets2, vert_to_edge_indices2);
722
723 for (const int sorted_i : verts.from_sorted1.index_range()) {
724 const int vert1 = verts.from_sorted1[sorted_i];
725 Vector<int> matching_verts;
726 const Span<int> edges1 = vert_to_edge_map1[vert1];
727 /* Try to find all matching vertices. We know that it will be in the same vertex set, if it
728 * exists. */
729 for (const int index_in_set : IndexRange(verts.set_sizes[sorted_i])) {
730 /* The set id is the index of its first element. */
731 const int vert2 = verts.from_sorted2[verts.set_ids[sorted_i] + index_in_set];
732 const Span<int> edges2 = vert_to_edge_map2[vert2];
733 if (edges1.size() != edges2.size()) {
734 continue;
735 }
736 bool vert_matches = true;
737 for (const int edge1 : edges1) {
738 bool found_matching_edge = false;
739 for (const int edge2 : edges2) {
740 if (edges.set_ids[edges.to_sorted1[edge1]] == edges.set_ids[edges.to_sorted2[edge2]]) {
741 found_matching_edge = true;
742 break;
743 }
744 }
745 if (!found_matching_edge) {
746 vert_matches = false;
747 break;
748 }
749 }
750 if (vert_matches) {
751 matching_verts.append(index_in_set);
752 }
753 }
754
755 if (matching_verts.is_empty()) {
757 }
758
759 /* Update the maps. */
760
761 /* In principle, we should make sure that there is exactly one matching vertex. If the mesh is
762 * of good enough quality, that will always be the case. In other cases we just assume that any
763 * choice will be valid. Otherwise, the logic becomes a lot more difficult. Because we want to
764 * test for mesh equality as well, we try to pick the matching vert with the same index. */
765 int index_in_set = matching_verts.first();
766 for (const int other_index_in_set : matching_verts) {
767 const int other_sorted_index = verts.set_ids[sorted_i] + other_index_in_set;
768 if (verts.from_sorted1[sorted_i] == verts.from_sorted2[other_sorted_index]) {
769 index_in_set = other_index_in_set;
770 break;
771 }
772 }
773 std::swap(verts.from_sorted2[sorted_i],
774 verts.from_sorted2[verts.set_ids[sorted_i] + index_in_set]);
775 for (const int other_set_i : IndexRange(verts.set_ids[sorted_i], verts.set_sizes[sorted_i])) {
776 /* New first element, since this one is now in a new set. */
777 verts.set_ids[other_set_i] = sorted_i + 1;
778 verts.set_sizes[other_set_i] -= 1;
779 }
780 verts.set_ids[sorted_i] = sorted_i;
781 verts.set_sizes[sorted_i] = 1;
782 }
783
785
786 verts.recalculate_inverse_maps();
787
788 /* The bijective mapping is now given by composing `verts.to_sorted1` with `verts.from_sorted2`,
789 * or vice versa. Since we don't actually need the mapping (we just care that it exists), we
790 * don't construct it here. */
791
792 return std::nullopt;
793}
794
795std::optional<GeoMismatch> compare_meshes(const Mesh &mesh1,
796 const Mesh &mesh2,
797 const float threshold)
798{
799
800 /* These will be assumed implicitly later on. */
801 if (mesh1.verts_num != mesh2.verts_num) {
803 }
804 if (mesh1.edges_num != mesh2.edges_num) {
806 }
807 if (mesh1.corners_num != mesh2.corners_num) {
809 }
810 if (mesh1.faces_num != mesh2.faces_num) {
812 }
813
814 std::optional<GeoMismatch> mismatch = {};
815
816 const AttributeAccessor mesh1_attributes = mesh1.attributes();
817 const AttributeAccessor mesh2_attributes = mesh2.attributes();
818 mismatch = verify_attributes_compatible(mesh1_attributes, mesh2_attributes);
819 if (mismatch) {
820 return mismatch;
821 }
822
825 mesh1_attributes, mesh2_attributes, AttrDomain::Point, {}, verts, threshold);
826 if (mismatch) {
827 return mismatch;
828 }
829
830 /* We need the maps going the other way as well. */
831 verts.recalculate_inverse_maps();
832
833 IndexMapping edges(mesh1.edges_num);
834 if (!sort_edges(mesh1.edges(), mesh2.edges(), verts, edges)) {
836 }
837
839 mesh1_attributes, mesh2_attributes, AttrDomain::Edge, {".edge_verts"}, edges, threshold);
840 if (mismatch) {
841 return mismatch;
842 };
843
844 /* We need the maps going the other way as well. */
846
847 IndexMapping corners(mesh1.corners_num);
848 if (!sort_corners_based_on_domain(mesh1.corner_verts(), mesh2.corner_verts(), verts, corners)) {
850 }
851
852 if (!sort_corners_based_on_domain(mesh1.corner_edges(), mesh2.corner_edges(), edges, corners)) {
854 }
855
856 mismatch = sort_domain_using_attributes(mesh1_attributes,
857 mesh2_attributes,
859 {".corner_vert", ".corner_edge"},
860 corners,
861 threshold);
862 if (mismatch) {
863 return mismatch;
864 };
865
866 /* We need the maps going the other way as well. */
867 corners.recalculate_inverse_maps();
868
870 if (!sort_faces_based_on_corners(corners, mesh1.face_offsets(), mesh2.face_offsets(), faces)) {
872 }
873
875 mesh1_attributes, mesh2_attributes, AttrDomain::Face, {}, faces, threshold);
876 if (mismatch) {
877 return mismatch;
878 };
879
880 mismatch = construct_vert_mapping(mesh1, mesh2, verts, edges);
881 if (mismatch) {
882 return mismatch;
883 }
884
885 /* Now we double check that the other topology maps agree with this vertex mapping. */
886
887 if (!sort_edges(mesh1.edges(), mesh2.edges(), verts, edges)) {
889 }
890
891 make_set_sizes_one(edges);
892
894
895 if (!sort_corners_based_on_domain(mesh1.corner_verts(), mesh2.corner_verts(), verts, corners)) {
897 }
898
899 if (!sort_corners_based_on_domain(mesh1.corner_edges(), mesh2.corner_edges(), edges, corners)) {
901 }
902
903 make_set_sizes_one(corners);
904
905 corners.recalculate_inverse_maps();
906
907 if (!sort_faces_based_on_corners(corners, mesh1.face_offsets(), mesh2.face_offsets(), faces)) {
909 }
910
912
913 /* The meshes are isomorphic, we now just need to determine if they are equal i.e., the indices
914 * are the same. */
915 for (const int sorted_i : verts.from_sorted1.index_range()) {
916 if (verts.from_sorted1[sorted_i] != verts.from_sorted2[sorted_i]) {
918 }
919 }
920 /* Skip the test for edges, since a lot of tests actually have different edge indices.
921 *TODO: remove this once those tests have been updated. */
922 for (const int sorted_i : corners.from_sorted1.index_range()) {
923 if (corners.from_sorted1[sorted_i] != corners.from_sorted2[sorted_i]) {
925 }
926 }
927 for (const int sorted_i : faces.from_sorted1.index_range()) {
928 if (faces.from_sorted1[sorted_i] != faces.from_sorted2[sorted_i]) {
930 }
931 }
932
933 /* No mismatches found. */
934 return std::nullopt;
935}
936
940static bool sort_curves(const OffsetIndices<int> offset_indices1,
941 const OffsetIndices<int> offset_indices2,
943{
944 Array<int> curve_point_counts1(offset_indices1.size());
945 Array<int> curve_point_counts2(offset_indices2.size());
947 offset_indices1, offset_indices1.index_range(), curve_point_counts1.as_mutable_span());
949 offset_indices2, offset_indices2.index_range(), curve_point_counts2.as_mutable_span());
951 curves.from_sorted1,
952 curves.from_sorted2,
953 curve_point_counts1.as_span(),
954 curve_point_counts2.as_span(),
955 0);
956 const bool curves_sizes_match = update_set_ids(curves.set_ids,
957 curve_point_counts1.as_span(),
958 curve_point_counts2.as_span(),
959 curves.from_sorted1,
960 curves.from_sorted2,
961 0,
962 0);
963 if (!curves_sizes_match) {
964 return false;
965 }
966 update_set_sizes(curves.set_ids, curves.set_sizes);
967 return true;
968}
969
970std::optional<GeoMismatch> compare_curves(const CurvesGeometry &curves1,
971 const CurvesGeometry &curves2,
972 const float threshold)
973{
974 /* These will be assumed implicitly later on. */
975 if (curves1.points_num() != curves2.points_num()) {
977 }
978 if (curves1.curves_num() != curves2.curves_num()) {
980 }
981
982 std::optional<GeoMismatch> mismatch = {};
983
984 const AttributeAccessor curves1_attributes = curves1.attributes();
985 const AttributeAccessor curves2_attributes = curves2.attributes();
986 mismatch = verify_attributes_compatible(curves1_attributes, curves2_attributes);
987 if (mismatch) {
988 return mismatch;
989 }
990
991 IndexMapping points(curves1.points_num());
993 curves1_attributes, curves2_attributes, AttrDomain::Point, {}, points, threshold);
994 if (mismatch) {
995 return mismatch;
996 }
997
998 IndexMapping curves(curves1.curves_num());
999 if (!sort_curves(curves1.offsets(), curves2.offsets(), curves)) {
1001 }
1002
1004 curves1_attributes, curves2_attributes, AttrDomain::Curve, {}, curves, threshold);
1005 if (mismatch) {
1006 return mismatch;
1007 }
1008
1009 for (const int sorted_i : points.from_sorted1.index_range()) {
1010 if (points.from_sorted1[sorted_i] != points.from_sorted2[sorted_i]) {
1011 return GeoMismatch::Indices;
1012 }
1013 }
1014
1015 for (const int sorted_i : curves.from_sorted1.index_range()) {
1016 if (curves.from_sorted1[sorted_i] != curves.from_sorted2[sorted_i]) {
1017 return GeoMismatch::Indices;
1018 }
1019 }
1020
1021 /* No mismatches found. */
1022 return std::nullopt;
1023}
1024
1025} // namespace blender::bke::compare_geometry
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE bool compare_threshold_relative(float value1, float value2, float thresh)
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
long long int int64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
IndexRange index_range() const
Definition BLI_array.hh:349
AttributeSet attributes
Span< T > as_span() const
Definition BLI_array.hh:232
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:237
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:573
constexpr bool is_empty() const
Definition BLI_span.hh:509
constexpr IndexRange index_range() const
Definition BLI_span.hh:670
bool remove_as(const ForwardKey &key)
Definition BLI_set.hh:389
int64_t remove_if(Predicate &&predicate)
Definition BLI_set.hh:515
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
void append(const T &value)
bool is_empty() const
const T & first() const
bool contains(StringRef attribute_id) const
GAttributeReader lookup(const StringRef attribute_id) const
Set< StringRefNull > all_ids() const
AttributeAccessor attributes() const
static ushort indices[]
static float verts[][3]
VecBase< float, 4 > float4
#define T
static char faces[256]
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
static bool sort_curves(const OffsetIndices< int > offset_indices1, const OffsetIndices< int > offset_indices2, IndexMapping &curves)
std::optional< GeoMismatch > 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 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 void edges_from_vert_sets(const Span< int2 > edges, const Span< int > verts_to_sorted, const Span< int > vert_set_ids, MutableSpan< OrderedEdge > r_edges)
static void sort_indices(MutableSpan< int > indices, const Span< T > values, const int component_i)
static std::optional< GeoMismatch > verify_attributes_compatible(const AttributeAccessor &attributes1, const AttributeAccessor &attributes2)
static bool sort_corners_based_on_domain(const Span< int > corner_domain1, const Span< int > corner_domain2, const IndexMapping &domain, IndexMapping &corners)
static bool ignored_attribute(const StringRef id)
static void make_set_sizes_one(IndexMapping &indices)
static void update_set_sizes(const Span< int > set_ids, MutableSpan< int > set_sizes)
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)
static bool sort_edges(const Span< int2 > edges1, const Span< int2 > edges2, const IndexMapping &verts, IndexMapping &edges)
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 all_set_sizes_one(const Span< int > set_sizes)
static std::optional< GeoMismatch > construct_vert_mapping(const Mesh &mesh1, const Mesh &mesh2, IndexMapping &verts, IndexMapping &edges)
std::optional< GeoMismatch > compare_curves(const CurvesGeometry &curves1, const CurvesGeometry &curves2, float threshold)
Checks if the two curves geometries are different, returning the type of mismatch if any....
static bool values_different(const T value1, const T value2, const float threshold, const int component_i)
static std::optional< GeoMismatch > sort_domain_using_attributes(const AttributeAccessor &attributes1, const AttributeAccessor &attributes2, const AttrDomain domain, const Span< StringRef > excluded_attributes, IndexMapping &maps, const float threshold)
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 bool sort_faces_based_on_corners(const IndexMapping &corners, const Span< int > face_offsets1, const Span< int > face_offsets2, IndexMapping &faces)
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 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)
const char * mismatch_to_string(const GeoMismatch &mismatch)
GroupedSpan< int > build_vert_to_edge_map(Span< int2 > edges, int verts_num, Array< int > &r_offsets, Array< int > &r_indices)
bool attribute_name_is_anonymous(const StringRef name)
void copy_group_sizes(OffsetIndices< int > offsets, const IndexMask &mask, MutableSpan< int > sizes)
constexpr bool is_same_any_v
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
int corners_num
int edges_num
int faces_num
int verts_num
i
Definition text_draw.cc:230