Crater.rs: N-Dimensional Constructive Solid Geometry

crater.rs is a library for constructing and analyzing N-dimensional scalar fields. Primary features include:

  • Constructive Solid Geometry (CSG) for N-dimensional scalar fields
  • Ray casting for N-dimensional CSG regions
  • Volume estimation for N-dimensional CSG regions
  • Surface analysis for N-dimensional CSG regions
  • Marching cubes for isosurface extraction
  • Field visualization for N-dimensional scalar fields

Ready to start building? Head to Getting Started for installation instructions and your first example.

If you're not yet convinced, check out the Ray Casting Gallery, showing crater.rs's most sophisticated feature. This gallery is generated from the examples/raycast/gallery.rs example.

Citation and References

When using crater.rs in academic work, please cite:

@software{crater_rs,
  title={`crater.rs`: N-Dimensional Constructive Solid Geometry Library},
  author={Clyde Huibregtse},
  year={2025},
  url={https://gitlab.com/games1122013/crater.rs/},
  version={0.8.0}
}

Getting Started

This guide introduces you to the core abstractions in crater.rs. Understanding these concepts is essential for working effectively with the library.

Installation

Add Crater.rs to your Cargo.toml:

[dependencies]
crater = "0.7.0"

Scalar Fields

A scalar field is a function that assigns a single number to every point in space. In Crater.rs, we use scalar fields to represent geometry implicitly.

For a 3D scalar field, we have a function where:

  • means the point is inside the shape
  • means the point is on the surface
  • means the point is outside the shape

Example: Sphere

A sphere centered at origin with radius has the scalar field:

In Crater.rs:

use crater::{csg::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
let sphere = Field3D::<Wgpu>::sphere(1.0, device.clone());

// Create a grid of origins in R³
// [n³, 3]
let n = 8; // Grid size
let grid_data = (0..n).flat_map(
        |i| (0..n).flat_map(
            move |j| (0..n).flat_map(
                move |k| [i as f32, j as f32, k as f32]
            )
        )
    ).collect::<Vec<_>>();
let origins = Tensor::<Wgpu, 1, Float>::from_data(
    grid_data.as_slice(),
    &device
).reshape([n * n * n, 3]);

// Evaluate the scalar field at the origins
// [n³, 1]
let evaluations = sphere.evaluate(origins);
}

Isosurfaces

An isosurface is the set of all points where a scalar field equals a constant value. The most common isosurface is at level 0, which represents the surface of the shape.

Creating Isosurfaces

use crater::csg::prelude::*;
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
let sphere = Field3D::<Wgpu>::sphere(1.0, device.clone());
// Continuing from the previous example
let surface = sphere.clone().into_isosurface(0.0);

// Points slightly outside the sphere (f = 0.1)
let outer_surface = sphere.clone().into_isosurface(0.1);

// Points slightly inside the sphere (f = -0.1)
let inner_surface = sphere.clone().into_isosurface(-0.1);
}

Regions

A region represents a solid volume in space. It's the continuous "inside" of an isosurface, defined by where a scalar field is negative.

From Field to Region

use crater::csg::prelude::*;
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
let sphere = Field3D::<Wgpu>::sphere(1.0, device.clone());
let surface = sphere.into_isosurface(0.0);
// Convert to region (solid volume)
let sphere_region = surface.region();
}

The .region() method creates a solid region from the isosurface.

Region Operations

Regions support intuitive boolean operations:

use crater::{csg::prelude::*, primitives::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
let sphere = Field3D::<Wgpu>::sphere(1.0, device.clone()).into_isosurface(0.0).region();
let cube = BoundingBox::new([-0.8, -0.8, -0.8], [0.8, 0.8, 0.8])
    .into_region(device.clone());

// Union (logical OR)
let union = &sphere | &cube;

// Intersection (logical AND)  
let intersection = &sphere & &cube;

// Negation (logical NOT)
let negation = -&sphere;

// Difference (logical \ and NOT)
let difference = sphere & -cube;
}

Meshing

Isosurfaces can be visualized using marching cubes:

use crater::{csg::prelude::*, primitives::prelude::*, serde::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
let sphere_field = Field3D::<Wgpu>::sphere(1.0, device.clone());
// Convert isosurface to region for marching cubes
let region = sphere_field.into_isosurface(0.0).region();

// Generate mesh
let mesh = marching_cubes::<Wgpu>(
    &MarchingCubesParams {
        region,
        bounds: BoundingBox::new([-2.0, -2.0, -2.0], [2.0, 2.0, 2.0]),
        resolution: (64, 64, 64),
        algebra: Algebra::default(),
    },
    &device
);

// Export for visualization
let mesh_collection: MeshCollection = mesh.into();
mesh_collection.write_stl("sphere.stl").unwrap();
}

Constructive Solid Geometry (CSG)

CSG allows you to build complex shapes by combining simpler ones using boolean operations.

Basic Operations

Union (OR: |)

Combines two shapes into one larger shape:

