A ray meets a surface's triangles without gaps, across a shared edge or corner as inside a face #15

Merged
agra merged 2 commits from task/3889-a-ray-exactly-along-an-edge-shared-by-tw into master 2026-10-29 15:23:34 +02:00
Owner

Tracker: #3889

A ray that crossed a surface exactly on an edge or a corner two or more triangles share could pass between them. rayTriangle computed the hit point in each triangle's own plane and tested it against that triangle's edges. Each neighbour rounded the point its own way, so both could reject it. On the baked first map a vertical ray at Blender (180, 0), a vertex of the terrain's 2 m grid, missed the world. So did 32,946 of the 346,921 vertical rays on the map's integer lattice, the points on its grid lines and on its cells' diagonals.

What changes

rayTriangle (src/physics/collision.sx) now runs the watertight ray/triangle test (Woop, Benthin and Wald, 2013).

  • The ray's frame. ray builds a Ray: the axis the ray's direction leans along most, and a shear that makes the ray run straight down that axis. seen places each corner in that frame, so a corner lands at one point whichever triangle it belongs to.
  • Edge weights in f64. Each corner's weight, across the edge opposite it, is computed in f64. Products of f32s are exact there, so the sign is exact, and swapping the ends negates the value exactly. A ray that crosses a closed surface therefore meets at least one of its triangles. A ray across a shared edge or corner meets every triangle that shares it.
  • One distance per edge. On an edge, each triangle's own weights could round the distance their own way. So edgeDistance takes the distance from the edge's ends alone, by a formula symmetric in them, and every triangle sharing the edge answers the same bits. At a corner, the weights already give exactly the corner's distance. The existing tie rule, least distance and then lowest index, therefore names the lowest-indexed triangle sharing the edge or corner.

Rays are queried only by tests: nothing in src/ or tools/ calls ray, and the bake is unchanged.

Tests

tests/physics_queries.sx:

  • a sloped 3×3-vertex terrain patch, each cell split by its diagonal as the first map's terrain is, with a ray down a grid line and one through the vertex six triangles share, each at its height and naming the lowest index;
  • a ray through the middle of a shared edge whose distance lies halfway between two f32s, where the two triangles' own weights would round it apart;
  • that ray against each of the two triangles in all six corner orders, so the shared edge takes each of the three corner positions, either way round: all twelve answer the same distance;
  • a ray a few nanometres off a shared edge, which f32 weights read as on it;
  • a slanted ray, a level ray at an upright face, and a ray in a face's plane, which misses;
  • near and nearVec fail on NaN, which > let through.

At master (71450a5) the grid-line, vertex, halfway and hair-off rays fail.

tests/first_map_world.sx: the ray at Blender (180, 0) over the baked map meets the ground that a ray 1 cm off meets. groundAt no longer says its points must avoid the grid lines, and assets/scenes/first_map_prefabs.py no longer says WRECK lies off them so that a ray down meets the floor there. tests/physics_seeded.sx calls the new rayTriangle and rayHit signatures.

Evidence

  • Lattice, baked map. 346,921 vertical rays from scratch probes: 32,946 missed at master, 0 at the head. Each now names the lowest index among the triangles tied at its distance, and no ray sees two claims within 0.1 mm that are not equal.
  • Synthetic heightfield. A closed 64×64-cell heightfield with random heights, probed with rays aimed at its vertices, grid lines and diagonals, each starting above the field and ending below it. At master the vertex, grid-line, diagonal and vertical batches missed 8,902 of 68,601, 4,194 of 71,164, 6,386 of 70,621 and 22,639 of 100,000. At the head they missed 0 of each.
  • Mutants. Each of the following fails a test: f32 weights; no edge rule; the edge weights swapped; the edge always along either axis; a one-sided test; edges excluded; no zero-area check; the main axis always y; either shear's sign flipped; no scale; no distance bounds. The corner-order case also fails each edge position's rule taken wrong or off: the edge on corners b and c, c and a, or a and b giving another edge's distance (or corner b's), or falling back to the triangle's own weights, and edgeDistance picking its axis by a signed difference.
  • Gate. sak-gate full passed at 999c9d5 (gate run 818), 24 of 24.
