geodesic

Crates.iogeodesic
lib.rsgeodesic
version0.1.0
created_at2025-05-31 16:58:20.654834+00
updated_at2025-06-14 21:31:51.053168+00
descriptionA Rust library for constructing and sampling 3D geometric scenes.
homepage
repositoryhttps://github.com/FreddyWordingham/geodesic
max_upload_size
id1696486
size4,430,529
Freddy Wordingham (FreddyWordingham)

documentation

README

Geodesic

Crates.io Documentation License

A high-performance ray tracing library for Rust, designed for both educational use and production applications. Geodesic provides a clean, type-safe API for building ray tracers with support for various geometric primitives, acceleration structures, and scene management.

Features

  • 🚀 High Performance: Optimized ray-primitive intersection algorithms with BVH acceleration
  • 🎯 Type Safety: Generic over floating-point types with comprehensive error handling
  • 📐 Rich Geometry: Support for spheres, planes, triangles, and complex meshes
  • 🏗️ Scene Management: Flexible scene construction with asset management and instancing
  • 📦 Serialization: JSON-based scene, camera, and asset serialization
  • 🎥 Camera Models: Perspective and orthographic projections with configurable resolution
  • ⚡ Acceleration Structures: Bounding Volume Hierarchy (BVH) with Surface Area Heuristic
  • 🔧 Configurable: Extensive configuration options for performance tuning

Quick Start

Add this to your Cargo.toml:

[dependencies]
geodesic = "0.1.0"

Basic Ray Tracing

use geodesic::prelude::*;
use nalgebra::{Point3, Unit, Vector3};

// Create a simple scene with a sphere
let scene = Scene::builder()
    .add_sphere(Point3::new(0.0, 0.0, 0.0), 1.0)?
    .build()?;

// Set up a camera
let camera = Camera::new(
    Point3::new(0.0, 0.0, 5.0),  // position
    Point3::new(0.0, 0.0, 0.0),  // look at
    Projection::Perspective(90.0f32.to_radians()),
    [800, 600]  // resolution [height, width]
)?;

// Render a pixel
let ray = camera.generate_ray([400, 300])?;
if let Some(hit) = scene.intersect(&ray)? {
    println!("Hit at distance: {}", hit.distance);
}

Loading Scenes from Files

use geodesic::prelude::*;

// Load assets (meshes, textures, etc.)
let assets = SerializedAssets::<f32>::load("assets.json")?.build()?;

// Load scene configuration
let scene = SerializedScene::<f32>::load("scene.json")?.build(&assets)?;

// Load camera settings
let camera = SerializedCamera::load("camera.json")?.build()?;

// Ready to render!

Mesh Loading

use geodesic::prelude::*;

// Load a Wavefront OBJ file
let bvh_config = BvhConfig::default();
let mesh = Mesh::load(&bvh_config, "model.obj")?;

// Add to scene with transformation
let transform = Matrix4::new_translation(&Vector3::new(1.0, 0.0, 0.0));
let scene = Scene::builder()
    .add_instance(&mesh, transform)?
    .build()?;

Architecture

Core Components

  • Geometry: Primitives like Sphere, Plane, Triangle, and Mesh
  • Ray Tracing: Ray and Hit structures for intersection calculations
  • Acceleration: Bvh (Bounding Volume Hierarchy) for fast ray-scene intersection
  • Scene Management: Scene, Camera, and Assets for organizing render data
  • Serialization: JSON-based configuration for scenes, cameras, and assets

Traits

  • Traceable: Ray intersection testing for any geometry
  • Bounded: Axis-aligned bounding box computation
  • Persistable: JSON serialization/deserialization

Performance

Geodesic is designed for performance with:

  • BVH Acceleration: O(log n) ray-scene intersection complexity
  • SIMD-Friendly: Compatible with nalgebra's SIMD optimizations
  • Memory Efficient: Minimal allocations during rendering
  • Parallel Ready: Thread-safe structures for parallel rendering

Examples

Simple Light Map Renderer

