@@ -144,126 +144,115 @@ function HalfEdgeTopology(elems::AbstractVector{<:Connectivity}; sort=true)
144144
145145 # sort elements to make sure that they
146146 # are traversed in adjacent-first order
147- adjelems = sort ? adjsort (elems) : elems
148- eleminds = sort ? indexin (adjelems, elems) : 1 : length (elems)
147+ elemsort = sort ? adjsort (elems) : elems
148+ eleminds = sort ? indexin (elemsort, elems) : 1 : length (elems)
149+ adjelems:: Vector{Vector{Int}} = map (collect ∘ indices, elemsort)
149150
150151 # start assuming that all elements are
151- # oriented consistently as CCW
152- CCW = trues (length (adjelems))
152+ # oriented consistently (e.g. CCW)
153+ isreversed = falses (length (adjelems))
153154
154- # initialize with first element
155+ # initialize half-edges using first element
155156 half4pair = Dict {Tuple{Int,Int},HalfEdge} ()
156- elem = first (adjelems)
157- inds = collect (indices (elem))
158- v = CircularVector (inds)
159- n = length (v)
160- for i in 1 : n
161- half4pair[(v[i], v[i + 1 ])] = HalfEdge (v[i], eleminds[1 ])
157+ elem = first (eleminds)
158+ inds = first (adjelems)
159+ n = length (inds)
160+ for i in eachindex (inds)
161+ u = inds[i]
162+ v = inds[mod1 (i + 1 , n)]
163+ # insert halfedges u -> v and v -> u
164+ he = get! (() -> HalfEdge (u, elem), half4pair, (u, v))
165+ half = get! (() -> HalfEdge (v, nothing ), half4pair, (v, u))
166+ he. half = half
167+ half. half = he
162168 end
163169
164- # insert all other elements
165- for e in 2 : length (adjelems)
166- elem = adjelems[e]
167- inds = collect (indices (elem ))
168- v = CircularVector (inds )
169- n = length (v)
170- for i in 1 : n
171- # if pair of vertices is already in the
172- # dictionary this means that the current
173- # polygon has inconsistent orientation
174- if haskey (half4pair, (v[i], v[i + 1 ]) )
175- # delete inserted pairs so far
176- CCW[e] = false
177- for j in 1 : (i - 1 )
178- delete! (half4pair, (v[j], v[j + 1 ]))
179- end
180- break
170+ # update half-edges using all other elements
171+ added = false
172+ disconnected = false
173+ remaining = collect (2 : length (adjelems ))
174+ while ! isempty (remaining )
175+ iter = 1
176+ while iter ≤ length (remaining)
177+ other = remaining[iter]
178+ elem = eleminds[other]
179+ inds = adjelems[other]
180+ n = length (inds )
181+ if anyhalf (inds, half4pair) || disconnected
182+ # at least one half-edge has been found, so we can assess
183+ # the orientation w.r.t. previously added elements/edges
184+ added = true
185+ disconnected = false
186+ deleteat! (remaining, iter)
181187 else
182- # insert pair in consistent orientation
183- half4pair[(v[i], v[i + 1 ])] = HalfEdge (v[i], eleminds[e])
188+ iter += 1
189+ continue
184190 end
185- end
186191
187- if ! CCW[e]
188- # reinsert pairs in CCW orientation
189- for i in 1 : n
190- half4pair[(v[i + 1 ], v[i])] = HalfEdge (v[i + 1 ], eleminds[e])
192+ # if the other element is already claimed by any half-edge
193+ # then the element must be reversed before further updates
194+ isreversed[other] = anyhalfclaimed (inds, half4pair)
195+
196+ # insert half-edges in consistent orientation
197+ if isreversed[other]
198+ step₁ = add1
199+ step₂ = add0
200+ else
201+ step₁ = add0
202+ step₂ = add1
191203 end
192- end
193- end
194204
195- # add missing pointers
196- for (e, elem) in Iterators. enumerate (adjelems)
197- inds = CCW[e] ? indices (elem) : reverse (indices (elem))
198- v = CircularVector (collect (inds))
199- n = length (v)
200- for i in 1 : n
201- # update pointers prev and next
202- he = half4pair[(v[i], v[i + 1 ])]
203- he. prev = half4pair[(v[i - 1 ], v[i])]
204- he. next = half4pair[(v[i + 1 ], v[i + 2 ])]
205-
206- # if not a border element, update half
207- if haskey (half4pair, (v[i + 1 ], v[i]))
208- he. half = half4pair[(v[i + 1 ], v[i])]
209- else # create half-edge for border
210- be = HalfEdge (v[i + 1 ], nothing )
211- be. half = he
212- he. half = be
205+ for i in eachindex (inds)
206+ u = inds[step₁ (i, n)]
207+ v = inds[step₂ (i, n)]
208+ he = get! (() -> HalfEdge (u, elem), half4pair, (u, v))
209+ if isnothing (he. elem)
210+ he. elem = elem
211+ else
212+ assertion (
213+ he. elem === elem,
214+ lazy " duplicate edge $((u, v)) for element $(elem) is inconsistent with previous edge $he"
215+ )
216+ end
217+ half = get! (() -> HalfEdge (v, nothing ), half4pair, (v, u))
218+ he. half = half
219+ half. half = he
213220 end
214221 end
215- end
216222
217- # save halfedges in a vector of pairs
218- halves = Vector {Tuple{HalfEdge,HalfEdge}} ()
219- visited = Set {Tuple{Int,Int}} ()
220- for ((u, v), he) in half4pair
221- uv = minmax (u, v)
222- if uv ∉ visited
223- push! (halves, (he, he. half))
224- push! (visited, uv)
223+ if added
224+ added = false
225+ elseif ! isempty (remaining)
226+ disconnected = true
227+ added = false
225228 end
226229 end
227230
228- HalfEdgeTopology (halves; nelems= length (elems))
229- end
230-
231- function adjsort (elems:: AbstractVector{<:Connectivity} )
232- # initialize list of adjacent elements
233- # with first element from original list
234- list = indices .(elems)
235- adjs = Tuple[popfirst! (list)]
231+ # add missing pointers and save halfedges in a vector of pairs
232+ halves = Vector {Tuple{HalfEdge,HalfEdge}} ()
233+ visited = Set {Tuple{Int,Int}} ()
234+ for (e, inds) in enumerate (adjelems)
235+ inds = isreversed[e] ? circshift! (reverse! (inds), 1 ) : inds
236+ n = length (inds)
237+ for i in eachindex (inds)
238+ u = inds[i]
239+ v = inds[mod1 (i + 1 , n)]
240+ w = inds[mod1 (i + 2 , n)]
236241
237- # the loop will terminate if the mesh
238- # is manifold, and that is always true
239- # with half-edge topology
240- while ! isempty (list)
241- # lookup all elements that share at least
242- # one vertex with the last adjacent element
243- found = false
244- vinds = last (adjs)
245- for i in vinds
246- einds = findall (e -> i ∈ e, list)
247- if ! isempty (einds)
248- # lookup all elements that share at
249- # least two vertices (i.e. edge)
250- for j in sort (einds, rev= true )
251- if length (vinds ∩ list[j]) > 1
252- found = true
253- push! (adjs, popat! (list, j))
254- end
255- end
242+ # update pointers prev and next
243+ he = half4pair[(u, v)]
244+ he. next = half4pair[(v, w)]
245+ he. next. prev = he
246+
247+ uv = minmax (u, v)
248+ if uv ∉ visited
249+ push! (halves, (he, he. half))
250+ push! (visited, uv)
256251 end
257252 end
258-
259- if ! found && ! isempty (list)
260- # we are done with this connected component
261- # pop a new element from the original list
262- push! (adjs, popfirst! (list))
263- end
264253 end
265254
266- connect .(adjs )
255+ HalfEdgeTopology (halves; nelems = length (elems) )
267256end
268257
269258paramdim (:: HalfEdgeTopology ) = 2
@@ -338,3 +327,70 @@ nfacets(t::HalfEdgeTopology) = length(t.halfedges) ÷ 2
338327# ------------
339328
340329Base. convert (:: Type{HalfEdgeTopology} , t:: Topology ) = HalfEdgeTopology (collect (elements (t)))
330+
331+ # -----------------
332+ # HELPER FUNCTIONS
333+ # -----------------
334+
335+ function adjsort (elems:: AbstractVector{<:Connectivity} )
336+ # initialize list of adjacent elements
337+ # with first element from original list
338+ list = indices .(elems)
339+ adjs = Tuple[popfirst! (list)]
340+
341+ # the loop will terminate if the mesh
342+ # is manifold, and that is always true
343+ # with half-edge topology
344+ while ! isempty (list)
345+ # lookup all elements that share at least
346+ # one vertex with the last adjacent element
347+ found = false
348+ vinds = last (adjs)
349+ for i in vinds
350+ einds = findall (e -> i ∈ e, list)
351+ if ! isempty (einds)
352+ # lookup all elements that share at
353+ # least two vertices (i.e. edge)
354+ for j in sort (einds, rev= true )
355+ if length (vinds ∩ list[j]) > 1
356+ found = true
357+ push! (adjs, popat! (list, j))
358+ end
359+ end
360+ end
361+ end
362+
363+ if ! found && ! isempty (list)
364+ # we are done with this connected component
365+ # pop a new element from the original list
366+ push! (adjs, popfirst! (list))
367+ end
368+ end
369+
370+ connect .(adjs)
371+ end
372+
373+ function anyhalf (inds, half4pair)
374+ n = length (inds)
375+ for i in eachindex (inds)
376+ uv = (inds[i], inds[mod1 (i + 1 , n)])
377+ if haskey (half4pair, uv)
378+ return true
379+ end
380+ end
381+ return false
382+ end
383+
384+ function anyhalfclaimed (inds, half4pair)
385+ n = length (inds)
386+ for i in eachindex (inds)
387+ uv = (inds[i], inds[mod1 (i + 1 , n)])
388+ if ! isnothing (get (() -> HalfEdge (0 , nothing ), half4pair, uv). elem)
389+ return true
390+ end
391+ end
392+ return false
393+ end
394+
395+ add0 (i, n) = i
396+ add1 (i, n) = mod1 (i + 1 , n)
0 commit comments