Blender V5.0
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
21#include "DNA_curve_types.h"
22#include "DNA_lattice_types.h"
23
25
26enum class GeoMismatch : int8_t {
27 NumPoints, /* The number of points is different. */
28 NumEdges, /* The number of edges is different. */
29 NumCorners, /* The number of corners is different. */
30 NumFaces, /* The number of faces is different. */
31 NumCurves, /* The number of curves is different. */
32 PointAttributes, /* Some values of the point attributes are different. */
33 EdgeAttributes, /* Some values of the edge attributes are different. */
34 CornerAttributes, /* Some values of the corner attributes are different. */
35 FaceAttributes, /* Some values of the face attributes are different. */
36 CurveAttributes, /* Some values of the curve attributes are different. */
37 EdgeTopology, /* The edge topology is different. */
38 FaceTopology, /* The face topology is different. */
39 CurveTopology, /* The curve topology is different. */
40 Attributes, /* The sets of attribute ids are different. */
41 AttributeTypes, /* Some attributes with the same name have different types. */
42 Indices, /* The geometries are the same up to a change of indices. */
43};
44
45const char *mismatch_to_string(const GeoMismatch &mismatch)
46{
47 switch (mismatch) {
49 return "The number of points is different";
51 return "The number of edges is different";
53 return "The number of corners is different";
55 return "The number of faces is different";
57 return "The number of curves is different";
59 return "Some values of the point attributes are different";
61 return "Some values of the edge attributes are different";
63 return "Some values of the corner attributes are different";
65 return "Some values of the face attributes are different";
67 return "Some values of the curve attributes are different";
69 return "The edge topology is different";
71 return "The face topology is different";
73 return "The curve topology is different";
75 return "The sets of attribute ids are different";
77 return "Some attributes with the same name have different types";
79 return "The geometries are the same up to a change of indices";
80 }
82 return "";
83}
84
86 private:
87 void calculate_inverse_map(const Span<int> map, MutableSpan<int> inverted_map)
88 {
89 for (const int i : map.index_range()) {
90 inverted_map[map[i]] = i;
91 }
92 }
93
94 public:
101
102 IndexMapping(const int64_t domain_size)
103 {
104 to_sorted1 = Array<int>(domain_size);
105 to_sorted2 = Array<int>(domain_size);
106 from_sorted1 = Array<int>(domain_size);
107 from_sorted2 = Array<int>(domain_size);
108 set_ids = Array<int>(domain_size);
109 set_sizes = Array<int>(domain_size);
110 std::iota(from_sorted1.begin(), from_sorted1.end(), 0);
111 std::iota(from_sorted2.begin(), from_sorted2.end(), 0);
112 std::iota(to_sorted1.begin(), to_sorted1.end(), 0);
113 std::iota(to_sorted2.begin(), to_sorted2.end(), 0);
114 set_ids.fill(0);
115 set_sizes.fill(set_ids.size());
116 }
117
122 {
123 calculate_inverse_map(from_sorted1, to_sorted1);
124 calculate_inverse_map(from_sorted2, to_sorted2);
125 }
126};
127
132template<typename T>
133static void sort_indices(MutableSpan<int> indices, const Span<T> values, const int component_i)
134{
135 /* We need to have an appropriate comparison function, depending on the type. */
136 std::stable_sort(indices.begin(), indices.end(), [&](int i1, int i2) {
137 const T value1 = values[i1];
138 const T value2 = values[i2];
139 if constexpr (is_same_any_v<T, int, float, bool, int8_t, OrderedEdge>) {
140 /* These types are already comparable. */
141 return value1 < value2;
142 }
144 return value1[component_i] < value2[component_i];
145 }
146 if constexpr (std::is_same_v<T, math::Quaternion>) {
147 const float4 value1_quat = float4(value1);
148 const float4 value2_quat = float4(value2);
149 return value1_quat[component_i] < value2_quat[component_i];
150 }
151 if constexpr (std::is_same_v<T, float4x4>) {
152 return value1.base_ptr()[component_i] < value2.base_ptr()[component_i];
153 }
154 if constexpr (std::is_same_v<T, int2>) {
155 for (int i = 0; i < 2; i++) {
156 if (value1[i] != value2[i]) {
157 return value1[i] < value2[i];
158 }
159 }
160 return false;
161 }
162 if constexpr (std::is_same_v<T, ColorGeometry4b>) {
163 for (int i = 0; i < 4; i++) {
164 if (value1[i] != value2[i]) {
165 return value1[i] < value2[i];
166 }
167 }
168 return false;
169 }
170
172 return false;
173 });
174}
175
180 const Span<int> values,
181 const Span<int> values_to_sorted,
182 const Span<int> set_ids)
183{
184 std::stable_sort(indices.begin(), indices.end(), [&](int i1, int i2) {
185 return set_ids[values_to_sorted[values[i1]]] < set_ids[values_to_sorted[values[i2]]];
186 });
187}
188
189/* Sort the elements in each set based on the attribute values. */
190template<typename T>
191static void sort_per_set_based_on_attributes(const Span<int> set_sizes,
192 MutableSpan<int> sorted_to_domain1,
193 MutableSpan<int> sorted_to_domain2,
194 const Span<T> values1,
195 const Span<T> values2,
196 const int component_i)
197{
198 int i = 0;
199 while (i < set_sizes.size()) {
200 const int set_size = set_sizes[i];
201 if (set_size == 1) {
202 /* No need to sort anymore. */
203 i += 1;
204 continue;
205 }
206
207 sort_indices(sorted_to_domain1.slice(IndexRange(i, set_size)), values1, component_i);
208 sort_indices(sorted_to_domain2.slice(IndexRange(i, set_size)), values2, component_i);
209 i += set_size;
210 }
211}
212
213/* Sort the elements in each set based on the set ids of the values. */
214static void sort_per_set_with_id_maps(const Span<int> set_sizes,
215 const Span<int> values1,
216 const Span<int> values2,
217 const Span<int> values1_to_sorted,
218 const Span<int> values2_to_sorted,
219 const Span<int> value_set_ids,
220 MutableSpan<int> sorted_to_domain1,
221 MutableSpan<int> sorted_to_domain2)
222{
223 int i = 0;
224 while (i < sorted_to_domain1.size()) {
225 const int set_size = set_sizes[i];
226 if (set_size == 1) {
227 /* No need to sort anymore. */
228 i += 1;
229 continue;
230 }
231
232 sort_indices_with_id_maps(sorted_to_domain1.slice(IndexRange(i, set_size)),
233 values1,
234 values1_to_sorted,
235 value_set_ids);
236 sort_indices_with_id_maps(sorted_to_domain2.slice(IndexRange(i, set_size)),
237 values2,
238 values2_to_sorted,
239 value_set_ids);
240 i += set_size;
241 }
242}
243
248template<typename T>
249static bool values_different(const T value1,
250 const T value2,
251 const float threshold,
252 const int component_i)
253{
255 /* These types already have a good implementation. */
256 return value1 != value2;
257 }
258 /* The other types are based on floats. */
259 if constexpr (std::is_same_v<T, float>) {
260 return compare_threshold_relative(value1, value2, threshold);
261 }
262
263 /* GCC 15.x triggers an array-bounds warning unless `component_i` is assumed to be in range. */
264#if (defined(__GNUC__) && (__GNUC__ >= 15) && !defined(__clang__))
265# define ASSERT_AND_ASSUME(expr) \
266 BLI_assert(expr); \
267 [[assume(expr)]];
268#else
269# define ASSERT_AND_ASSUME(expr) BLI_assert(expr);
270#endif
271
272 if constexpr (is_same_any_v<T, float2>) {
273 ASSERT_AND_ASSUME(component_i >= 0 && component_i < 2);
274 return compare_threshold_relative(value1[component_i], value2[component_i], threshold);
275 }
276 if constexpr (is_same_any_v<T, float3>) {
277 ASSERT_AND_ASSUME(component_i >= 0 && component_i < 3);
278 return compare_threshold_relative(value1[component_i], value2[component_i], threshold);
279 }
280 if constexpr (is_same_any_v<T, ColorGeometry4f>) {
281 ASSERT_AND_ASSUME(component_i >= 0 && component_i < 4);
282 return compare_threshold_relative(value1[component_i], value2[component_i], threshold);
283 }
284 if constexpr (std::is_same_v<T, math::Quaternion>) {
285 ASSERT_AND_ASSUME(component_i >= 0 && component_i < 4);
286 const float4 value1_f = float4(value1);
287 const float4 value2_f = float4(value2);
288 return compare_threshold_relative(value1_f[component_i], value2_f[component_i], threshold);
289 }
290 if constexpr (std::is_same_v<T, float4x4>) {
291 ASSERT_AND_ASSUME(component_i >= 0 && component_i < 4);
293 value1.base_ptr()[component_i], value2.base_ptr()[component_i], threshold);
294 }
295
296#undef ASSERT_AND_ASSUME
297
299}
300
306template<typename T>
308 const Span<T> values1,
309 const Span<T> values2,
310 const Span<int> sorted_to_values1,
311 const Span<int> sorted_to_values2,
312 const float threshold,
313 const int component_i)
314{
315 /* Due to the way the sorting works, there could be a slightly bigger difference. */
316 const float value_threshold = 5 * threshold;
317 if (set_ids.is_empty()) {
318 return true;
319 }
320 T previous = values1[0];
321 int set_id = 0;
322 for (const int i : values1.index_range()) {
323 const T value1 = values1[sorted_to_values1[i]];
324 const T value2 = values2[sorted_to_values2[i]];
325 if (values_different(value1, value2, value_threshold, component_i)) {
326 /* They should be the same after sorting. */
327 return false;
328 }
329 if ((values_different(previous, value1, value_threshold, component_i) &&
330 values_different(previous, value2, value_threshold, component_i)) ||
331 set_ids[i] == i)
332 {
333 /* Different value, or this was already a different set. */
334 set_id = i;
335 previous = value1;
336 }
337 set_ids[i] = set_id;
338 }
339
340 return true;
341}
342
349 const Span<int> domain_to_values1,
350 const Span<int> domain_to_values2,
351 const Span<int> values1_to_sorted,
352 const Span<int> values2_to_sorted,
353 const Span<int> value_set_ids,
354 const Span<int> sorted_to_domain1,
355 const Span<int> sorted_to_domain2)
356{
357 if (set_ids.is_empty()) {
358 return true;
359 }
360 int previous = value_set_ids[values1_to_sorted[domain_to_values1[sorted_to_domain1[0]]]];
361 int set_id = 0;
362 for (const int i : sorted_to_domain1.index_range()) {
363 const int value_id1 =
364 value_set_ids[values1_to_sorted[domain_to_values1[sorted_to_domain1[i]]]];
365 const int value_id2 =
366 value_set_ids[values2_to_sorted[domain_to_values2[sorted_to_domain2[i]]]];
367 if (value_id1 != value_id2) {
368 /* They should be the same after sorting. */
369 return false;
370 }
371 if (value_id1 != previous || set_ids[i] == i) {
372 /* Different value, or this was already a different set. */
373 set_id = i;
374 previous = value_id1;
375 }
376 set_ids[i] = set_id;
377 }
378
379 return true;
380}
381
385static void update_set_sizes(const Span<int> set_ids, MutableSpan<int> set_sizes)
386{
387 int i = set_ids.size() - 1;
388 while (i >= 0) {
389 /* The id of a set is the index of its first element, so the size can be computed as the index
390 * of the last element minus the id (== index of first element) + 1. */
391 int set_size = i - set_ids[i] + 1;
392 /* Set the set size for each element in the set. */
393 for (int k = i - set_size + 1; k <= i; k++) {
394 set_sizes[k] = set_size;
395 }
396 i -= set_size;
397 }
398}
399
400static void edges_from_vert_sets(const Span<int2> edges,
401 const Span<int> verts_to_sorted,
402 const Span<int> vert_set_ids,
404{
405 for (const int i : r_edges.index_range()) {
406 const int2 e = edges[i];
407 r_edges[i] = OrderedEdge(vert_set_ids[verts_to_sorted[e.x]],
408 vert_set_ids[verts_to_sorted[e.y]]);
409 }
410}
411
415static bool sort_edges(const Span<int2> edges1,
416 const Span<int2> edges2,
417 const IndexMapping &verts,
418 IndexMapping &edges)
419{
420 /* Need `NoInitialization()` because OrderedEdge is not default constructible. */
421 Array<OrderedEdge> ordered_edges1(edges1.size(), NoInitialization());
422 Array<OrderedEdge> ordered_edges2(edges2.size(), NoInitialization());
423 edges_from_vert_sets(edges1, verts.to_sorted1, verts.set_ids, ordered_edges1);
424 edges_from_vert_sets(edges2, verts.to_sorted2, verts.set_ids, ordered_edges2);
426 edges.from_sorted1,
427 edges.from_sorted2,
428 ordered_edges1.as_span(),
429 ordered_edges2.as_span(),
430 0);
431 const bool edges_match = update_set_ids(edges.set_ids,
432 ordered_edges1.as_span(),
433 ordered_edges2.as_span(),
434 edges.from_sorted1,
435 edges.from_sorted2,
436 0,
437 0);
438 if (!edges_match) {
439 return false;
440 }
441 update_set_sizes(edges.set_ids, edges.set_sizes);
442 return true;
443}
444
448static bool sort_corners_based_on_domain(const Span<int> corner_domain1,
449 const Span<int> corner_domain2,
450 const IndexMapping &domain,
451 IndexMapping &corners)
452{
453 sort_per_set_with_id_maps(corners.set_sizes,
454 corner_domain1,
455 corner_domain2,
456 domain.to_sorted1,
457 domain.to_sorted2,
458 domain.set_ids,
459 corners.from_sorted1,
460 corners.from_sorted2);
461 const bool corners_line_up = update_set_ids_with_id_maps(corners.set_ids,
462 corner_domain1,
463 corner_domain2,
464 domain.to_sorted1,
465 domain.to_sorted2,
466 domain.set_ids,
467 corners.from_sorted1,
468 corners.from_sorted2);
469 if (!corners_line_up) {
470 return false;
471 }
472 update_set_sizes(corners.set_ids, corners.set_sizes);
473 return true;
474}
475
476static void calc_smallest_corner_ids(const Span<int> face_offsets,
477 const Span<int> corners_to_sorted,
478 const Span<int> corner_set_ids,
479 MutableSpan<int> smallest_corner_ids)
480{
481 for (const int face_i : smallest_corner_ids.index_range()) {
482 const int face_start = face_offsets[face_i];
483 const int face_end = face_offsets[face_i + 1];
484 int smallest = corner_set_ids[corners_to_sorted[face_start]];
485 const IndexRange corners = IndexRange(face_start, face_end - face_start);
486 for (const int corner_i : corners.drop_front(1)) {
487 const int corner_id = corner_set_ids[corners_to_sorted[corner_i]];
488 smallest = std::min(corner_id, smallest);
489 }
490 smallest_corner_ids[face_i] = smallest;
491 }
492}
493
497static bool sort_faces_based_on_corners(const IndexMapping &corners,
498 const Span<int> face_offsets1,
499 const Span<int> face_offsets2,
501{
502 /* The smallest corner set id, per face. */
503 Array<int> smallest_corner_ids1(faces.from_sorted1.size());
504 Array<int> smallest_corner_ids2(faces.from_sorted2.size());
506 face_offsets1, corners.to_sorted1, corners.set_ids, smallest_corner_ids1);
508 face_offsets2, corners.to_sorted2, corners.set_ids, smallest_corner_ids2);
510 faces.from_sorted1,
511 faces.from_sorted2,
512 smallest_corner_ids1.as_span(),
513 smallest_corner_ids2.as_span(),
514 0);
515 const bool faces_line_up = update_set_ids(faces.set_ids,
516 smallest_corner_ids1.as_span(),
517 smallest_corner_ids2.as_span(),
518 faces.from_sorted1,
519 faces.from_sorted2,
520 0,
521 0);
522 if (!faces_line_up) {
523 return false;
524 }
525 update_set_sizes(faces.set_ids, faces.set_sizes);
526 return true;
527}
528
536static bool ignored_attribute(const StringRef id)
537{
538 return attribute_name_is_anonymous(id) || id.startswith(".pn.") ||
539 ELEM(id, ".uv_select_vert", ".uv_select_edge", ".uv_select_face");
540}
541
548static std::optional<GeoMismatch> verify_attributes_compatible(
549 const AttributeAccessor &attributes1, const AttributeAccessor &attributes2)
550{
551 Set<StringRefNull> attribute_ids1 = attributes1.all_ids();
552 Set<StringRefNull> attribute_ids2 = attributes2.all_ids();
553 attribute_ids1.remove_if(ignored_attribute);
554 attribute_ids2.remove_if(ignored_attribute);
555
556 if (attribute_ids1 != attribute_ids2) {
557 /* Disabled for now due to tests not being up to date. */
558 // return GeoMismatch::Attributes;
559 }
560 for (const StringRef id : attribute_ids1) {
561 GAttributeReader reader1 = attributes1.lookup(id);
562 GAttributeReader reader2 = attributes2.lookup(id);
563 if (!reader1 || !reader2) {
564 /* Necessary because of previous disabled return. */
565 continue;
566 }
567 if (reader1.domain != reader2.domain || reader1.varray.type() != reader2.varray.type()) {
569 }
570 }
571 return std::nullopt;
572}
573
579static std::optional<GeoMismatch> sort_domain_using_attributes(
580 const AttributeAccessor &attributes1,
581 const AttributeAccessor &attributes2,
582 const AttrDomain domain,
583 const Span<StringRef> excluded_attributes,
584 IndexMapping &maps,
585 const float threshold)
586{
587
588 /* We only need the ids from one geometry, since we know they have the same attributes. */
589 Set<StringRefNull> attribute_ids = attributes1.all_ids();
590 for (const StringRef name : excluded_attributes) {
591 attribute_ids.remove_as(name);
592 }
593 attribute_ids.remove_if(ignored_attribute);
594
595 for (const StringRef id : attribute_ids) {
596 if (!attributes2.contains(id)) {
597 /* Only needed right now since some test meshes don't have the same attributes. */
599 }
600 GAttributeReader reader1 = attributes1.lookup(id);
601 GAttributeReader reader2 = attributes2.lookup(id);
602
603 if (reader1.domain != domain) {
604 /* We only look at attributes of the given domain. */
605 continue;
606 }
607
608 std::optional<GeoMismatch> mismatch = {};
609
610 attribute_math::convert_to_static_type(reader1.varray.type(), [&](auto dummy) {
611 using T = decltype(dummy);
612 const VArraySpan<T> values1 = reader1.varray.typed<T>();
613 const VArraySpan<T> values2 = reader2.varray.typed<T>();
614
615 /* Because sorting of float vectors is not very stable, we do a separate sort per component,
616 * re-computing the set ids each time. */
617 int num_loops = 1;
618 if constexpr (std::is_same_v<T, float2>) {
619 num_loops = 2;
620 }
621 else if constexpr (std::is_same_v<T, float3>) {
622 num_loops = 3;
623 }
625 num_loops = 4;
626 }
627 else if constexpr (is_same_any_v<T, float4x4>) {
628 num_loops = 16;
629 }
630 for (const int component_i : IndexRange(num_loops)) {
632 maps.set_sizes, maps.from_sorted1, maps.from_sorted2, values1, values2, component_i);
633 const bool attributes_line_up = update_set_ids(maps.set_ids,
634 values1,
635 values2,
636 maps.from_sorted1,
637 maps.from_sorted2,
638 threshold,
639 component_i);
640 if (!attributes_line_up) {
641 switch (domain) {
644 return;
645 case AttrDomain::Edge:
647 return;
650 return;
651 case AttrDomain::Face:
653 return;
656 return;
657 default:
659 break;
660 }
661 return;
662 }
664 }
665 });
666
667 if (mismatch) {
668 return mismatch;
669 }
670 }
671 return std::nullopt;
672}
673
674/* When all checks are done, it's possible that some set sizes are still not one e.g, when you have
675 * two loose verts at the same position they are indistinguishable. This makes all the set ID's one
676 * by choosing a match. If possible, the match is chosen such that they have the same unsorted
677 * index.
678 */
680{
681 for (const int sorted_i : indices.set_sizes.index_range()) {
682 if (indices.set_sizes[sorted_i] == 1) {
683 continue;
684 }
685 int match = sorted_i;
686 for (const int other_index :
687 IndexRange(indices.set_ids[sorted_i], indices.set_sizes[sorted_i]))
688 {
689 if (indices.from_sorted1[sorted_i] == indices.from_sorted2[other_index]) {
690 match = other_index;
691 break;
692 }
693 }
694 std::swap(indices.from_sorted2[sorted_i], indices.from_sorted2[match]);
695 for (const int other_set_i :
696 IndexRange(indices.set_ids[sorted_i], indices.set_sizes[sorted_i]))
697 {
698 /* New first element, since this one is now in a new set. */
699 indices.set_ids[other_set_i] = sorted_i + 1;
700 indices.set_sizes[other_set_i] -= 1;
701 }
702 indices.set_ids[sorted_i] = sorted_i;
703 indices.set_sizes[sorted_i] = 1;
704 }
705}
706
707static bool all_set_sizes_one(const Span<int> set_sizes)
708{
709 for (const int size : set_sizes) {
710 if (size != 1) {
711 return false;
712 }
713 }
714 return true;
715}
716
729static std::optional<GeoMismatch> construct_vert_mapping(const Mesh &mesh1,
730 const Mesh &mesh2,
732 IndexMapping &edges)
733{
734 if (all_set_sizes_one(verts.set_sizes)) {
735 /* The vertices are already in one-to-one correspondence. */
736 return std::nullopt;
737 }
738
739 /* Since we are not yet able to distinguish all vertices based on their attributes alone, we
740 * need to use the edge topology. */
741 Array<int> vert_to_edge_offsets1;
742 Array<int> vert_to_edge_indices1;
743 const GroupedSpan<int> vert_to_edge_map1 = mesh::build_vert_to_edge_map(
744 mesh1.edges(), mesh1.verts_num, vert_to_edge_offsets1, vert_to_edge_indices1);
745 Array<int> vert_to_edge_offsets2;
746 Array<int> vert_to_edge_indices2;
747 const GroupedSpan<int> vert_to_edge_map2 = mesh::build_vert_to_edge_map(
748 mesh2.edges(), mesh2.verts_num, vert_to_edge_offsets2, vert_to_edge_indices2);
749
750 for (const int sorted_i : verts.from_sorted1.index_range()) {
751 const int vert1 = verts.from_sorted1[sorted_i];
752 Vector<int> matching_verts;
753 const Span<int> edges1 = vert_to_edge_map1[vert1];
754 /* Try to find all matching vertices. We know that it will be in the same vertex set, if it
755 * exists. */
756 for (const int index_in_set : IndexRange(verts.set_sizes[sorted_i])) {
757 /* The set id is the index of its first element. */
758 const int vert2 = verts.from_sorted2[verts.set_ids[sorted_i] + index_in_set];
759 const Span<int> edges2 = vert_to_edge_map2[vert2];
760 if (edges1.size() != edges2.size()) {
761 continue;
762 }
763 bool vert_matches = true;
764 for (const int edge1 : edges1) {
765 bool found_matching_edge = false;
766 for (const int edge2 : edges2) {
767 if (edges.set_ids[edges.to_sorted1[edge1]] == edges.set_ids[edges.to_sorted2[edge2]]) {
768 found_matching_edge = true;
769 break;
770 }
771 }
772 if (!found_matching_edge) {
773 vert_matches = false;
774 break;
775 }
776 }
777 if (vert_matches) {
778 matching_verts.append(index_in_set);
779 }
780 }
781
782 if (matching_verts.is_empty()) {
784 }
785
786 /* Update the maps. */
787
788 /* In principle, we should make sure that there is exactly one matching vertex. If the mesh is
789 * of good enough quality, that will always be the case. In other cases we just assume that any
790 * choice will be valid. Otherwise, the logic becomes a lot more difficult. Because we want to
791 * test for mesh equality as well, we try to pick the matching vert with the same index. */
792 int index_in_set = matching_verts.first();
793 for (const int other_index_in_set : matching_verts) {
794 const int other_sorted_index = verts.set_ids[sorted_i] + other_index_in_set;
795 if (verts.from_sorted1[sorted_i] == verts.from_sorted2[other_sorted_index]) {
796 index_in_set = other_index_in_set;
797 break;
798 }
799 }
800 std::swap(verts.from_sorted2[sorted_i],
801 verts.from_sorted2[verts.set_ids[sorted_i] + index_in_set]);
802 for (const int other_set_i : IndexRange(verts.set_ids[sorted_i], verts.set_sizes[sorted_i])) {
803 /* New first element, since this one is now in a new set. */
804 verts.set_ids[other_set_i] = sorted_i + 1;
805 verts.set_sizes[other_set_i] -= 1;
806 }
807 verts.set_ids[sorted_i] = sorted_i;
808 verts.set_sizes[sorted_i] = 1;
809 }
810
812
813 verts.recalculate_inverse_maps();
814
815 /* The bijective mapping is now given by composing `verts.to_sorted1` with `verts.from_sorted2`,
816 * or vice versa. Since we don't actually need the mapping (we just care that it exists), we
817 * don't construct it here. */
818
819 return std::nullopt;
820}
821
822std::optional<GeoMismatch> compare_meshes(const Mesh &mesh1,
823 const Mesh &mesh2,
824 const float threshold)
825{
826
827 /* These will be assumed implicitly later on. */
828 if (mesh1.verts_num != mesh2.verts_num) {
830 }
831 if (mesh1.edges_num != mesh2.edges_num) {
833 }
834 if (mesh1.corners_num != mesh2.corners_num) {
836 }
837 if (mesh1.faces_num != mesh2.faces_num) {
839 }
840
841 std::optional<GeoMismatch> mismatch = {};
842
843 const AttributeAccessor mesh1_attributes = mesh1.attributes();
844 const AttributeAccessor mesh2_attributes = mesh2.attributes();
845 mismatch = verify_attributes_compatible(mesh1_attributes, mesh2_attributes);
846 if (mismatch) {
847 return mismatch;
848 }
849
852 mesh1_attributes, mesh2_attributes, AttrDomain::Point, {}, verts, threshold);
853 if (mismatch) {
854 return mismatch;
855 }
856
857 /* We need the maps going the other way as well. */
858 verts.recalculate_inverse_maps();
859
860 IndexMapping edges(mesh1.edges_num);
861 if (!sort_edges(mesh1.edges(), mesh2.edges(), verts, edges)) {
863 }
864
866 mesh1_attributes, mesh2_attributes, AttrDomain::Edge, {".edge_verts"}, edges, threshold);
867 if (mismatch) {
868 return mismatch;
869 };
870
871 /* We need the maps going the other way as well. */
873
874 IndexMapping corners(mesh1.corners_num);
875 if (!sort_corners_based_on_domain(mesh1.corner_verts(), mesh2.corner_verts(), verts, corners)) {
877 }
878
879 if (!sort_corners_based_on_domain(mesh1.corner_edges(), mesh2.corner_edges(), edges, corners)) {
881 }
882
883 mismatch = sort_domain_using_attributes(mesh1_attributes,
884 mesh2_attributes,
886 {".corner_vert", ".corner_edge"},
887 corners,
888 threshold);
889 if (mismatch) {
890 return mismatch;
891 };
892
893 /* We need the maps going the other way as well. */
894 corners.recalculate_inverse_maps();
895
897 if (!sort_faces_based_on_corners(corners, mesh1.face_offsets(), mesh2.face_offsets(), faces)) {
899 }
900
902 mesh1_attributes, mesh2_attributes, AttrDomain::Face, {}, faces, threshold);
903 if (mismatch) {
904 return mismatch;
905 };
906
907 mismatch = construct_vert_mapping(mesh1, mesh2, verts, edges);
908 if (mismatch) {
909 return mismatch;
910 }
911
912 /* Now we double check that the other topology maps agree with this vertex mapping. */
913
914 if (!sort_edges(mesh1.edges(), mesh2.edges(), verts, edges)) {
916 }
917
918 make_set_sizes_one(edges);
919
921
922 if (!sort_corners_based_on_domain(mesh1.corner_verts(), mesh2.corner_verts(), verts, corners)) {
924 }
925
926 if (!sort_corners_based_on_domain(mesh1.corner_edges(), mesh2.corner_edges(), edges, corners)) {
928 }
929
930 make_set_sizes_one(corners);
931
932 corners.recalculate_inverse_maps();
933
934 if (!sort_faces_based_on_corners(corners, mesh1.face_offsets(), mesh2.face_offsets(), faces)) {
936 }
937
939
940 /* The meshes are isomorphic, we now just need to determine if they are equal i.e., the indices
941 * are the same. */
942 for (const int sorted_i : verts.from_sorted1.index_range()) {
943 if (verts.from_sorted1[sorted_i] != verts.from_sorted2[sorted_i]) {
945 }
946 }
947 /* Skip the test for edges, since a lot of tests actually have different edge indices.
948 *TODO: remove this once those tests have been updated. */
949 for (const int sorted_i : corners.from_sorted1.index_range()) {
950 if (corners.from_sorted1[sorted_i] != corners.from_sorted2[sorted_i]) {
952 }
953 }
954 for (const int sorted_i : faces.from_sorted1.index_range()) {
955 if (faces.from_sorted1[sorted_i] != faces.from_sorted2[sorted_i]) {
957 }
958 }
959
960 /* No mismatches found. */
961 return std::nullopt;
962}
963
967static bool sort_curves(const OffsetIndices<int> offset_indices1,
968 const OffsetIndices<int> offset_indices2,
970{
971 Array<int> curve_point_counts1(offset_indices1.size());
972 Array<int> curve_point_counts2(offset_indices2.size());
974 offset_indices1, offset_indices1.index_range(), curve_point_counts1.as_mutable_span());
976 offset_indices2, offset_indices2.index_range(), curve_point_counts2.as_mutable_span());
978 curves.from_sorted1,
979 curves.from_sorted2,
980 curve_point_counts1.as_span(),
981 curve_point_counts2.as_span(),
982 0);
983 const bool curves_sizes_match = update_set_ids(curves.set_ids,
984 curve_point_counts1.as_span(),
985 curve_point_counts2.as_span(),
986 curves.from_sorted1,
987 curves.from_sorted2,
988 0,
989 0);
990 if (!curves_sizes_match) {
991 return false;
992 }
993 update_set_sizes(curves.set_ids, curves.set_sizes);
994 return true;
995}
996
997std::optional<GeoMismatch> compare_curves(const CurvesGeometry &curves1,
998 const CurvesGeometry &curves2,
999 const float threshold)
1000{
1001 /* These will be assumed implicitly later on. */
1002 if (curves1.points_num() != curves2.points_num()) {
1004 }
1005 if (curves1.curves_num() != curves2.curves_num()) {
1007 }
1008
1009 std::optional<GeoMismatch> mismatch = {};
1010
1011 const AttributeAccessor curves1_attributes = curves1.attributes();
1012 const AttributeAccessor curves2_attributes = curves2.attributes();
1013 mismatch = verify_attributes_compatible(curves1_attributes, curves2_attributes);
1014 if (mismatch) {
1015 return mismatch;
1016 }
1017
1018 IndexMapping points(curves1.points_num());
1020 curves1_attributes, curves2_attributes, AttrDomain::Point, {}, points, threshold);
1021 if (mismatch) {
1022 return mismatch;
1023 }
1024
1025 IndexMapping curves(curves1.curves_num());
1026 if (!sort_curves(curves1.offsets(), curves2.offsets(), curves)) {
1028 }
1029
1031 curves1_attributes, curves2_attributes, AttrDomain::Curve, {}, curves, threshold);
1032 if (mismatch) {
1033 return mismatch;
1034 }
1035
1036 for (const int sorted_i : points.from_sorted1.index_range()) {
1037 if (points.from_sorted1[sorted_i] != points.from_sorted2[sorted_i]) {
1038 return GeoMismatch::Indices;
1039 }
1040 }
1041
1042 for (const int sorted_i : curves.from_sorted1.index_range()) {
1043 if (curves.from_sorted1[sorted_i] != curves.from_sorted2[sorted_i]) {
1044 return GeoMismatch::Indices;
1045 }
1046 }
1047
1048 /* No mismatches found. */
1049 return std::nullopt;
1050}
1051
1052std::optional<GeoMismatch> compare_lattices(const Lattice &lattice1,
1053 const Lattice &lattice2,
1054 float threshold)
1055{
1056 if (lattice1.pntsu != lattice2.pntsu) {
1058 }
1059 if (lattice1.pntsv != lattice2.pntsv) {
1061 }
1062 if (lattice1.pntsw != lattice2.pntsw) {
1064 }
1065
1066 const int num_points = lattice1.pntsu * lattice1.pntsv * lattice1.pntsw;
1067 const Span<BPoint> bpoints1 = {lattice1.def, num_points};
1068 const Span<BPoint> bpoints2 = {lattice2.def, num_points};
1069 for (const int i : IndexRange(num_points)) {
1070 const float3 co1 = bpoints1[i].vec;
1071 const float3 co2 = bpoints2[i].vec;
1072 for (const int component : IndexRange(3)) {
1073 if (values_different(co1, co2, threshold, component)) {
1075 }
1076 }
1077 }
1078
1079 /* No mismatches found. */
1080 return std::nullopt;
1081}
1082
1083} // 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)
#define ELEM(...)
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:360
AttributeSet attributes
Span< T > as_span() const
Definition BLI_array.hh:243
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:248
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]
#define ASSERT_AND_ASSUME(expr)
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 update_set_ids(MutableSpan< int > set_ids, const Span< T > values1, const Span< T > values2, const Span< int > sorted_to_values1, const Span< int > sorted_to_values2, const float threshold, 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)
const char * mismatch_to_string(const GeoMismatch &mismatch)
std::optional< GeoMismatch > compare_lattices(const Lattice &lattice1, const Lattice &lattice2, float threshold)
Checks if the two lattices are different, returning the type of mismatch if any.
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
VecBase< float, 3 > float3
const char * name
struct BPoint * def
int corners_num
int edges_num
int faces_num
int verts_num
i
Definition text_draw.cc:230