Ray Casting
This guide covers ray casting in Crater.rs, which allows you to test for intersections between rays and regions. Ray casting is essential for applications like rendering, collision detection, and path finding.
What is Ray Casting?
Ray casting is the process of shooting a ray (a line with an origin and direction) into a scene and finding where it intersects with geometry. In Crater.rs, rays are cast against regions to find surface intersections.
A ray is defined by:
- Origin: Starting point of the ray
- Direction: Normalized vector indicating the ray's direction
- Extension: The point where the ray (may) intersect the surface
See the Ray Casting Gallery for examples of ray casting in action.
Creating Rays
The Rays
struct represents a batch of rays that can be cast at once, leveraging the performance of Burn.
Ray creation from device memory
use crater::analysis::prelude::*; use burn::backend::wgpu::{Wgpu, WgpuDevice}; use burn::backend::Autodiff; use burn::prelude::*; fn main() { let device = WgpuDevice::default(); // Create ray origins and directions as tensors let origins = Tensor::<Autodiff<Wgpu>, 2>::from_data( [ [0.0, 0.0, 2.0], // Ray 1: above origin [1.0, 0.0, 2.0], // Ray 2: above (1,0,0) [0.0, 1.0, 2.0], // Ray 3: above (0,1,0) ], &device ); let directions = Tensor::<Autodiff<Wgpu>, 2>::from_data( [ [0.0, 0.0, -1.0], // Ray 1: pointing down [0.0, 0.0, -1.0], // Ray 2: pointing down [0.0, 0.0, -1.0], // Ray 3: pointing down ], &device ); let rays = Rays::<_, 3>::new(origins, directions); }
Ray allocation from CPU memory
For testing and simple cases, you can use the new_from_slices
method:
use crater::analysis::prelude::*; use burn::backend::wgpu::{Wgpu, WgpuDevice}; use burn::backend::Autodiff; use burn::prelude::*; fn main() { let device = WgpuDevice::default(); let rays = Rays::<Autodiff<Wgpu>, 3>::new_from_slices( &[ [0.0, 0.0, 2.0], // Origins [1.0, 0.0, 2.0], [0.0, 1.0, 2.0], ], &[ [0.0, 0.0, -1.0], // Directions [0.0, 0.0, -1.0], [0.0, 0.0, -1.0], ], device ); }
Ray Casting Algorithms
Crater.rs provides three ray casting algorithms, each with different trade-offs:
Algorithm | Pros | Cons | Best Used For |
---|---|---|---|
Analytical | • Very fast and precise • Direct mathematical solutions • No iterative approximation | • Only works with analytically solvable shapes | Simple primitive shapes (spheres, planes, etc.) |
Bracket-and-Bisect | • Works with any implicit surface • Handles complex CSG operations • Robust and predictable | • Slower than analytical methods • Requires parameter tuning | Complex CSG regions, general-purpose ray casting |
Newton's Method | • Fast convergence for smooth surfaces • Good for differentiable implicit functions | • Can fail on discontinuous surfaces • Requires gradient information • May not converge for all cases | Smooth, differentiable implicit surfaces |
Usage
use crater::{analysis::prelude::*, csg::prelude::*, primitives::prelude::*}; use burn::backend::wgpu::{Wgpu, WgpuDevice}; use burn::backend::Autodiff; use burn::prelude::*; fn main() { let device = WgpuDevice::default(); let rays = Rays::new_from_slices(&[[0.0, 0.0, 2.0]], &[[0.0, 0.0, -1.0]], device.clone()); let sphere = Field3D::<Autodiff<Wgpu>>::sphere(1.0, device.clone()).into_isosurface(0.0).region(); // Choose the appropriate algorithm for your use case let results = sphere.ray_cast( rays, &Algebra::default(), RayCastAlgorithm::Analytical // or BracketAndBisect or Newton ); }
Interpreting Results
The RayCastResult
struct contains comprehensive information about the ray casting operation:
use crater::{analysis::prelude::*, csg::prelude::*, primitives::prelude::*}; use burn::backend::wgpu::{Wgpu, WgpuDevice}; use burn::backend::Autodiff; use burn::prelude::*; fn main() { let device = WgpuDevice::default(); let rays = Rays::new_from_slices(&[[0.0, 0.0, 2.0]], &[[0.0, 0.0, -1.0]], device.clone()); let sphere = Field3D::<Autodiff<Wgpu>>::sphere(1.0, device.clone()).into_isosurface(0.0).region(); let results = sphere.ray_cast(rays, &Algebra::default(), RayCastAlgorithm::Analytical); // Access hit points (extensions) let hit_points = results.extensions(); // [n_rays, 3], Float // Access distances to hit points let distances = results.distances(); // [n_rays], Float // Access field values at hit points let field_values = results.field_values(); // [n_rays], Float // Check which rays hit the surface let hit_mask = results.field_values().is_on_mask(); // [n_rays], Bool // Get only the successful hits let surface_hits = results.hits_on_surface(); // [n_rays, 3], Float // Count successful hits let hit_count = hit_mask.int().sum(); // [1], Int }
Understanding Ray Failures
When a ray doesn't intersect with the surface, the result contains RAY_FAIL_VALUE = f32::MAX
:
use crater::{analysis::prelude::*, csg::prelude::*, primitives::prelude::*}; use burn::backend::wgpu::{Wgpu, WgpuDevice}; use burn::backend::Autodiff; use burn::prelude::*; fn main() { let device = WgpuDevice::default(); let sphere = Field3D::<Autodiff<Wgpu>>::sphere(1.0, device.clone()) .into_isosurface(0.0) .region(); // Ray that misses the sphere let rays = Rays::new_from_slices( &[[2.0, 2.0, 2.0]], // Origin outside sphere &[[1.0, 1.0, 1.0]], // Direction pointing away device.clone() ); let results = sphere.ray_cast( rays, &Algebra::default(), RayCastAlgorithm::Analytical ); // Check if ray hit let hit_mask = results.field_values().is_on_mask(); let hit = hit_mask.clone().any().into_scalar(); if !hit { println!("Ray missed the sphere"); } }
Ray Casting with Builtins
Field2D
use crater::{analysis::prelude::*, csg::prelude::*, primitives::prelude::*}; use burn::backend::wgpu::{Wgpu, WgpuDevice}; use burn::backend::Autodiff; use burn::prelude::*; fn main() { let device = WgpuDevice::default(); // Circle let circle = Field2D::<Autodiff<Wgpu>>::circle(1.0, device.clone()) .into_isosurface(0.0) .region(); // Line (infinite line through origin) let line = Field2D::<Autodiff<Wgpu>>::line([0.0, 1.0], device.clone()) .into_isosurface(0.0) .region(); // Ray from below circle pointing up let rays = Rays::new_from_slices( &[[0.0, -2.0]], // Origin below circle &[[0.0, 1.0]], // Direction upward device.clone() ); // Basic halfspace regions let circle_results = circle.ray_cast( rays.clone(), &Algebra::default(), RayCastAlgorithm::Analytical ); // Composite CSG regions work just as well // "Above the line and inside the circle" let region = circle & -line; let results = region.ray_cast( rays, &Algebra::default(), RayCastAlgorithm::Analytical ); }
Field3D
use crater::{analysis::prelude::*, csg::prelude::*, primitives::prelude::*}; use burn::backend::wgpu::{Wgpu, WgpuDevice}; use burn::backend::Autodiff; use burn::prelude::*; fn main() { let device = WgpuDevice::default(); // Sphere let sphere = Field3D::<Autodiff<Wgpu>>::sphere(1.0, device.clone()) .into_isosurface(0.0) .region(); // Plane let plane = Field3D::<Autodiff<Wgpu>>::plane([0.0, 0.0, 1.0], device.clone()) .into_isosurface(0.0) .region(); // Grid of rays from above let rays = Rays::new_from_slices( &[ [0.0, 0.0, 2.0], [0.5, 0.0, 2.0], [0.0, 0.5, 2.0], ], &[ [0.0, 0.0, -1.0], // All pointing down [0.0, 0.0, -1.0], [0.0, 0.0, -1.0], ], device.clone() ); // Basic halfspace regions let sphere_results = sphere.ray_cast( rays.clone(), &Algebra::default(), RayCastAlgorithm::Analytical ); // Composite CSG regions work just as well // "Inside the sphere and above the plane" let region = sphere & -plane; let results = region.ray_cast( rays, &Algebra::default(), RayCastAlgorithm::Analytical ); }
Next Steps
Now that you understand ray casting:
- Surface Analysis - Learn about surface normal calculation
- Volume Estimation - Understand volume computation methods
- Theory - Dive deeper into the mathematical foundations