Skip to content

Commit 28d8d50

Browse files
sbryngelsonclaude
andcommitted
fix: add 2D winding number branch for p==0 simulations
The 3D solid angle (Van Oosterom-Strackee) degenerates when all triangles are coplanar (z=0), producing near-zero winding numbers everywhere. For 2D (p==0), use the 2D winding number instead: sum the signed angle subtended by each boundary edge via atan2(cross,dot). This uses the existing gpu_boundary_v and gpu_boundary_edge_count arrays that are already computed and uploaded for 2D STL models. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a1d3d9e commit 28d8d50

1 file changed

Lines changed: 53 additions & 32 deletions

File tree

src/common/m_model.fpp

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -585,10 +585,11 @@ contains
585585

586586
!> This procedure determines if a point is inside a surface using
587587
!! the generalized winding number (Jacobson et al., SIGGRAPH 2013).
588-
!! The winding number is the sum of signed solid angles subtended
589-
!! by each triangle, normalized by 4*pi. Returns ~1.0 inside,
588+
!! In 3D, sums the solid angle subtended by each triangle (Van
589+
!! Oosterom-Strackee formula). In 2D (p==0), sums the signed
590+
!! angle subtended by each boundary edge. Returns ~1.0 inside,
590591
!! ~0.0 outside. Unlike ray casting, this is robust to small
591-
!! triangles and vertex winding order.
592+
!! triangles/edges and vertex winding order.
592593
!! @param ntrs Number of triangles in the model.
593594
!! @param pid Patch ID of this model.
594595
!! @param point Point to test.
@@ -606,40 +607,60 @@ contains
606607
real(wp) :: r1(3), r2(3), r3(3)
607608
real(wp) :: r1_mag, r2_mag, r3_mag
608609
real(wp) :: numerator, denominator
610+
real(wp) :: d1(2), d2(2)
609611
integer :: q
610612

611613
fraction = 0.0_wp
612614

613-
do q = 1, ntrs
614-
r1 = gpu_trs_v(1, :, q, pid) - point
615-
r2 = gpu_trs_v(2, :, q, pid) - point
616-
r3 = gpu_trs_v(3, :, q, pid) - point
617-
618-
r1_mag = sqrt(dot_product(r1, r1))
619-
r2_mag = sqrt(dot_product(r2, r2))
620-
r3_mag = sqrt(dot_product(r3, r3))
621-
622-
! Van Oosterom-Strackee formula:
623-
! tan(Omega/2) = numerator / denominator
624-
! numerator = scalar triple product r1 . (r2 x r3)
625-
numerator = r1(1)*(r2(2)*r3(3) - r2(3)*r3(2)) &
626-
+ r1(2)*(r2(3)*r3(1) - r2(1)*r3(3)) &
627-
+ r1(3)*(r2(1)*r3(2) - r2(2)*r3(1))
628-
629-
denominator = r1_mag*r2_mag*r3_mag &
630-
+ dot_product(r1, r2)*r3_mag &
631-
+ dot_product(r2, r3)*r1_mag &
632-
+ dot_product(r3, r1)*r2_mag
633-
634-
! Solid angle = 2 * atan2(num, den).
635-
! atan2(0,0) = 0 per IEEE 754, so degenerate triangles
636-
! contribute nothing without special casing.
637-
fraction = fraction + atan2(numerator, denominator)
638-
end do
615+
if (p == 0) then
616+
! 2D winding number: sum signed angles subtended by
617+
! each boundary edge at the query point.
618+
do q = 1, gpu_boundary_edge_count(pid)
619+
d1(1) = gpu_boundary_v(q, 1, 1, pid) - point(1)
620+
d1(2) = gpu_boundary_v(q, 1, 2, pid) - point(2)
621+
d2(1) = gpu_boundary_v(q, 2, 1, pid) - point(1)
622+
d2(2) = gpu_boundary_v(q, 2, 2, pid) - point(2)
623+
624+
! Signed angle = atan2(d1 x d2, d1 . d2)
625+
fraction = fraction + atan2( &
626+
d1(1)*d2(2) - d1(2)*d2(1), &
627+
d1(1)*d2(1) + d1(2)*d2(2))
628+
end do
629+
630+
! 2D winding number = total angle / (2*pi)
631+
fraction = fraction/(2.0_wp*acos(-1.0_wp))
632+
else
633+
! 3D winding number: sum solid angles via Van
634+
! Oosterom-Strackee formula.
635+
do q = 1, ntrs
636+
r1 = gpu_trs_v(1, :, q, pid) - point
637+
r2 = gpu_trs_v(2, :, q, pid) - point
638+
r3 = gpu_trs_v(3, :, q, pid) - point
639+
640+
r1_mag = sqrt(dot_product(r1, r1))
641+
r2_mag = sqrt(dot_product(r2, r2))
642+
r3_mag = sqrt(dot_product(r3, r3))
643+
644+
! tan(Omega/2) = numerator / denominator
645+
! numerator = scalar triple product r1 . (r2 x r3)
646+
numerator = r1(1)*(r2(2)*r3(3) - r2(3)*r3(2)) &
647+
+ r1(2)*(r2(3)*r3(1) - r2(1)*r3(3)) &
648+
+ r1(3)*(r2(1)*r3(2) - r2(2)*r3(1))
649+
650+
denominator = r1_mag*r2_mag*r3_mag &
651+
+ dot_product(r1, r2)*r3_mag &
652+
+ dot_product(r2, r3)*r1_mag &
653+
+ dot_product(r3, r1)*r2_mag
654+
655+
! atan2(0,0) = 0 per IEEE 754, so degenerate
656+
! triangles contribute nothing.
657+
fraction = fraction + atan2(numerator, denominator)
658+
end do
639659

640-
! Winding number = total solid angle / (4 * pi)
641-
! Each triangle contributes 2*atan2, so sum / (2*pi)
642-
fraction = fraction/(2.0_wp*acos(-1.0_wp))
660+
! Winding number = total solid angle / (4*pi)
661+
! Each triangle contributes 2*atan2, so sum / (2*pi)
662+
fraction = fraction/(2.0_wp*acos(-1.0_wp))
663+
end if
643664

644665
end function f_model_is_inside_flat
645666

0 commit comments

Comments
 (0)