use crater::{csg::prelude::*, primitives::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
let shape1 = Field3D::<Wgpu>::sphere(1.0, device.clone())
    .into_isosurface(0.0)
    .region();
let shape2 = Field3D::<Wgpu>::sphere(1.0, device.clone())
    .into_isosurface(0.0)
    .region()
    .transform(Translate([1.5, 0.0, 0.0]));

let union = &shape1 | &shape2;  // Dumbbell shape
}

Intersection (AND: &)

Keeps only the overlapping parts:

use crater::{csg::prelude::*, primitives::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
let sphere = Field3D::<Wgpu>::sphere(1.0, device.clone())
    .into_isosurface(0.0)
    .region();
let cube = BoundingBox::new([-0.8, -0.8, -0.8], [0.8, 0.8, 0.8])
    .into_region(device.clone());

let intersection = sphere & cube;  // Rounded cube
}

Transformations

Regions can be transformed using standard geometric operations:

Translation

use crater::{csg::prelude::*, primitives::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
let sphere_region = Field3D::<Wgpu>::sphere(1.0, device.clone()).into_isosurface(0.0).region();
let moved_sphere = sphere_region.transform(Translate([1.0, 2.0, 0.0]));
}

Rotation

use crater::{csg::prelude::*, primitives::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
let cylinder_region = Field3D::<Wgpu>::cylinder(1.0, device.clone()).into_isosurface(0.0).region();
let rotated_cylinder = cylinder_region.transform(
    RotateAbout::new(0, 1, std::f32::consts::FRAC_PI_2, &device)
);
}

Scaling

use crater::{csg::prelude::*, primitives::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
let sphere_region = Field3D::<Wgpu>::sphere(1.0, device.clone()).into_isosurface(0.0).region();
let scaled_sphere = sphere_region
    .transform(ScaleDim(0, 2.0))
    .transform(ScaleDim(1, 1.0))
    .transform(ScaleDim(2, 1.0));
}

Working with Different Dimensions

Crater.rs supports arbitrary dimensions through type parameters:

use crater::{csg::prelude::*, primitives::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
// 2D shapes
let circle = Field2D::<Wgpu>::circle(1.0, device.clone());
let line = Field2D::<Wgpu>::line([0.0, 1.0], device.clone());

// 3D shapes  
let sphere = Field3D::<Wgpu>::sphere(1.0, device.clone());
let cone = Field3D::<Wgpu>::cone([0.0, 0.0, 1.0], 0.5, device.clone());

// N-dimensional shapes
let hypersphere = FieldND::<4, Wgpu>::hypersphere(1.0, device.clone());
}

Next Steps

Now that you understand the core concepts:

  1. Ray Casting - Learn how to ray cast against Regions
  2. Theory - Mathematical underpinnings of the library

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:

AlgorithmProsConsBest Used For
Analytical• Very fast and precise
• Direct mathematical solutions
• No iterative approximation
• Only works with analytically solvable shapesSimple 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:

  1. Surface Analysis - Learn about surface normal calculation
  2. Volume Estimation - Understand volume computation methods
  3. Theory - Dive deeper into the mathematical foundations

Volume Estimation

Volume estimation is the process of computing the volume of a region in space. Since regions in crater.rs can represent complex CSG Regions where analytical volumes are difficult, we use Monte Carlo methods for robust volume approximation.

The Monte Carlo approach:

  • Sample: Generate random points in a bounding box
  • Test: Check if each point is inside the region
  • Estimate: Volume = (inside_count / total_count) × bounding_box_volume

Basic Volume Estimation

The monte_carlo_volume method is available on all regions:

use crater::{csg::prelude::*, primitives::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::backend::Autodiff;
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();

// Create a unit sphere
let sphere = Field3D::<Autodiff<Wgpu>>::sphere(1.0, device.clone())
    .into_isosurface(0.0)
    .region();

// Define bounding box that encompasses the sphere
let bounds = BoundingBox::new([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]);

// Estimate volume with 100,000 samples
let volume = sphere.monte_carlo_volume(100_000, &bounds, &Algebra::default());

// Analytical volume of unit sphere = 4/3 * π ≈ 4.19
println!("Estimated volume: {:.2}", volume);
}

Understanding Parameters

Sample Count

More samples improve accuracy but increase computation time:

use crater::{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();
let bounds = BoundingBox::new([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]);
// Low accuracy, fast
let volume_1k = sphere.monte_carlo_volume(1_000, &bounds, &Algebra::default());

// Medium accuracy, moderate speed
let volume_100k = sphere.monte_carlo_volume(100_000, &bounds, &Algebra::default());

// High accuracy, slower (~3x more accurate than 100k samples)
let volume_1m = sphere.monte_carlo_volume(1_000_000, &bounds, &Algebra::default());
}

Error decreases as O(1/√N) where N is the sample count.

Bounding Box

The bounding box should tightly encompass your region:

use crater::{csg::prelude::*, primitives::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::backend::Autodiff;
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
// Sphere with radius 2.0
let sphere = Field3D::<Autodiff<Wgpu>>::sphere(2.0, device.clone())
    .into_isosurface(0.0)
    .region();

// Good: tight bounds
let tight_bounds = BoundingBox::new([-2.0, -2.0, -2.0], [2.0, 2.0, 2.0]);

// Bad: loose bounds (wastes samples)
let loose_bounds = BoundingBox::new([-10.0, -10.0, -10.0], [10.0, 10.0, 10.0]);

let volume_tight = sphere.monte_carlo_volume(100_000, &tight_bounds, &Algebra::default());
let volume_loose = sphere.monte_carlo_volume(100_000, &loose_bounds, &Algebra::default());

// Both give same result, but tight bounds are more efficient
}

Volume Estimation with Primitives

2D Shapes

use crater::{csg::prelude::*, primitives::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::backend::Autodiff;
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
// Unit circle
let circle = Field2D::<Autodiff<Wgpu>>::circle(1.0, device.clone())
    .into_isosurface(0.0)
    .region();

let bounds_2d = BoundingBox::new([-1.0, -1.0], [1.0, 1.0]);
let circle_area = circle.monte_carlo_volume(100_000, &bounds_2d, &Algebra::default());

// Analytical area = π ≈ 3.14
println!("Circle area: {:.2}", circle_area);
}

3D Shapes

use crater::{csg::prelude::*, primitives::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::backend::Autodiff;
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
// Cube
let cube_bounds = BoundingBox::new([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]);
let cube: Region<3, Autodiff<Wgpu>> = cube_bounds.into_region(device.clone());
let cube_volume = cube.monte_carlo_volume(100_000, &cube_bounds, &Algebra::default());

// Cone
let cone = Field3D::<Autodiff<Wgpu>>::cone(
    [0.0, 0.0, 1.0], // axis
    std::f32::consts::FRAC_PI_4, // 45-degree half-angle
    device.clone()
).into_isosurface(0.0).region();

let cone_bounds = BoundingBox::new([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]);
let cone_volume = cone.monte_carlo_volume(100_000, &cone_bounds, &Algebra::default());
}

Higher Dimensions

use crater::{csg::prelude::*, primitives::prelude::*};
use burn::backend::wgpu::{Wgpu, WgpuDevice};
use burn::backend::Autodiff;
use burn::prelude::*;
fn main() {
let device = WgpuDevice::default();
// 4D hypersphere
let hypersphere = FieldND::<4, Autodiff<Wgpu>>::hypersphere(1.0, device.clone())
    .into_isosurface(0.0)
    .region();

let bounds_4d = BoundingBox::new([-1.0; 4], [1.0; 4]);
let hypervolume = hypersphere.monte_carlo_volume(100_000, &bounds_4d, &Algebra::default());

// Analytical 4D sphere volume = π²/2 * r⁴ ≈ 4.93 for r=1
println!("4D sphere volume: {:.2}", hypervolume);
}

Bounding Box Optimization

Tight bounds are more efficient for achieving an expected uncertainty.

use crater::{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();
// Tight bounds: 8 volume units, ~50% hit rate
let tight = BoundingBox::new([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]);

// Loose bounds: 1000 volume units, ~0.4% hit rate  
let loose = BoundingBox::new([-5.0, -5.0, -5.0], [5.0, 5.0, 5.0]);

let tight_volume = sphere.monte_carlo_volume(100_000, &tight, &Algebra::default());
let loose_volume = sphere.monte_carlo_volume(100_000, &loose, &Algebra::default());

}

Rescaling

This guide provides practical examples and detailed usage patterns for rescaling transformations in Crater. For the mathematical theory, see Rescaling Transformations.

Available Rescaling Functions

Hyperbolic Tangent Family

Standard Tanh:

  • Domain:
  • Range:
  • Properties: Smooth function, preserves sign, symmetric about origin
  • Parameters: controls transition steepness

Scaled Tanh:

  • Domain:
  • Range:
  • Properties: Configurable center point and half-range
#![allow(unused)]
fn main() {
use crater::csg::prelude::*;
use crater::csg::rescale::RescaleScheme;
use burn::backend::wgpu::WgpuDevice;
use burn::backend::wgpu::Wgpu;

fn example_tanh_rescaling() {
    // Standard tanh: maps to (-1, 1)
    let device = WgpuDevice::default();
    let field = Field3D::<Wgpu>::sphere(2.0, device).into_isosurface(0.0);
    let tanh_field = field.clone().rescale(RescaleScheme::Tanh { k: 2.0 });
    
    // Scaled tanh: maps to (3, 7) with center at 5
    let scaled_field = field.rescale(RescaleScheme::ScaledTanh {
        center: 5.0,
        scale: 2.0,
        k: 1.0,
    });
}
}

Logistic (Sigmoid) Family

Standard Sigmoid:

  • Domain:
  • Range:
  • Properties: Smooth function, monotonically increasing, asymptotic bounds
  • Numerical Stability: Implementation uses different formulas for positive/negative inputs

Scaled Sigmoid:

  • Domain:
  • Range:
  • Properties: Configurable minimum and range