use geodesic::prelude::*;
use rayon::prelude::*;

fn render_lightmap(
    scene: &Scene<f32>,
    camera: &Camera<f32>,
    light_pos: Point3<f32>
) -> Result<Vec<Vec<f32>>, GeodesicError> {
    let [height, width] = *camera.resolution();

    (0..height).into_par_iter().map(|row| {
        (0..width).map(|col| {
            let ray = camera.generate_ray([row, col])?;

            if let Some(hit) = scene.intersect(&ray)? {
                // Calculate lighting
                let hit_pos = ray.origin + ray.direction.scale(hit.distance);
                let light_dir = Unit::new_normalize(light_pos - hit_pos);
                let diffuse = hit.geometric_normal.dot(&light_dir).max(0.0);

                // Check shadows
                let shadow_ray = Ray::new(hit_pos, light_dir);
                let in_shadow = scene.intersect_any(&shadow_ray, 100.0)?;

                Ok(if in_shadow { 0.1 } else { 0.1 + 0.9 * diffuse })
            } else {
                Ok(0.0)
            }
        }).collect::<Result<Vec<f32>, GeodesicError>>()
    }).collect()
}

Scene Configuration

Create JSON configuration files for complex scenes:

assets.json

{
  "bvh_config": {
    "traverse_cost": 1.0,
    "intersect_cost": 1.25,
    "sah_buckets": 16,
    "max_shapes_per_node": 4,
    "max_depth": 64
  },
  "meshes": [
    ["dragon", "models/dragon.obj"],
    ["bunny", "models/bunny.obj"]
  ]
}

scene.json

{
  "objects": [
    {
      "Plane": [
        [0.0, 0.0, 0.0],
        [0.0, 0.0, 1.0]
      ]
    },
    {
      "Sphere": [[0.0, 0.0, 1.0], 1.0]
    },
    {
      "Instance": [
        "dragon",
        {
          "translation": [2.0, 0.0, 0.0],
          "rotation": [0.0, 45.0, 0.0],
          "scale": 2.0
        }
      ]
    }
  ]
}

camera.json

{
  "projection": { "Perspective": 60.0 },
  "position": [5.0, 5.0, 5.0],
  "look_at": [0.0, 0.0, 1.0],
  "resolution": [1080, 1920]
}

Configuration

BVH Tuning

The BVH acceleration structure can be tuned for different scene types:

let bvh_config = BvhConfig::new(
    1.0,    // traverse_cost - cost of traversing internal nodes
    1.25,   // intersect_cost - cost of primitive intersection
    16,     // sah_buckets - buckets for Surface Area Heuristic
    4,      // max_shapes_per_node - leaf node capacity
    64      // max_depth - maximum tree depth
)?;

Generic Precision

Geodesic supports different floating-point precisions:

// Single precision (faster)
type Scene32 = Scene<'static, f32>;

// Double precision (more accurate)
type Scene64 = Scene<'static, f64>;

Error Handling

Geodesic provides comprehensive error handling with detailed error types:

match scene.intersect(&ray) {
    Ok(Some(hit)) => println!("Hit at {}", hit.distance),
    Ok(None) => println!("No intersection"),
    Err(GeodesicError::InvalidGeometry(msg)) => eprintln!("Geometry error: {}", msg),
    Err(GeodesicError::Math(msg)) => eprintln!("Math error: {}", msg),
    Err(e) => eprintln!("Other error: {}", e),
}

Supported File Formats

  • Wavefront OBJ: Triangle mesh loading with vertex normals
  • JSON: Scene, camera, and asset configuration

Minimum Supported Rust Version (MSRV)

Geodesic requires Rust 1.70 or later.

Development

# Clone the repository
git clone https://github.com/FreddyWordingham/geodesic.git
cd geodesic

# Create example scene and camera files
cargo run --example save

# Load and render the example scene
cargo run --example load

# Check documentation
cargo doc --open

License

This project is licensed under the MIT License - see the LICENSE file for details.

Commit count: 21

cargo fmt