Tracker: [#3889](https://sak.swipelab.com/tasks/3889) A ray that crossed a surface exactly on an edge or a corner two or more triangles share could pass between them. `rayTriangle` computed the hit point in each triangle's own plane and tested it against that triangle's edges. Each neighbour rounded the point its own way, so both could reject it. On the baked first map a vertical ray at Blender (180, 0), a vertex of the terrain's 2 m grid, missed the world. So did 32,946 of the 346,921 vertical rays on the map's integer lattice, the points on its grid lines and on its cells' diagonals. ## What changes `rayTriangle` (src/physics/collision.sx) now runs the watertight ray/triangle test (Woop, Benthin and Wald, 2013). - **The ray's frame.** `ray` builds a `Ray`: the axis the ray's direction leans along most, and a shear that makes the ray run straight down that axis. `seen` places each corner in that frame, so a corner lands at one point whichever triangle it belongs to. - **Edge weights in f64.** Each corner's weight, `across` the edge opposite it, is computed in f64. Products of f32s are exact there, so the sign is exact, and swapping the ends negates the value exactly. A ray that crosses a closed surface therefore meets at least one of its triangles. A ray across a shared edge or corner meets every triangle that shares it. - **One distance per edge.** On an edge, each triangle's own weights could round the distance their own way. So `edgeDistance` takes the distance from the edge's ends alone, by a formula symmetric in them, and every triangle sharing the edge answers the same bits. At a corner, the weights already give exactly the corner's distance. The existing tie rule, least distance and then lowest index, therefore names the lowest-indexed triangle sharing the edge or corner. Rays are queried only by tests: nothing in src/ or tools/ calls `ray`, and the bake is unchanged. ## Tests tests/physics_queries.sx: - a sloped 3×3-vertex terrain patch, each cell split by its diagonal as the first map's terrain is, with a ray down a grid line and one through the vertex six triangles share, each at its height and naming the lowest index; - a ray through the middle of a shared edge whose distance lies halfway between two f32s, where the two triangles' own weights would round it apart; - that ray against each of the two triangles in all six corner orders, so the shared edge takes each of the three corner positions, either way round: all twelve answer the same distance; - a ray a few nanometres off a shared edge, which f32 weights read as on it; - a slanted ray, a level ray at an upright face, and a ray in a face's plane, which misses; - `near` and `nearVec` fail on NaN, which `>` let through. At master (71450a5) the grid-line, vertex, halfway and hair-off rays fail. tests/first_map_world.sx: the ray at Blender (180, 0) over the baked map meets the ground that a ray 1 cm off meets. `groundAt` no longer says its points must avoid the grid lines, and assets/scenes/first_map_prefabs.py no longer says WRECK lies off them so that a ray down meets the floor there. tests/physics_seeded.sx calls the new `rayTriangle` and `rayHit` signatures. ## Evidence - **Lattice, baked map.** 346,921 vertical rays from scratch probes: 32,946 missed at master, 0 at the head. Each now names the lowest index among the triangles tied at its distance, and no ray sees two claims within 0.1 mm that are not equal. - **Synthetic heightfield.** A closed 64×64-cell heightfield with random heights, probed with rays aimed at its vertices, grid lines and diagonals, each starting above the field and ending below it. At master the vertex, grid-line, diagonal and vertical batches missed 8,902 of 68,601, 4,194 of 71,164, 6,386 of 70,621 and 22,639 of 100,000. At the head they missed 0 of each. - **Mutants.** Each of the following fails a test: f32 weights; no edge rule; the edge weights swapped; the edge always along either axis; a one-sided test; edges excluded; no zero-area check; the main axis always y; either shear's sign flipped; no scale; no distance bounds. The corner-order case also fails each edge position's rule taken wrong or off: the edge on corners b and c, c and a, or a and b giving another edge's distance (or corner b's), or falling back to the triangle's own weights, and `edgeDistance` picking its axis by a signed difference. - **Gate.** `sak-gate full` passed at 999c9d5 (gate run 818), 24 of 24.
agra merged commit 7787db2cde into master 2026-10-29 15:23:34 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sx/hulken!15
No description provided.