Arctangent Family

Standard Arctan:

  • Domain:
  • Range:
  • Properties: Smooth function, preserves sign, linear near origin for small

Scaled Arctan:

  • Domain:
  • Range:

Error Function

Error Function:

  • Domain:
  • Range:
  • Properties: Smooth function, Gaussian-like transition, preserves sign
  • Implementation: High-quality approximation using

Soft Clipping

Soft Clip:

  • Domain:
  • Range:
  • Properties: Preserves sign, linear for small inputs, asymptotic bounds
  • Continuity: Continuous everywhere but not differentiable at when

Practical Examples

Example 1: Normalizing Distance Fields

use crater::csg::prelude::*;
use crater::csg::rescale::RescaleScheme;
use burn::backend::wgpu::WgpuDevice;
use burn::backend::wgpu::Wgpu;

fn main() {
    let device = WgpuDevice::default();
    // Create a sphere distance field
    let sphere = Field3D::<Wgpu>::sphere(2.0, device);
    // Normalize to (-1, 1) range with tanh
    let normalized = sphere.rescale(RescaleScheme::Tanh { k: 0.5 });
}

Example 2: Creating Probability-Like Fields

use crater::csg::prelude::*;
use crater::csg::rescale::RescaleScheme;
use burn::backend::wgpu::WgpuDevice;
use burn::backend::wgpu::Wgpu;

fn main() {
    let device = WgpuDevice::default();
    // Create a sphere distance field
    let sphere = Field3D::<Wgpu>::sphere(2.0, device);
    // Convert distance field to probability-like values in (0, 1)
    let probability_field = sphere.rescale(RescaleScheme::Sigmoid { k: 2.0 });
    
    // Values near the surface (distance ≈ 0) map to ≈ 0.5
    // Negative distances (inside) map to values < 0.5
    // Positive distances (outside) map to values > 0.5
}

Example 3: Custom Range Mapping

use crater::csg::prelude::*;
use crater::csg::rescale::RescaleScheme;
use burn::backend::wgpu::WgpuDevice;
use burn::backend::wgpu::Wgpu;

fn main() {
    let device = WgpuDevice::default();
    // Map field values to engineering units [0, 100] 
    let field = Field3D::<Wgpu>::sphere(2.0, device).into_isosurface(0.0);
    let engineering_field = field.rescale(RescaleScheme::ScaledSigmoid {
        min: 0.0,
        range: 100.0,
        k: 1.0,
    });
}

Example 4: Large-Scale Numerical Stability

This example demonstrates how rescaling prevents numerical failures with large geometric features:

use crater::csg::prelude::*;
use crater::analysis::prelude::*;
use burn::backend::wgpu::WgpuDevice;
use burn::backend::wgpu::Wgpu;
use burn::backend::Autodiff;
use burn::prelude::*;

fn main() {
    let device = WgpuDevice::default();
    // Create a very large sphere (radius = 100 units)
    // Without rescaling, distance values would range from -∞ to +∞
    let large_sphere = Field3D::<Autodiff<Wgpu>>::sphere(100.0, device.clone()).into_isosurface(0.0);
    
    // Apply tanh rescaling to bound values to (-1, 1)
    // k = 0.01 provides smooth transition around the surface
    let rescaled_sphere = large_sphere.rescale(RescaleScheme::Tanh { k: 0.01 });
    
    // Test points at various distances from center
    let test_points = [
        [0.0, 0.0, 0.0],     // Center: distance = -100 → tanh(-1) ≈ -0.76
        [50.0, 0.0, 0.0],    // Halfway: distance = -50 → tanh(-0.5) ≈ -0.46  
        [100.0, 0.0, 0.0],   // Surface: distance = 0 → tanh(0) = 0
        [150.0, 0.0, 0.0],   // Outside: distance = 50 → tanh(0.5) ≈ 0.46
        [200.0, 0.0, 0.0],   // Far outside: distance = 100 → tanh(1) ≈ 0.76
    ];
    
    let results = rescaled_sphere.evaluate(Tensor::<Autodiff<Wgpu>, 2, Float>::from_data(test_points, &device));
    
    // All values are guaranteed to be in (-1, 1)
    // Ray casting and other algorithms converge reliably
    // No overflow or underflow issues with extreme distances
    
    // Ray casting that would fail with unbounded fields now works
    let rays = Rays::new_from_slices(
        &[[110.0, 0.0, 0.0], [90.0, 0.0, 0.0]], 
        &[[-1.0, 0.0, 0.0], [1.0, 0.0, 0.0]],   
        device
    );
    
    let intersections = rescaled_sphere.region().ray_cast(
        rays,
        &Algebra::default(),
        RayCastAlgorithm::Analytical
    );
    // ✓ Successful intersection finding with bounded field values
}

Why This Example Matters: Large distance values (±100 units) would cause floating-point precision issues in iterative algorithms

Parameter Selection Guidelines

Steepness Parameter

  • Small (< 1): Gradual transition, nearly linear near origin
  • Large (> 5): Sharp transition, step-function-like behavior
  • : Balanced transition suitable for most applications

