Skip to content

Commit a1d3d9e

Browse files
sbryngelsonclaude
andcommitted
refactor: replace ray casting with generalized winding number for inside/outside test
Replace the 26-ray casting approach in f_model_is_inside_flat with the generalized winding number (Jacobson et al., SIGGRAPH 2013). The winding number sums the solid angle subtended by each triangle at the query point using the Van Oosterom-Strackee formula. This is robust to small triangles: a tiny triangle contributes a proportionally small solid angle rather than being missed entirely by rays. It is also winding-order independent and branch-free in the inner loop, which is GPU-friendly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a9e3726 commit a1d3d9e

1 file changed

Lines changed: 44 additions & 38 deletions

File tree

src/common/m_model.fpp

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -583,11 +583,16 @@ contains
583583

584584
end function f_model_is_inside
585585

586-
!> This procedure, given a cell center will determine if a point exists instide a surface
587-
!! @param ntrs Number of triangles in the model
588-
!! @param pid Patch ID od this model
586+
!> This procedure determines if a point is inside a surface using
587+
!! 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,
590+
!! ~0.0 outside. Unlike ray casting, this is robust to small
591+
!! triangles and vertex winding order.
592+
!! @param ntrs Number of triangles in the model.
593+
!! @param pid Patch ID of this model.
589594
!! @param point Point to test.
590-
!! @return fraction The perfentage of candidate rays cast indicate that we are inside the model
595+
!! @return fraction Winding number (~1.0 inside, ~0.0 outside).
591596
function f_model_is_inside_flat(ntrs, pid, point) result(fraction)
592597

593598
$:GPU_ROUTINE(parallelism='[seq]')
@@ -597,43 +602,44 @@ contains
597602
real(wp), dimension(1:3), intent(in) :: point
598603

599604
real(wp) :: fraction
600-
type(t_ray) :: ray
601-
type(t_triangle) :: tri
602-
integer :: i, j, k, q, nInOrOut, nHits
603605

604-
! cast 26 rays from the point and count the number at leave the boundary
605-
nInOrOut = 0
606-
do i = -1, 1
607-
do j = -1, 1
608-
do k = -1, 1
609-
if (i /= 0 .or. j /= 0 .or. k /= 0) then
610-
! We cannot get inersections if the ray is exactly in line with triangle plane
611-
if (p == 0 .and. k == 0) cycle
612-
613-
! generate the ray
614-
ray%o = point
615-
ray%d(:) = [real(i, wp), real(j, wp), real(k, wp)]
616-
ray%d = ray%d/sqrt(real(abs(i) + abs(j) + abs(k), wp))
617-
618-
! count the number of intersections
619-
nHits = 0
620-
do q = 1, ntrs
621-
tri%v(:, :) = gpu_trs_v(:, :, q, pid)
622-
nHits = nHits + f_intersects_triangle(ray, tri)
623-
end do
624-
! if the ray intersected an odd number of times, we must be inside
625-
nInOrOut = nInOrOut + mod(nHits, 2)
626-
end if
627-
end do
628-
end do
606+
real(wp) :: r1(3), r2(3), r3(3)
607+
real(wp) :: r1_mag, r2_mag, r3_mag
608+
real(wp) :: numerator, denominator
609+
integer :: q
610+
611+
fraction = 0.0_wp
612+
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)
629638
end do
630639

631-
if (p == 0) then
632-
! in 2D, we skipped 8 rays
633-
fraction = real(nInOrOut)/18._wp
634-
else
635-
fraction = real(nInOrOut)/26._wp
636-
end if
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))
637643

638644
end function f_model_is_inside_flat
639645

0 commit comments

Comments
 (0)