@@ -641,6 +641,65 @@ function Base.:(==)(a::Mesh, b::Mesh)
641641 (faces (a) == faces (b)) && (a. views == b. views)
642642end
643643
644+ """
645+ strictly_equal_face_vertices(a::Mesh, b::Mesh)
646+
647+ Checks whether mesh a and b are equal in terms of vertices used in their faces.
648+ This allows for vertex data and indices to be synchronously permuted. For
649+ example, this will recognize
650+ ```
651+ a = Mesh([a, b, c], [GLTriangleFace(1,2,3)])
652+ b = Mesh([a, c, b], [GLTriangleFace(1,3,2)])
653+ ```
654+ as equal, because while the positions and faces have different orders the vertices
655+ in the face are the same:
656+ ```
657+ [a, c, b][[1, 3, 2]] == [a, b, c] == [a, b, c][[1,2,3]]
658+ ```
659+
660+ This still returns false if the order of faces is permuted, e.g.
661+ `Mesh(ps, [f1, f2]) != Mesh(ps, [f2, f1])`. It also returns false if vertices are
662+ cyclically permuted within a face, i.e. `ps[[1,2,3]] != ps[[2,3,1]]`.
663+ """
664+ function strictly_equal_face_vertices (a:: Mesh , b:: Mesh )
665+ # Quick checks
666+ if propertynames (a) != propertynames (b) || length (faces (a)) != length (faces (b))
667+ return false
668+ end
669+
670+ N = length (faces (a))
671+ # for views we want to ignore empty ranges (they don't represent geometry)
672+ # and treat 1:N as no range (as that is used interchangeably)
673+ views1 = filter (view -> length (view) > 0 && (minimum (view) > 1 || maximum (view) < N), a. views)
674+ views2 = filter (view -> length (view) > 0 && (minimum (view) > 1 || maximum (view) < N), b. views)
675+ views1 != views2 && return false
676+
677+ # TODO : Allow different face orders & cyclic permutation within faces.
678+ # E.g. use hash.(data[face]), cyclically permute min to front, hash result
679+ # and add them to heaps (or sets?) so we can compare them at the end
680+ # That should probably be another function as it's probably a significant
681+ # step up in overhead?
682+ for (attrib1, attrib2) in zip (vertex_attributes (a), vertex_attributes (b))
683+ if attrib1 isa FaceView
684+ if ! (attrib2 isa FaceView) || length (faces (attrib1)) != length (faces (attrib2))
685+ return false
686+ end
687+ for (f1, f2) in zip (faces (attrib1), faces (attrib2))
688+ values (attrib1)[f1] == values (attrib2)[f2] || return false
689+ end
690+ else
691+ if attrib2 isa FaceView
692+ return false
693+ end
694+ for (f1, f2) in zip (faces (a), faces (b))
695+ attrib1[f1] == attrib2[f2] || return false
696+ end
697+ end
698+ end
699+
700+ return true
701+ end
702+
644703function Base. iterate (mesh:: Mesh , i= 1 )
645704 return i - 1 < length (mesh) ? (mesh[i], i + 1 ) : nothing
646705end
0 commit comments