Range Parameters

  • Choose based on downstream requirements
  • Consider numerical precision of target system
  • Ensure compatibility with other field operations

Scalar Fields, Isosurfaces and Constructive Solid Geometry

This page provides the mathematical foundation for this library. Understanding of the notation, conventions and constructions in this page is integral to effective use the advanced methods the library has to offer.

Tensor Notation

This library employs tensor notation throughout. Understanding the notation conventions will help clarify the mathematical exposition.

  • Objects with no indices are scalar-valued. (e.g. , )
  • Objects with one or more indices are tensor-valued. (e.g. , , )
  • Rank- tensors (vectors) are represented in lower-case, rank- where tensors use upper-case. (e.g. , )
  • Tensor operations use Einstein summation notation, eliding the summation notation (e.g. )
  • Element-wise or broadcasted multiplication is indicated by juxtaposition (e.g. ). Shared indices indicate broadcasted multiplication (e.g. is the action of multiplying each row of by the -th element of ).
  • The spacial dimension is indexed by , and the batch dimension is indexed by . (e.g. , )

Scalar Fields and Isosurfaces

Consider a scalar-valued function . This function is a field defined over a domain . An isosurface of is the set of points such that , where is a constant. Without loss of generality, we study only.

Naturally, we define the surface of , , as the set of points such that .

Classifying vectors as inside or outside the region enclosed by is a common task in computer graphics. Specifically, a vector is inside the surface if and outside the surface if .

Batch Processing with Tensors

In the form above, operates sequentially over vectors . To enable batch evaluation of , we define a tensor-valued function .

where is a rank- tensor with length . Concretely, satisfies the following properties:

where is the Kronecker delta.

The first property states that is equivalent to evaluating over each row of independently.

The second property states that the Jacobian of with respect to our choice of input is block diagonal, meaning each output row is a function of only its corresponding input row.

For the remainder of this work, we opt to use our batch, tensor-valued formulation of for all computations.

Transformations

Any function can be used to perform covariant transformations on our scalar fields, without having to change our definition of .

Intuitively, the transformation is not on , but on the domain of .

Concretely, we can implement common transformations on our scalar fields by choosing an appropriate .

Translation

Translation can be written as a tensor-valued function that takes a tensor and returns a translated tensor .

where is the translation vector.

Scaling

Scale can be written as a tensor-valued function that takes a tensor and returns a scaled tensor .

where is the scale factor.

Rotation

A general -dimensional coordinate rotation can be written as an operator that takes a tensor and returns a rotated tensor and .

Rotations like can be represented by a sequence of individual plane rotations. A plane rotation is a rotation in a 2D plane spanned by two coordinate axes, and , and is parameterized by a rotation angle . Call this plane rotation . Then, the general -dimensional rotation can be written as a product of plane rotations.

Intuitively, is the identity tensor.

CSG Operations

The fundamental object in CSG is the Region, which is defined as the set of points such that . Regions are combined using the set-theoretic operations of union, intersection, and difference 4. Mathematically, these operations are defined as the following tensor:

In direct set notation, these composite Regions are defined as follows:

These operations align with our intuitive understanding of set theory over , but they are expressed solely in terms of scalar fields.

CSG Algebras

In practice, one is free to override and redefine these operations. We define an Algebra as an implementation of the above contract. This is an algebra in the mathematical sense, as well as in our algorithms. The theoretical foundation for R-functions used in CSG operations was established by Rvachev 3.

where is the power set of .

Advanced CSG Operations

Beyond the basic operations, crater.rs implements more sophisticated ways to combine scalar fields.

Differentiable Approximations

In practice, one may wish to use a differentiable approximation of the basic CSG operations. These methods provide smooth blending capabilities as described in the Function Representation literature 2.

where is a parameter that controls the smoothness of the operation. reduces to the original min-max operations.

Blending

One such custom CSG algebra enables continuous blending between two regions.

The blending function is defined as follows, and is added to the outputs of both the and operators.

Ray Casting

This page describes the mathematical theory behind the ray casting algorithms implemented in Crater.rs. Ray casting has been fundamental to computer graphics since the early work of Appel 6.

Mathematical Foundation

Ray casting is the computational problem of finding a tensor such that , where is the function representing ray extensions:

Here, represents ray origins and represents ray directions.

This library implements multiple algorithms for determining ray intersections with isosurfaces and regions. The algorithms are configured via the RayCastAlgorithm enum.

Analytical Method

The analytical method for computing ray intersections is a special, but highly performant and precise algorithm. This algorithm computes the intersection point analytically, by making the above 1-D implicit function explicit:

is reformulated as:

Where is a RayField. RayFields are similar to ScalarFields, but take an extra input parameter, a tensor of unit directions . The field returns the rank-1 tensor of distances from to the point at which along the direction .

A valid may not be analytically available. Therefore, the analytical method is not available generally, for all . For common ScalarFields in this library, the mapping is implemented already.

Concrete Implementations

