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());

}