Skip to content

Commit 73385bf

Browse files
committed
add MultiFace remapping code
1 parent 90988fe commit 73385bf

File tree

3 files changed

+135
-1
lines changed

3 files changed

+135
-1
lines changed

src/basic_types.jl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,18 @@ struct MultiFace{N, T, FaceType <: AbstractFace{N, T}, Names, M} <: AbstractFace
6161
return new{N, T, FT, Names, M}(nt)
6262
end
6363
end
64+
65+
MultiFace(; kwargs...) = MultiFace(NamedTuple(kwargs))
66+
MultiFace{Names}(args...) where {Names} = MultiFace(NamedTuple{Names}(args))
67+
6468
Base.names(::Type{<: MultiFace{N, T, FT, Names}}) where {N, T, FT, Names} = Names
69+
function Base.getindex(f::MultiFace{N, T, FT, Names, N_Attrib}, i::Integer) where {N, T, FT, Names, N_Attrib}
70+
return ntuple(n -> f.faces[n][i], N_Attrib)
71+
end
72+
73+
# TODO: Some shorthands
74+
const NormalFace = MultiFace{(:position, :normal)}
75+
const NormalUVFace = MultiFace{(:position, :normal, :uv)}
6576

6677
@propagate_inbounds Base.getindex(x::Polytope, i::Integer) = coordinates(x)[i]
6778
@propagate_inbounds Base.iterate(x::Polytope) = iterate(coordinates(x))
@@ -366,7 +377,7 @@ end
366377

367378
coordinates(mesh::Mesh) = mesh.position
368379
faces(mesh::Mesh) = mesh.connectivity
369-
normals(mesh::Mesh) = hasproperty(mesh, :normals) ? mesh.normals : nothing
380+
normals(mesh::Mesh) = hasproperty(mesh, :normal) ? mesh.normal : nothing
370381
texturecoordinates(mesh::Mesh) = hasproperty(mesh, :uv) ? mesh.uv : nothing
371382
vertex_attributes(mesh::Mesh) = getfield(mesh, :vertex_attributes)
372383

src/meshes.jl

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,107 @@ function Base.merge(meshes::AbstractVector{T}) where T <: MetaMesh
146146
return MetaMesh(big_mesh, big_meta)
147147
end
148148

149+
# TODO: naming
150+
# synchronize_vertex_attributes
151+
# merge_vertex_(attribute)_indices
152+
# convert(Face, MultiFace)
153+
# ...
154+
function merge_vertex_indices(mesh)
155+
attribs, fs, views = merge_vertex_indices(
156+
vertex_attributes(mesh), faces(mesh), mesh.views)
157+
158+
return Mesh(attribs, fs, views)
159+
end
160+
161+
function merge_vertex_indices(
162+
attribs::NamedTuple{Names},
163+
faces::AbstractVector{<: MultiFace{N, T, FT, Names}},
164+
views::Vector{UnitRange}
165+
) where {Names, N, T, FT}
166+
167+
# Note: typing checks for matching Names
168+
169+
if isempty(views)
170+
new_faces, vertex_map = merge_vertex_indices(faces)
171+
new_attribs = ntuple(n -> attribs[n][vertex_map[n]], length(Names))
172+
return NamedTuple{Names}(new_attribs), new_faces, views
173+
end
174+
175+
new_attribs = NamedTuple((Pair(k, similar(v, 0)) for (k, v) in pairs(attribs)))
176+
new_faces = similar(faces, FT, 0)
177+
new_views = UnitRange[]
178+
179+
for idxs in views
180+
# TODO: this depends on T in Face (1 based -> 1, 0 based -> 0)
181+
vertex_index_counter = T(length(new_attribs[1]) + 1)
182+
183+
# Generate new face from current view, with the first vertex_index
184+
# corresponding to the first vertex attribute added in this iteration
185+
face_view = view(faces, idxs)
186+
new_faces_in_view, vertex_map = merge_vertex_indices(face_view, vertex_index_counter)
187+
188+
# update vertex attributes
189+
for (name, indices) in pairs(vertex_map)
190+
append!(new_attribs[name], view(attribs[name], indices))
191+
end
192+
193+
# add new faces and new view
194+
start = length(new_faces) + 1
195+
append!(new_faces, new_faces_in_view)
196+
append!(new_views, start:length(new_faces))
197+
end
198+
199+
return new_attribs, new_faces, new_views
200+
end
201+
202+
function merge_vertex_indices(
203+
faces::AbstractVector{<: MultiFace{N, T, FT, Names, N_Attrib}},
204+
vertex_index_counter = T(1)
205+
) where {N, T, FT <: AbstractFace{N, T}, Names, N_Attrib}
206+
207+
N_faces = length(faces)
208+
209+
# maps a combination of old indices in MultiFace to a new vertex_index
210+
vertex_index_map = Dict{NTuple{N_Attrib, T}, T}()
211+
212+
# Faces after conversion
213+
new_faces = sizehint!(FT[], N_faces)
214+
215+
# indices that remap attributes
216+
attribute_indices = ntuple(n -> sizehint!(UInt32[], N_faces), N_Attrib)
217+
218+
# keep track of the remmaped indices for one vertex so we don't have to
219+
# query the dict twice
220+
temp = zeros(N)
221+
222+
for multi_face in faces
223+
224+
for i in 1:N
225+
# get the i-th set of vertex indices from multi_face, i.e.
226+
# (multi_face.position_index[i], multi_face.normal_index[i], ...)
227+
vertex = ntuple(n -> multi_face.faces[n][i], N_Attrib)
228+
229+
# if the vertex exists, get it's index
230+
# otherwise register it with the next available vertex index
231+
temp[i] = get!(vertex_index_map, vertex) do
232+
vertex_index_counter += 1
233+
push!.(attribute_indices, vertex)
234+
return vertex_index_counter - 1
235+
end
236+
end
237+
238+
# generate new face
239+
push!(new_faces, FT(temp))
240+
end
241+
242+
# in case we are reserving more than needed
243+
sizehint!(new_faces, length(new_faces))
244+
245+
return new_faces, attribute_indices
246+
end
247+
248+
249+
149250

150251
function map_coordinates(f, mesh::Mesh)
151252
result = copy(mesh)

test/meshes.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,25 @@ end
3232
# https://github.com/JuliaGeometry/GeometryBasics.jl/issues/136
3333
merge(Mesh[]) == Mesh(Point3f[], GLTriangleFace[])
3434
end
35+
36+
@testset "Vertex Index Remapping" begin
37+
# Sanity Check
38+
# TODO: extend
39+
m = Mesh(
40+
[GeometryBasics.NormalFace(QuadFace(1, 2, 3, 4), QuadFace(1,1,1,1))],
41+
position = Point2f[(0, 0), (1, 0), (1, 1), (0, 1)],
42+
normal = [Vec3f(0,0,1)]
43+
)
44+
45+
m2 = GeometryBasics.merge_vertex_indices(m)
46+
47+
@test faces(m) isa AbstractVector{<: GeometryBasics.MultiFace}
48+
@test names(eltype(faces(m))) == keys(GeometryBasics.vertex_attributes(m))
49+
@test isempty(m.views)
50+
51+
@test faces(m2) isa AbstractVector{<: QuadFace}
52+
@test coordinates(m2) == coordinates(m)
53+
@test normals(m2) != normals(m)
54+
@test normals(m2) == [only(normals(m)) for _ in 1:4]
55+
@test isempty(m2.views)
56+
end

0 commit comments

Comments
 (0)