(Hyper)planes

For a hyperplane with normal vector , we define the following two quantities:

Then, the ray field is:

There are a few degenerate cases:

  • If , then the ray is parallel to the hyperplane, and the ray field is undefined.
  • If , then the ray origin is on the hyperplane.
  • If , then the ray intersection is behind the ray origin.

(Hyper)spheres

For a hypersphere with radius , the primal scalar field is defined as:

For ray intersections, the 1-D implicit function is:

The final equation is a quadratic form over . Using the quadratic equation:

The discriminant is:

The possible solutions for take the form:

There are a few cases:

  • If , then the ray does not intersect the sphere.
  • If , then the ray intersects the sphere at a single point (tangent).
  • If , then the ray intersects the sphere at two points, one or both of which may be behind the ray.

In all cases, we take the smallest non-negative root.

(Hyper)cones

The primal scalar field for a cone with unit-axis and opening angle is defined as:

For ray intersections, the 1-D implicit function is:

The final equation is a quadratic form over . Using the quadratic equation:

The discriminant is:

The possible solutions for take the form:

There are a few cases:

  • If , then the ray does not intersect the cone.
  • If , then the ray intersects the cone at a single point (tangent).
  • If , then the ray intersects the cone at two points, one or both of which may be behind the ray.

In all cases, we take the smallest non-negative root.

(Hyper)cylinders

The primal scalar field for a hypercylinder with radius extending along the last dimension is defined as:

where the summation is over the first dimensions. For ray intersections, we project the problem to the first dimensions:

Let for and for .

The 1-D implicit function becomes:

This reduces to a quadratic form with coefficients:

The solutions follow the same quadratic formula as hyperspheres, taking the smallest non-negative root.

(Hyper)tori

The primal scalar field for a hypertorus with major radius and minor radius is defined as:

For ray intersections, the analytical solution involves solving a quartic equation, which is significantly more complex than quadratic equations. Due to this complexity and numerical stability concerns, analytical solutions are not implemented for hypertori. Alternative approaches like sphere tracing 8 may be more suitable for such complex implicit surfaces.

Bracket and Bisect Algorithm

The ray casting process consists of two main steps:

  1. Ray Bracketing: Finding intervals along rays that contain potential intersections
  2. Ray Bisection: Refining these intervals to precisely locate intersection points

Ray Bracketing Algorithm

The ray bracketing algorithm efficiently finds intervals along rays that contain isosurface intersections. This implementation leverages tensor operations for parallel processing of multiple rays.

Algorithm Steps:

  1. Initialize parameters:
    • Tensor field , ray origins , unit directions
    • Step size , maximum iterations
  2. Initialize ray parameters:
    • (left bracket)
    • (right bracket)
  3. Iterate until convergence or max iterations:
    • Set
    • Evaluate field at right bracket:
    • Create mask for rays that don't yet have a bracket:
    • Update brackets using the mask
  4. Return brackets such that

Ray Bracketing

Ray Bisection Algorithm

After bracketing finds intervals containing isosurface intersections, the bisection algorithm refines these brackets to precisely locate the intersection points.

Algorithm Steps:

  1. Initialize with bracketing tensors containing the isosurface
  2. Iterate until convergence or max iterations:
    • Compute midpoint:
    • Create masks for which half contains the root:
    • Update brackets based on which half contains the root
  3. Return refined intersection points

Ray Bisection

Volume Estimation Methods

This page describes the Monte Carlo volume estimation methods implemented in Crater.rs. Monte Carlo methods were pioneered by Metropolis and Ulam 7 and have become essential tools for numerical computation in high-dimensional spaces.

Consider a scalar field . We define the region enclosed by the surface as:

Where is the domain of interest.

Monte Carlo Volume Estimation

Monte Carlo volume estimation is a probabilistic method for approximating the volume of complex regions. This method is particularly useful for high-dimensional spaces where traditional grid-based methods would suffer from the curse of dimensionality.

Algorithm Steps:

  1. Generate random points uniformly distributed in domain :
  2. Evaluate the scalar field at these points:
  3. Count the number of points inside the region:
  4. Compute the volume approximation:

To approximate the volume of , we sample points from a uniform distribution, and count the fraction of points that fall inside the region:

Where:

  • is the known volume of the domain
  • is the volume we're estimating
  • is the indicator function that equals 1 if the condition is true, 0 otherwise

Error Analysis

The error in this approximation decreases as , where is the number of points sampled. This makes it suitable for high-dimensional volume estimation where traditional grid-based methods would suffer from the curse of dimensionality.

The standard error of the volume estimate is approximately:

Where is the estimated proportion of the domain occupied by the region.

Alternative Methods

While Monte Carlo methods are the primary approach in Crater.rs, other volume estimation methods include:

  • Grid-based methods: Discretize space into regular grids (suffers from curse of dimensionality)
  • Analytical methods: Exact solutions for simple primitives (limited applicability)
  • Adaptive sampling: Concentrate samples in regions of high curvature or complexity

Monte Carlo methods provide the best balance of generality, accuracy, and performance for complex CSG geometries.

