Skip to content

Commit 08fab13

Browse files
authored
Don't consider faces equal under cyclic permutation by default (#241)
* don't consider faces equal under cyclic permutation by default * move definitions and add docstrings * add tests
1 parent b190056 commit 08fab13

File tree

3 files changed

+82
-56
lines changed

3 files changed

+82
-56
lines changed

src/basic_types.jl

-47
Original file line numberDiff line numberDiff line change
@@ -81,53 +81,6 @@ function Base.show(io::IO, x::NgonFace{N, T}) where {N, T}
8181
return print(io, name, "(", join(value.(x), ", "), ")")
8282
end
8383

84-
# two faces are the same if they match or they just cycle indices
85-
function Base.:(==)(f1::FT, f2::FT) where {N, FT <: AbstractFace{N}}
86-
_, min_i1 = findmin(f1.data)
87-
_, min_i2 = findmin(f2.data)
88-
@inbounds for i in 1:N
89-
if f1[mod1(min_i1 + i, end)] !== f2[mod1(min_i2 + i, end)]
90-
return false
91-
end
92-
end
93-
return true
94-
end
95-
function Base.hash(f::AbstractFace{N}, h::UInt) where {N}
96-
_, min_i = findmin(f.data)
97-
@inbounds for i in min_i:N
98-
h = hash(f[i], h)
99-
end
100-
@inbounds for i in 1:min_i-1
101-
h = hash(f[i], h)
102-
end
103-
return h
104-
end
105-
Base.isequal(f1::AbstractFace, f2::AbstractFace) = ==(f1, f2)
106-
107-
# Fastpaths
108-
Base.:(==)(f1::FT, f2::FT) where {FT <: AbstractFace{2}} = minmax(f1.data...) == minmax(f2.data...)
109-
Base.hash(f::AbstractFace{2}, h::UInt) = hash(minmax(f.data...), h)
110-
111-
function Base.:(==)(f1::FT, f2::FT) where {FT <: AbstractFace{3}}
112-
return (f1.data == f2.data) || (f1.data == (f2[2], f2[3], f2[1])) ||
113-
(f1.data == (f2[3], f2[1], f2[2]))
114-
end
115-
function Base.hash(f::AbstractFace{3}, h::UInt)
116-
if f[1] < f[2]
117-
if f[1] < f[3]
118-
return hash(f.data, h)
119-
else
120-
return hash((f[3], f[1], f[2]), h)
121-
end
122-
else
123-
if f[2] < f[3]
124-
return hash((f[2], f[3], f[1]), h)
125-
else
126-
return hash((f[3], f[1], f[2]), h)
127-
end
128-
end
129-
end
130-
13184
Face(::Type{<:NgonFace{N}}, ::Type{T}) where {N,T} = NgonFace{N,T}
13285
Face(F::Type{NgonFace{N,FT}}, ::Type{T}) where {FT,N,T} = F
13386

src/meshes.jl

