kpreid / all-is-cubes

Yet another block/voxel game; in this one the blocks are made out of blocks. Runs in browsers on WebGL+WebAssembly.
https://kpreid.dreamwidth.org/tag/all+is+cubes
Apache License 2.0
147 stars 8 forks source link

Rigorous recursive raycast #488

Open kpreid opened 2 months ago

kpreid commented 2 months ago

We have a function called recursive_ray, which is used by the raytracer and cursor to descend into a block's voxels. It is just implemented as:

pub(crate) fn recursive_ray(ray: Ray, cube: Cube, resolution: Resolution) -> Ray {
    Ray {
        origin: ((ray.origin - cube.lower_bounds().map(FreeCoordinate::from))
            * FreeCoordinate::from(resolution))
        .to_point(),
        direction: ray.direction,
    }
}

However, this approach is subject to floating-point error (I think; a test case proving that would be good). Instead, we should build it directly into RaycastStep so that it can generate a child Raycaster with exactly the right state, guaranteeing that it will step through at least one voxel that lies on the surface of the relevant block (as opposed to, say, entirely missing the block by passing diagonally through an edge of its bounds).

kpreid commented 1 month ago

Commit 0f34c9a7241bea2c824e48c147d5d02b47ddb1d1 replaces block::recursive_ray() with raycast::RaycastStep::recursive_raycast(). I also added a basic test. None of the actual improvements have been done yet.

I wanted to add a Raycaster::ray() method that conveniently returns the ray, but that turned out to be difficult because Raycaster modifies its ray field for its own purposes. Something to revisit later.

kpreid commented 1 week ago

On further consideration of the problem, I'm thinking that the current impl Iterator for Raycaster design isn't the best way to proceed — we want to be able to fetch more pieces of the raycaster's internal state, which is inconsistent with having a single, non-borrowing step (iterator item) type.

Also, here's an interesting question about recursive raycasts: we've been thinking about cube-containment consistency when entering voxels, but what about exiting? What if numerical error in the recursive cast leads to the recursive ray changing direction a little? This could result in a similar diagonal crack from the ray passing through a gap. Suppose we have half-blocks A and B arranged like this (+s mark the outer level block grid):

+AAA+   +
AAAA
    BBBB
    BBBB
+   +   +

Then it might be possible for a ray exiting A to miss the solid part of B and render as a crack.

In order to avoid this, we would want to not just have a special procedure for recursive-enter but one for recursive-exit too — the inner Raycaster writing some of its state back to the outer one. Possibly the API would be a single integrated TwoLevelRaycaster of some sort.