Rescaling Transformations

Rescaling transformations provide mathematically precise control over scalar field output ranges through univariate functions.

Given a scalar field , a rescaling transformation produces a new field:

where is a univariate function, applied element-wise to the field. This composition preserves the geometric structure of the original field while transforming scalar values according to the chosen function .

Function Classes

  1. Hyperbolic Tangent Family: Standard and scaled tanh functions
  2. Logistic (Sigmoid) Family: Standard and scaled sigmoid functions
  3. Arctangent Family: Standard and scaled arctan functions
  4. Error Function: Gaussian-like transition with erf approximation
  5. Soft Clipping: Linear preservation with asymptotic bounds

Properties of Rescaling

Rescaling transformations maintain several important geometric properties:

  1. Isosurface Preservation: If is an isosurface of , then the transformed field has corresponding isosurfaces at .
  2. Monotonicity: For strictly monotonic , the relative ordering of field values is preserved.
  3. Bounded Output: Many rescaling functions map to bounded intervals, providing guaranteed output ranges.
  4. Differentiability: Most rescaling functions are smooth functions, meaning they have continuous derivatives of all orders. This property is crucial for:
    • Gradient-based optimization
    • Surface normal computation
    • Analytical ray-surface intersection

Rescaling for Ray Cast Stability

Rescaling transformations are crucial for ensuring numerical stability in ray casting operations. Consider the contrived construction:

is a univariate function, representing the scalar field's value along a single ray. Ray casting is the process of finding a root of to within a tolerance .

The floating point number line is not uniformly distributed. The difference between two adjacent numbers that are representable increases as the numbers get larger. This means for high-gradient fields (e.g., large shapes where, at the isosurface, the field is changing rapidly), there is no representable value of that can satisfy the tolerance . This is shown in the first inset below.

Ray Casting Stability

To rectify this, we use our rescaling transformations to map the field values to a closed interval. This has the effect of vertically compressing the field. It is shown in the second inset above.

This method ensures that at least one value of exists that satisfies the tolerance , and for continuous fields, the converged value of is at most one floating point number away from the true root.

References

For foundational mathematical techniques:

  • Abramowitz & Stegun Handbook of Mathematical Functions (1964) 12
  • Press et al. Numerical Recipes (2007) 13
  • Boyd & Vandenberghe Convex Optimization (2004) 14

Isosurface Analysis

Surface analysis in crater.rs is based on the properties of scalar fields and their derivatives. Given a scalar field , we can compute various surface properties.

Gradient Computation

The gradient of a scalar field at origins is the vector of partial derivatives:

In crater.rs, gradients are computed using automatic differentiation through the Burn framework.

Gradient Properties

  • The gradient points in the direction of steepest increase of the scalar field
  • The magnitude of the gradient indicates the rate of change
  • At points on an isosurface , the gradient is normal to the surface

Surface Normals

For origins on an isosurface , the unit normal vector is:

Surface Curvature

Surface curvature measures how much the surface deviates from being flat. For a surface defined by , the mean curvature is:

Where is the Hessian matrix of second derivatives.

Marching Cubes Algorithm

Marching cubes is one of the most widely used algorithms for isosurface extraction, producing a triangular mesh approximation of the isosurface 1.

In crater.rs, the algorithm is implemented with GPU acceleration using the Burn tensor computation library, making it highly efficient even for large grids.

Overview

The marching cubes algorithm converts implicit surfaces (represented by scalar fields) into explicit triangular meshes. This is essential for visualization, 3D printing, and further geometric processing.

Why Marching Cubes?

  • Universal: Works with any scalar field function
  • Robust: Handles complex topologies automatically
  • Efficient: GPU-accelerated implementation
  • Widely Supported: Standard mesh format output (STL, VTK)

Core Algorithm Steps

  1. Grid Construction: Create a voxel grid with vertices within a bounding box
  2. Field Evaluation: Evaluate the scalar field at each grid vertex: for all vertices
  3. Voxel Processing: For each voxel in the grid:
    • Determine which of the 8 corners are inside or outside the isosurface
    • Compute the 8-bit cube index (0-255) based on corner classifications. This can be done with a tensor convolution over reshaped to with a kernel of shape and stride 1.
    • If cube index indicates the isosurface intersects the voxel:
      • Lookup triangle configuration in edge table
      • For each edge intersected by the isosurface:
        • Interpolate vertices to find the exact intersection point
      • Construct triangles using the interpolated intersection points
  4. Mesh Assembly: Collect all triangles into a single mesh

Mathematical Foundation

Cube Classification

Each voxel has 8 corners, labeled 0-7. For each corner , we evaluate:

  • If : corner is inside (bit = 1)
  • If : corner is outside (bit = 0)

The cube index is computed as:

This gives 256 possible configurations (0-255).

Marching Cubes Configurations

Edge Interpolation

When the isosurface crosses an edge between vertices and with field values and , the intersection point is:

where the interpolation parameter is:

This assumes linear interpolation along the edge.

