@@ -2427,8 +2427,11 @@ static int remove_duplicate_intersections(double *slist, int count, double tol)
24272427#define MESH_MAX_INTERSECTIONS 256
24282428
24292429/* Count ray-mesh intersections using BVH traversal.
2430- Collects all intersection t-values, deduplicates, and returns the count. */
2431- static int count_ray_mesh_intersections (const mesh * m , vector3 origin , vector3 dir ) {
2430+ Collects all intersection t-values, deduplicates, and returns the count.
2431+ If had_degenerate is non-NULL, sets it to 1 if any near-duplicate
2432+ t-values were found (indicating a potential edge/vertex hit). */
2433+ static int count_ray_mesh_intersections_ex (const mesh * m , vector3 origin , vector3 dir ,
2434+ int * had_degenerate ) {
24322435 double slist [MESH_MAX_INTERSECTIONS ];
24332436 int count = mesh_ray_all_intersections (m , origin , dir , slist , MESH_MAX_INTERSECTIONS );
24342437
@@ -2440,32 +2443,46 @@ static int count_ray_mesh_intersections(const mesh *m, vector3 origin, vector3 d
24402443
24412444 if (nforward > 1 ) {
24422445 qsort (slist , nforward , sizeof (double ), mesh_dcmp );
2443- nforward = remove_duplicate_intersections (slist , nforward , 1e-10 );
2446+ int deduped = remove_duplicate_intersections (slist , nforward , 1e-10 );
2447+ if (had_degenerate ) * had_degenerate = (deduped != nforward );
2448+ nforward = deduped ;
2449+ } else {
2450+ if (had_degenerate ) * had_degenerate = 0 ;
24442451 }
24452452
24462453 return nforward ;
24472454}
24482455
2456+ static int count_ray_mesh_intersections (const mesh * m , vector3 origin , vector3 dir ) {
2457+ return count_ray_mesh_intersections_ex (m , origin , dir , NULL );
2458+ }
2459+
24492460/***************************************************************/
24502461/* Core mesh geometric operations */
24512462/***************************************************************/
24522463
2464+ static const vector3 mesh_ray_dirs [3 ] = {
2465+ {0.57735026918962576 , 0.57735026918962576 , 0.57735026918962576 }, /* (1,1,1)/√3 */
2466+ {0.80178372573727319 , 0.53452248382484879 , 0.26726124191242440 }, /* (3,2,1)/√14 */
2467+ {0.12309149097933272 , 0.49236596391733088 , 0.86164043685532904 }, /* (1,4,7)/√66 */
2468+ };
2469+
24532470static boolean point_in_mesh (const mesh * m , vector3 p ) {
24542471 if (!m -> is_closed ) return 0 ;
24552472
2456- /* Cast rays along multiple directions and take majority vote.
2457- A single ray can give wrong parity when it passes through a mesh
2458- edge or vertex, causing deduplication to miss or double-count
2459- a crossing. Three irrational directions make it extremely unlikely
2460- that more than one ray hits a degenerate case at the same point. */
2461- static const vector3 dirs [ 3 ] = {
2462- { 0.57735026918962576 , 0.57735026918962576 , 0.57735026918962576 }, /* (1,1,1)/√3 */
2463- { 0.80178372573727319 , 0.53452248382484879 , 0.26726124191242440 }, /* (3,2,1)/√14 */
2464- { 0.12309149097933272 , 0.49236596391733088 , 0.86164043685532904 }, /* (1,4,7)/√66 */
2465- };
2466- int votes = 0 ;
2467- for (int d = 0 ; d < 3 ; d ++ ) {
2468- int crossings = count_ray_mesh_intersections (m , p , dirs [d ]);
2473+ /* Fast path: cast one ray. If no degenerate edge/vertex hits were
2474+ detected during deduplication, trust the result immediately.
2475+ Only fall back to 3-ray majority vote when a degenerate case
2476+ is detected (near-duplicate t-values removed). */
2477+ int had_degenerate ;
2478+ int crossings = count_ray_mesh_intersections_ex ( m , p , mesh_ray_dirs [ 0 ], & had_degenerate );
2479+ if (! had_degenerate )
2480+ return ( crossings % 2 ) == 1 ;
2481+
2482+ /* Degenerate hit detected — cast two more rays and take majority vote. */
2483+ int votes = ( crossings % 2 == 1 ) ? 1 : 0 ;
2484+ for (int d = 1 ; d < 3 ; d ++ ) {
2485+ crossings = count_ray_mesh_intersections (m , p , mesh_ray_dirs [d ]);
24692486 if (crossings % 2 == 1 ) votes ++ ;
24702487 }
24712488 return votes >= 2 ;
@@ -2593,9 +2610,65 @@ static void init_mesh(geometric_object *o) {
25932610 m -> centroid = vector3_plus (m -> centroid , m -> vertices .items [i ]);
25942611 m -> centroid = vector3_scale (1.0 / nv , m -> centroid );
25952612
2596- /* Check if mesh is closed (every edge shared by exactly 2 faces)
2597- and ensure consistent outward-facing normals. */
2598- m -> is_closed = 1 ;
2613+ /* Check if mesh is closed: every edge must be shared by exactly 2 faces.
2614+ An edge is identified by a sorted pair of vertex indices. We use a
2615+ hash table mapping edge → face count. */
2616+ {
2617+ /* Hash table size: next power of 2 above 3*nf (each face has 3 edges). */
2618+ int htsize = 1 ;
2619+ while (htsize < 4 * nf ) htsize <<= 1 ;
2620+ int htmask = htsize - 1 ;
2621+
2622+ /* Each bucket stores (v_lo, v_hi, count). Use open addressing. */
2623+ int * ht_vlo = (int * )calloc (htsize , sizeof (int ));
2624+ int * ht_vhi = (int * )calloc (htsize , sizeof (int ));
2625+ int * ht_cnt = (int * )calloc (htsize , sizeof (int ));
2626+ /* Mark empty slots with -1. */
2627+ for (int i = 0 ; i < htsize ; i ++ ) ht_vlo [i ] = -1 ;
2628+
2629+ m -> is_closed = 1 ;
2630+ for (int f = 0 ; f < nf && m -> is_closed ; f ++ ) {
2631+ for (int e = 0 ; e < 3 ; e ++ ) {
2632+ int va = m -> face_indices [3 * f + e ];
2633+ int vb = m -> face_indices [3 * f + (e + 1 ) % 3 ];
2634+ int vlo = (va < vb ) ? va : vb ;
2635+ int vhi = (va < vb ) ? vb : va ;
2636+ unsigned int h = (unsigned int )(vlo * 73856093u ^ vhi * 19349663u ) & htmask ;
2637+ /* Linear probing. */
2638+ while (1 ) {
2639+ if (ht_vlo [h ] == -1 ) {
2640+ /* Empty slot: insert new edge. */
2641+ ht_vlo [h ] = vlo ;
2642+ ht_vhi [h ] = vhi ;
2643+ ht_cnt [h ] = 1 ;
2644+ break ;
2645+ } else if (ht_vlo [h ] == vlo && ht_vhi [h ] == vhi ) {
2646+ /* Found existing edge. */
2647+ ht_cnt [h ]++ ;
2648+ if (ht_cnt [h ] > 2 ) m -> is_closed = 0 ; /* non-manifold edge */
2649+ break ;
2650+ }
2651+ h = (h + 1 ) & htmask ;
2652+ }
2653+ }
2654+ }
2655+ /* Check that all edges have exactly 2 faces. */
2656+ if (m -> is_closed ) {
2657+ for (int i = 0 ; i < htsize ; i ++ ) {
2658+ if (ht_vlo [i ] != -1 && ht_cnt [i ] != 2 ) {
2659+ m -> is_closed = 0 ;
2660+ break ;
2661+ }
2662+ }
2663+ }
2664+ free (ht_vlo );
2665+ free (ht_vhi );
2666+ free (ht_cnt );
2667+
2668+ if (!m -> is_closed )
2669+ ctl_printf ("WARNING: mesh is not closed (not all edges shared by exactly 2 faces).\n"
2670+ " point_in_mesh results may be incorrect.\n" );
2671+ }
25992672
26002673 /* Use signed volume to check/fix normal orientation. */
26012674 double signed_vol = 0 ;
0 commit comments