@@ -272,8 +272,6 @@ VoronoiDiagram2DGenerator<TCoordRepType>::ConstructDiagram()
272272 EdgeInfo curr1;
273273 EdgeInfo curr2;
274274
275- unsigned char frontbnd;
276- unsigned char backbnd;
277275 std::vector<IdentifierType> cellPoints;
278276 for (unsigned int i = 0 ; i < m_NumberOfSeeds; ++i)
279277 {
@@ -283,12 +281,56 @@ VoronoiDiagram2DGenerator<TCoordRepType>::ConstructDiagram()
283281 buildEdges.push_back (curr);
284282 EdgeInfo front = curr;
285283 EdgeInfo back = curr;
286- while (!(rawEdges[i].empty ()))
284+ // Assemble raw edges into a connected chain for this Voronoi cell.
285+ // Each iteration pops an edge from the deque and attempts to attach
286+ // it to the front or back of the growing chain. Edges that cannot
287+ // attach are pushed back for retry, because later attachments change
288+ // the chain endpoints and may make previously unattachable edges
289+ // attachable.
290+ //
291+ // A stall counter tracks progress: it resets whenever an edge
292+ // attaches, and terminates the loop when a full pass through the
293+ // deque makes no progress. Without this, certain degenerate seed
294+ // configurations (near-collinear seeds, ITK issue #4386) cause an
295+ // infinite loop because Fortune's algorithm produces near-zero-length
296+ // edges whose endpoints differ by less than floating-point tolerance
297+ // but have different vertex IDs. These degenerate edges cannot
298+ // attach because:
299+ // 1. Their endpoints don't match any chain vertex by ID.
300+ // 2. The chain may already be closed (front[0] == back[1]).
301+ // 3. The boundary-bridging logic doesn't apply when the chain
302+ // endpoints are interior (not on the domain boundary).
303+ // Such edges are safely dropped — they represent floating-point
304+ // artifacts where two boundary intersection points should be
305+ // identical in exact arithmetic.
306+ auto remainingBeforeStall = rawEdges[i].size ();
307+ while (!(rawEdges[i].empty ()) && (remainingBeforeStall != 0 ))
287308 {
309+ --remainingBeforeStall;
288310 curr = rawEdges[i].front ();
289311 rawEdges[i].pop_front ();
290- frontbnd = Pointonbnd (front[0 ]);
291- backbnd = Pointonbnd (back[1 ]);
312+
313+ // Check if this edge is a degenerate near-zero-length artifact.
314+ // Fortune's algorithm can produce edges whose two endpoints map
315+ // to the same geometric point (within DIFF_TOLERENCE) but have
316+ // different vertex IDs. These carry no geometric information
317+ // and can be safely discarded.
318+ const PointType & edgeStart = m_OutputVD->GetVertex (curr[0 ]);
319+ const PointType & edgeEnd = m_OutputVD->GetVertex (curr[1 ]);
320+ if (!differentPoint (edgeStart, edgeEnd))
321+ {
322+ itkDebugMacro (" Dropping degenerate near-zero-length edge ["
323+ << curr[0 ] << " (" << edgeStart[0 ] << " ," << edgeStart[1 ] << " ) -> " << curr[1 ] << " ("
324+ << edgeEnd[0 ] << " ," << edgeEnd[1 ] << " )]"
325+ << " for cell " << i << " : endpoints within DIFF_TOLERENCE=" << DIFF_TOLERENCE);
326+ // Count as progress — this edge is resolved (discarded).
327+ remainingBeforeStall = rawEdges[i].size ();
328+ continue ;
329+ }
330+
331+ unsigned char frontbnd = Pointonbnd (front[0 ]);
332+ unsigned char backbnd = Pointonbnd (back[1 ]);
333+ bool edgeAttached = true ;
292334 if (curr[0 ] == back[1 ])
293335 {
294336 buildEdges.push_back (curr);
@@ -357,20 +399,38 @@ VoronoiDiagram2DGenerator<TCoordRepType>::ConstructDiagram()
357399 else
358400 {
359401 rawEdges[i].push_back (curr);
402+ edgeAttached = false ;
360403 }
361404 }
362405 else
363406 {
364407 rawEdges[i].push_back (curr);
408+ edgeAttached = false ;
365409 }
410+ if (edgeAttached)
411+ {
412+ // Progress was made — chain endpoints changed, so previously
413+ // unattachable edges may now be attachable.
414+ remainingBeforeStall = rawEdges[i].size ();
415+ }
416+ }
417+ // After assembly, all edges for this cell should have been either
418+ // attached to the chain or identified as degenerate artifacts.
419+ // Any remaining edges indicate an unexpected algorithmic failure.
420+ if (!rawEdges[i].empty ())
421+ {
422+ itkExceptionMacro (" VoronoiDiagram2DGenerator::ConstructDiagram: "
423+ << rawEdges[i].size () << " non-degenerate edge(s) could not be "
424+ << " assembled into cell " << i << " boundary chain. "
425+ << " This indicates an unexpected geometric configuration." );
366426 }
367427
368428 curr = buildEdges.front ();
369429 curr1 = buildEdges.back ();
370430 if (curr[0 ] != curr1[1 ])
371431 {
372- frontbnd = Pointonbnd (curr[0 ]);
373- backbnd = Pointonbnd (curr1[1 ]);
432+ unsigned char frontbnd = Pointonbnd (curr[0 ]);
433+ unsigned char backbnd = Pointonbnd (curr1[1 ]);
374434 if ((frontbnd != 0 ) && (backbnd != 0 ))
375435 {
376436 if (frontbnd == backbnd)
0 commit comments