Interactive Raycast Gallery

Generated automatically via CI/CD using:

cargo run --example raycast-gallery -- --output gallery.html --ray-count 2500

Contributing to Crater.rs

Commit Message Guidelines

Crater.rs uses git-cliff to generate changelogs based on commit messages. Please follow the syntax defined in ./cliff.toml.

Examples

For new features:

feat: new feature added

For bug fixes:

fix: bug squashed!

Development Setup

Building the Documentation

To build and serve the mdBook locally with hot-reload:

./scripts/build-docs.sh --serve

This will start a local server at http://localhost:3000 with automatic reload when you make changes to the documentation.

Running Tests

Before submitting changes, ensure all tests pass:

cargo test

Static Analysis and Styling

Before submitting changes, ensure the project passes lint checks:

./scripts/static-analysis.sh

Release Process (Maintainers Only)

Crater.rs uses cargo-release for automated releases. The release process is handled by CI/CD:

How Releases Work

  1. Automated CI/CD Pipeline: The release job in CI configures git for direct push access via OAuth
  2. cargo-release Execution: This tool automatically:
    • Runs ./scripts/pre-release.sh which:
      • Executes end-to-end tests and generates artifacts in ./artifacts
      • Runs git-cliff (configured in cliff.toml) to generate changelog
    • Updates version numbers across all relevant files
    • Publishes to crates.io
    • Creates commit with changes tagged as "chore" (skips CI to avoid loops)
    • Adds git tag for the release
    • Pushes changes and tags to the main branch

Creating a Release

As a maintainer, when a merge request lands:

  1. The release job becomes available for manual execution
  2. Start the job with the appropriate bump level:
    BUMP_LEVEL=(patch|minor|major)
    

Choose the bump level based on the type of changes:

  • patch: Bug fixes and small improvements
  • minor: New features that maintain backward compatibility
  • major: Breaking changes

The automated process handles everything else, ensuring consistent and reliable releases.

Bibliography

Academic Papers

  1. Lorensen, W. E., & Cline, H. E. (1987). Marching cubes: A high resolution 3D surface construction algorithm

    • ACM DLFree PDF
    • Seminal paper introducing the marching cubes algorithm for extracting polygonal meshes from volumetric data.
  2. Pasko, A., Adzhiev, V., Sourin, A., & Savchenko, V. (1995). Function representation in geometric modeling: concepts, implementation and applications

    • SpringerResearchGate PDF
    • Foundational paper for F-rep (Function Representation) modeling, directly relevant to implicit surface work in Crater.rs.
  3. Rvachev, V. L. (1982). Theory of R-functions and some applications

  4. Ricci, A. (1973). A constructive geometry for computer graphics

    • DOI
    • Early work on constructive solid geometry for computer graphics.
  5. Frisken, S. F., Perry, R. N., Rockwood, A. P., & Jones, T. R. (2000). Adaptively sampled distance fields: a general representation of shape for computer graphics

    • DOI
    • Adaptive distance field representation for efficient shape modeling.
  6. Appel, A. (1968). Some techniques for shading machine renderings of solids

    • ACM DL
    • Early work on ray casting and computer graphics rendering.
  7. Metropolis, N., & Ulam, S. (1949). The Monte Carlo method

    • JSTOR
    • Introduction to Monte Carlo methods for numerical computation.
  8. Hart, J. C. (1996). Sphere tracing: A geometric method for the antialiased rendering of implicit surfaces

    • Springer
    • Sphere tracing algorithm for rendering implicit surfaces.
  9. Bloomenthal, J., Bajaj, C., Blinn, J., Cani-Gascuel, M. P., Rockwood, A., Wyvill, B., & Wyvill, G. (1997). Introduction to implicit surfaces

    • Google BooksAmazon
    • Comprehensive introduction to implicit surface modeling and techniques.
  10. Requicha, A. A. (1980). Representations for rigid solids: Theory, methods, and systems

    • ACM DLSemantic Scholar
    • ACM Computing Surveys, 12(4), 437-464 - Foundational work on solid modeling representations.
  11. Glassner, A. S. (Ed.). (1989). An introduction to ray tracing

  12. Abramowitz, M., & Stegun, I. A. (1964). Handbook of Mathematical Functions with Formulas, Graphs, and Mathematical Tables

    • National Bureau of Standards
    • Comprehensive reference for special functions, numerical methods, and mathematical formulas.
  13. Press, W. H., Teukolsky, S. A., Vetterling, W. T., & Flannery, B. P. (2007). Numerical Recipes: The Art of Scientific Computing (3rd ed.)

    • Cambridge University Press
    • Practical numerical algorithms and computational techniques.
  14. Boyd, S., & Vandenberghe, L. (2004). Convex Optimization

    • Cambridge University Press
    • Mathematical foundations of optimization theory and smooth function analysis.

Software and Libraries

  1. Burn Deep Learning Framework

    • WebsiteGitHub
    • GPU-accelerated tensor computation framework used by Crater.rs.
  2. Rust Programming Language

  3. mdBook