+68-3
Original file line numberDiff line numberDiff line change
@@ -462,15 +462,80 @@ function split_mesh(mesh::Mesh, views::Vector{<: UnitRange{<: Integer}} = mesh.v
462462
end
463463
end
464464

465+
466+
467+
# two faces are the same if they match or they just cycle indices
468+
"""
469+
cyclic_equal(face1, face2)
470+
471+
Returns true if two faces are equal up to a cyclic permutation of their indices.
472+
E.g. considers `GLTriangleFace(2,3,1)` equal to `GLTriangleFace(1,2,3)` but not
473+
`GLTriangleFace(2,1,3)`.
474+
"""
475+
function cyclic_equal(f1::FT, f2::FT) where {N, FT <: AbstractFace{N}}
476+
_, min_i1 = findmin(f1.data)
477+
_, min_i2 = findmin(f2.data)
478+
@inbounds for i in 1:N
479+
if f1[mod1(min_i1 + i, end)] !== f2[mod1(min_i2 + i, end)]
480+
return false
481+
end
482+
end
483+
return true
484+
end
485+
486+
"""
487+
cyclic_hash(face[, h::UInt = hash(0)])
488+
489+
Creates a hash for the given face that is equal under cyclic permutation of the
490+
faces indices.
491+
For example `GLTriangleFace(1,2,3)` will have the same hash as `(2,3,1)` and
492+
`(3,1,2)`, but be different from `(1,3,2)` and its cyclic permutations.
493+
"""
494+
function cyclic_hash(f::AbstractFace{N}, h::UInt = hash(0)) where {N}
495+
_, min_i = findmin(f.data)
496+
@inbounds for i in min_i:N
497+
h = hash(f[i], h)
498+
end
499+
@inbounds for i in 1:min_i-1
500+
h = hash(f[i], h)
501+
end
502+
return h
503+
end
504+
505+
# Fastpaths
506+
cyclic_equal(f1::FT, f2::FT) where {FT <: AbstractFace{2}} = minmax(f1.data...) == minmax(f2.data...)
507+
cyclic_hash(f::AbstractFace{2}, h::UInt = hash(0)) = hash(minmax(f.data...), h)
508+
509+
function cyclic_equal(f1::FT, f2::FT) where {FT <: AbstractFace{3}}
510+
return (f1.data == f2.data) || (f1.data == (f2[2], f2[3], f2[1])) ||
511+
(f1.data == (f2[3], f2[1], f2[2]))
512+
end
513+
function cyclic_hash(f::AbstractFace{3}, h::UInt = hash(0))
514+
if f[1] < f[2]
515+
if f[1] < f[3]
516+
return hash(f.data, h)
517+
else
518+
return hash((f[3], f[1], f[2]), h)
519+
end
520+
else
521+
if f[2] < f[3]
522+
return hash((f[2], f[3], f[1]), h)
523+
else
524+
return hash((f[3], f[1], f[2]), h)
525+
end
526+
end
527+
end
528+
529+
465530
"""
466531
remove_duplicates(faces)
467532
468533
Uses a Dict to remove duplicates from the given `faces`.
469534
"""
470535
function remove_duplicates(fs::AbstractVector{FT}) where {FT <: AbstractFace}
471-
hashmap = Dict{FT, Nothing}()
472-
foreach(k -> setindex!(hashmap, nothing, k), fs)
473-
return collect(keys(hashmap))
536+
hashmap = Dict{UInt64, FT}()
537+
foreach(f -> hashmap[cyclic_hash(f)] = f, fs)
538+
return collect(values(hashmap))
474539
end
475540

476541

test/geometrytypes.jl

+14-6
Original file line numberDiff line numberDiff line change
@@ -290,13 +290,21 @@ end
290290
f = QuadFace(3, 4, 7, 8)
291291
@test data[f] == ("3", "4", "7", "8")
292292

293-
@test hash(f) != hash(QuadFace(1,2,3,4))
294-
@test hash(f) == hash(QuadFace(3,4,7,8))
293+
@test GeometryBasics.cyclic_hash(f) != GeometryBasics.cyclic_hash(QuadFace(1,2,3,4))
294+
@test GeometryBasics.cyclic_hash(f) == GeometryBasics.cyclic_hash(QuadFace(3,4,7,8))
295295
# cyclic permutation does not change the face
296-
@test hash(f) == hash(QuadFace(7,8,3,4))
297-
@test hash(GLTriangleFace(1,2,3)) == hash(GLTriangleFace(1,2,3))
298-
@test hash(GLTriangleFace(1,2,3)) == hash(GLTriangleFace(2,3,1))
299-
@test hash(GLTriangleFace(1,2,3)) == hash(GLTriangleFace(3,1,2))
296+
@test GeometryBasics.cyclic_hash(f) == GeometryBasics.cyclic_hash(QuadFace(7,8,3,4))
297+
@test GeometryBasics.cyclic_hash(GLTriangleFace(1,2,3)) == GeometryBasics.cyclic_hash(GLTriangleFace(1,2,3))
298+
@test GeometryBasics.cyclic_hash(GLTriangleFace(1,2,3)) == GeometryBasics.cyclic_hash(GLTriangleFace(2,3,1))
299+
@test GeometryBasics.cyclic_hash(GLTriangleFace(1,2,3)) == GeometryBasics.cyclic_hash(GLTriangleFace(3,1,2))
300+
301+
# repeat with cyclic_equal
302+
@test !GeometryBasics.cyclic_equal(f, QuadFace(1,2,3,4))
303+
@test GeometryBasics.cyclic_equal(f, QuadFace(3,4,7,8))
304+
@test GeometryBasics.cyclic_equal(f, QuadFace(7,8,3,4))
305+
@test GeometryBasics.cyclic_equal(GLTriangleFace(1,2,3), GLTriangleFace(1,2,3))
306+
@test GeometryBasics.cyclic_equal(GLTriangleFace(1,2,3), GLTriangleFace(2,3,1))
307+
@test GeometryBasics.cyclic_equal(GLTriangleFace(1,2,3), GLTriangleFace(3,1,2))
300308
end
301309

302310
@testset "FaceView" begin

0 commit comments

Comments
 (0)