Implementing a 3D Sphere Struct in Rust

Mục tiêu: Creating a 3D sphere struct with vector operations and methods for normal calculation and containment checks.


Implementing a 3D Sphere Struct in Rust

To begin with, we need to create a struct that represents a 3D sphere. A sphere in 3D space can be defined by its center point and radius. In Rust, we’ll create a Sphere struct that contains these properties.

The Sphere Struct

/// Represents a sphere in 3D space
#[derive(Debug, Clone, Copy)]
struct Sphere {
    /// Center point of the sphere in 3D space
    center: Vector,
    /// Radius of the sphere
    radius: f64,
}

impl Sphere {
    /// Creates a new sphere with the given center and radius
    fn new(center: Vector, radius: f64) -> Self {
        Sphere {
            center,
            radius,
        }
    }

    /// Returns the surface normal at the given point on the sphere
    fn normal(&self, point: Vector) -> Vector {
        (point - self.center).normalize()
    }

    /// Checks if the given point is inside the sphere
    fn contains(&self, point: Vector) -> bool {
        (point - self.center).magnitude() <= self.radius
    }
}

The Vector Struct

To work with 3D points and vectors, we’ll also need a Vector struct:

/// Represents a 3D vector
#[derive(Debug, Clone, Copy)]
struct Vector {
    x: f64,
    y: f64,
    z: f64,
}

impl Vector {
    /// Creates a new vector from its components
    fn new(x: f64, y: f64, z: f64) -> Self {
        Vector { x, y, z }
    }

    /// Calculates the magnitude of the vector
    fn magnitude(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2) + self.z.powi(2)).sqrt()
    }

    /// Normalizes the vector to have length 1
    fn normalize(&self) -> Vector {
        let mag = self.magnitude();
        Vector {
            x: self.x / mag,
            y: self.y / mag,
            z: self.z / mag,
        }
    }

    /// Dot product of two vectors
    fn dot(&self, other: Vector) -> f64 {
        self.x * other.x + self.y * other.y + self.z * other.z
    }

    /// Cross product of two vectors
    fn cross(&self, other: Vector) -> Vector {
        Vector {
            x: self.y * other.z - self.z * other.y,
            y: self.z * other.x - self.x * other.z,
            z: self.x * other.y - self.y * other.x,
        }
    }

    /// Subtracts one vector from another
    fn subtract(&self, other: Vector) -> Vector {
        Vector {
            x: self.x - other.x,
            y: self.y - other.y,
            z: self.z - other.z,
        }
    }
}

Explanation

  • Sphere Struct:
  • center: This is a Vector representing the coordinates (x, y, z) of the center of the sphere.
  • radius: This is a floating-point number representing the radius of the sphere.
  • new(): This is a constructor method that creates a new sphere with the given center and radius.
  • normal(): This method calculates the surface normal at a given point on the sphere. The normal vector is important for lighting calculations.
  • contains(): This method checks if a given point is inside the sphere by comparing the distance from the point to the center with the radius.
  • Vector Struct:
  • x, y, z: These are the components of the vector.
  • new(): Creates a new vector from its components.
  • magnitude(): Calculates the length of the vector using the Pythagorean theorem.
  • normalize(): Returns a unit vector in the same direction as the original vector.
  • dot(): Computes the dot product, which is useful for determining angles between vectors.
  • cross(): Computes the cross product, which gives a vector perpendicular to both original vectors.
  • subtract(): Subtracts one vector from another, which is useful for various vector operations.

Next Steps

  1. Create Plane Struct: You’ll need to create a similar struct for a plane, which will have its own properties and methods.
  2. Scene Management: Implement functions to add these objects to your scene and manage them.
  3. Vector Math: The vector operations we’ve implemented will be crucial for subsequent tasks like ray generation and intersection calculations.

Further Reading

Implementing a 3D Plane Struct in Rust for Ray Tracing

Mục tiêu: Define a struct to represent a 3D plane using the plane equation Ax + By + Cz + D = 0, including a normal vector and color, with a constructor and helper function for normalization.


To represent a 3D plane in your Ray Tracer project, we’ll create a struct that can define a plane in three-dimensional space. A plane can be mathematically represented by the equation Ax + By + Cz + D = 0, where (A, B, C) is the normal vector to the plane.

Here’s how we’ll implement this:

// Define a struct to represent a 3D plane
#[derive(Debug)]
struct Plane {
    a: f64,
    b: f64,
    c: f64,
    d: f64,
    normal: (f64, f64, f64),
    color: (f64, f64, f64),
}

impl Plane {
    // Constructor to create a new Plane
    fn new(a: f64, b: f64, c: f64, d: f64, color: (f64, f64, f64)) -> Self {
        // Calculate the normal vector
        let normal = (a, b, c);
        let magnitude = vector_length(normal);

        // Normalize the normal vector
        let normal_x = a / magnitude;
        let normal_y = b / magnitude;
        let normal_z = c / magnitude;

        Plane {
            a,
            b,
            c,
            d,
            normal: (normal_x, normal_y, normal_z),
            color,
        }
    }
}

// Helper function to calculate the length of a vector
fn vector_length(vector: (f64, f64, f64)) -> f64 {
    let (x, y, z) = vector;
    (x.powi(2) + y.powi(2) + z.powi(2)).sqrt()
}

Explanation

  1. Plane Struct Definition:
  2. a, b, and c represent the coefficients of the plane equation
  3. d is the constant term in the plane equation
  4. normal is the normalized normal vector to the plane
  5. color is the color of the plane represented as RGB values
  6. Constructor (new method):
  7. Takes the plane coefficients (a, b, c, d) and color as parameters
  8. Calculates the normal vector from (a, b, c)
  9. Normalizes the normal vector to ensure it has a length of 1
  10. Helper Function:
  11. vector_length calculates the magnitude of a vector using the Euclidean norm formula

Example Usage

// Create a plane representing the xy-plane (z=0)
let xy_plane = Plane::new(0.0, 0.0, 1.0, 0.0, (1.0, 1.0, 1.0));

Next Steps

Now that we’ve implemented both the Sphere and Plane structs, you can move on to:

  1. Implementing functions to add these objects to your scene
  2. Writing helper functions for vector math operations
  3. Setting up your initial scene with at least one sphere and one plane

Further Reading

This implementation provides a solid foundation for representing planes in your Ray Tracer. The normalized normal vector will be particularly useful for lighting calculations in subsequent steps.

Implementing Object Management in Rust Ray Tracer

Mục tiêu: Creating a Scene struct to manage 3D objects like spheres and planes in a Rust-based ray tracer.


Implementing Object Management in Rust Ray Tracer

Now that we have our Sphere and Plane structs defined, let’s create a system to manage these objects in our scene. We’ll create a Scene struct that will hold all our objects and provide methods to add them.

Step-by-Step Implementation

1. Define the Scene Struct

We’ll start by creating a Scene struct that contains a vector of objects. Each object can be either a Sphere or a Plane, so we’ll use an enum to represent different object types.

// Define an enum to represent different object types
enum Object {
    Sphere(Sphere),
    Plane(Plane),
}

// Scene struct to hold all objects
struct Scene {
    objects: Vec<Object>,
}

impl Scene {
    // Create a new empty scene
    fn empty() -> Self {
        Scene {
            objects: Vec::new(),
        }
    }

    // Method to add a sphere to the scene
    fn add_sphere(&mut self, sphere: Sphere) {
        self.objects.push(Object::Sphere(sphere));
    }

    // Method to add a plane to the scene
    fn add_plane(&mut self, plane: Plane) {
        self.objects.push(Object::Plane(plane));
    }
}

2. Creating and Managing Objects

Now you can create a scene and add objects to it:

// Example usage in main function
fn main() {
    // Create a new empty scene
    let mut scene = Scene::empty();

    // Create a red sphere
    let sphere_material = Material {
        color: (1.0, 0.0, 0.0),
        reflectivity: 0.5,
    };
    let sphere = Sphere {
        center: (0.0, 0.0, 0.0),
        radius: 1.0,
        material: sphere_material,
    };

    // Create a blue plane
    let plane_material = Material {
        color: (0.0, 0.0, 1.0),
        reflectivity: 0.3,
    };
    let plane = Plane {
        normal: (0.0, 1.0, 0.0),
        distance: 0.0,
        material: plane_material,
    };

    // Add objects to the scene
    scene.add_sphere(sphere);
    scene.add_plane(plane);

    // Add another green sphere
    let another_sphere = Sphere {
        center: (2.0, 0.0, 0.0),
        radius: 1.0,
        material: Material {
            color: (0.0, 1.0, 0.0),
            reflectivity: 0.5,
        },
    };
    scene.add_sphere(another_sphere);
}

Explanation

  • Scene Struct: The Scene struct acts as a container for all our 3D objects. It uses a vector to store Object enum variants, which can be either a Sphere or a Plane.
  • Enum for Objects: Using an enum allows us to easily add more object types in the future (like cylinders, cones, etc.) by simply adding new variants.
  • Adding Objects: The add_sphere and add_plane methods make it easy to add objects to the scene without directly manipulating the vector.
  • Material Properties: Each object has material properties like color and reflectivity, which will be important for lighting calculations later.

Next Steps

Now that we can manage objects in our scene, the next step is to implement ray generation. This involves:

  1. Creating a camera struct
  2. Generating rays from the camera position through each screen pixel
  3. Calculating ray directions based on the viewing frustum

Further Reading

Setting Up Initial 3D Scene with Sphere and Plane

Mục tiêu: Creating a scene with a sphere and plane, adding them to the scene using Rust.


Setting Up the Initial Scene with Sphere and Plane

Now that we’ve defined the Sphere and Plane structs, let’s create an initial scene with these objects. We’ll create instances of these objects and add them to our scene.

Step-by-Step Explanation

  1. Create a Scene Instance: We’ll start by creating a new instance of our Scene struct.
  2. Create a Sphere: We’ll create a sphere with a specific center position and radius. For simplicity, let’s place the sphere at (0.0, 0.0, -5.0) with a radius of 1.0.
  3. Create a Plane: We’ll create a plane with a specific position and normal vector. Let’s place it at (0.0, 0.0, 0.0) with a normal vector pointing upwards (0.0, 1.0, 0.0).
  4. Add Objects to Scene: We’ll use the add_object method to add both our sphere and plane to the scene.

Here’s how the code will look:

// Create a new scene
let mut scene = Scene::new();

// Create a sphere
let sphere = Sphere {
    center: Vector3::new(0.0, 0.0, -5.0),
    radius: 1.0,
};

// Create a plane
let plane = Plane {
    position: Vector3::new(0.0, 0.0, 0.0),
    normal: Vector3::new(0.0, 1.0, 0.0),
};

// Add objects to the scene
scene.add_object(Box::new(sphere));
scene.add_object(Box::new(plane));

Explanation of the Code

  • Scene Creation: We create a new, empty scene using Scene::new().
  • Sphere Creation: We create a sphere centered at (0.0, 0.0, -5.0) with a radius of 1.0. This places the sphere 5 units away from the camera along the z-axis.
  • Plane Creation: We create a plane positioned at the origin (0.0, 0.0, 0.0) with a normal vector pointing upwards. This creates a flat, horizontal plane.
  • Adding Objects: We use the add_object method to add both our sphere and plane to the scene. The Box::new() is used to create a trait object that implements the Object trait.

Next Steps

Now that we’ve set up our initial scene, the next steps would be to implement ray generation and object intersection logic. This will allow us to start rendering our scene.

Further Reading

Implementing Vector Math Operations in Rust for Ray Tracing

Mục tiêu: Implementation of essential vector mathematics operations in Rust for ray tracing applications.


Implementing Vector Math Operations in Rust for Ray Tracing

Vector mathematics is the foundation of any ray tracing implementation. Vectors represent points, directions, and transformations in 3D space. In this task, we’ll implement essential vector operations that will be used throughout our ray tracer project.

Why Vector Math is Important

  • Points in Space: Vectors can represent points in 3D space (x, y, z)
  • Directions: Vectors can represent directions for rays
  • Transformations: Vectors are used in various transformations like translation, rotation, and scaling
  • Lighting Calculations: Vectors are crucial for normal calculations and light direction computations

Vector Operations We’ll Implement

  1. Vector Addition
  2. Vector Subtraction
  3. Vector Scaling
  4. Dot Product
  5. Cross Product
  6. Vector Magnitude
  7. Vector Normalization

Implementing the Vector3 Struct and Operations

// Define a 3D vector structure
#[derive(Debug, Copy, Clone)]
pub struct Vector3 {
    pub x: f64,
    pub y: f64,
    pub z: f64,
}

impl Vector3 {
    // Constructor to create a new Vector3 from three components
    pub fn from_triple(x: f64, y: f64, z: f64) -> Self {
        Vector3 { x, y, z }
    }

    // Create a zero vector
    pub fn from_zero() -> Self {
        Vector3 { x: 0.0, y: 0.0, z: 0.0 }
    }

    // Calculate the magnitude (length) of the vector
    pub fn magnitude(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2) + self.z.powi(2)).sqrt()
    }

    // Normalize the vector to unit length
    pub fn normalize(&self) -> Vector3 {
        let mag = self.magnitude();
        Vector3 {
            x: self.x / mag,
            y: self.y / mag,
            z: self.z / mag,
        }
    }

    // Dot product of two vectors
    pub fn dot(&self, other: &Vector3) -> f64 {
        self.x * other.x + self.y * other.y + self.z * other.z
    }

    // Cross product of two vectors
    pub fn cross(&self, other: &Vector3) -> Vector3 {
        Vector3 {
            x: self.y * other.z - self.z * other.y,
            y: self.z * other.x - self.x * other.z,
            z: self.x * other.y - self.y * other.x,
        }
    }
}

// Implement vector addition using operator overloading
impl std::ops::Add for Vector3 {
    type Output = Vector3;

    fn add(self, other: Vector3) -> Vector3 {
        Vector3 {
            x: self.x + other.x,
            y: self.y + other.y,
            z: self.z + other.z,
        }
    }
}

// Implement vector subtraction using operator overloading
impl std::ops::Sub for Vector3 {
    type Output = Vector3;

    fn sub(self, other: Vector3) -> Vector3 {
        Vector3 {
            x: self.x - other.x,
            y: self.y - other.y,
            z: self.z - other.z,
        }
    }
}

// Implement scalar multiplication using operator overloading
impl std::ops::Mul<f64> for Vector3 {
    type Output = Vector3;

    fn mul(self, scalar: f64) -> Vector3 {
        Vector3 {
            x: self.x * scalar,
            y: self.y * scalar,
            z: self.z * scalar,
        }
    }
}

// Implement scalar division using operator overloading
impl std::ops::Div<f64> for Vector3 {
    type Output = Vector3;

    fn div(self, scalar: f64) -> Vector3 {
        Vector3 {
            x: self.x / scalar,
            y: self.y / scalar,
            z: self.z / scalar,
        }
    }
}

// Calculate distance between two points
pub fn distance(p1: &Vector3, p2: &Vector3) -> f64 {
    let vector = *p1 - *p2;
    vector.magnitude()
}

Explanation of the Code

  1. Vector3 Struct:
  2. Represents a 3D vector with x, y, z components
  3. Implements common operations through methods and operator overloading
  4. Constructors:
  5. from_triple: Creates a vector from three components
  6. from_zero: Creates a zero vector
  7. Vector Operations:
  8. magnitude(): Returns the length of the vector
  9. normalize(): Returns a unit vector in the same direction
  10. dot(): Computes the dot product with another vector
  11. cross(): Computes the cross product with another vector
  12. add, sub, mul, div: Operator overloads for vector addition, subtraction, scalar multiplication, and division
  13. Helper Function:
  14. distance(): Calculates the distance between two points in space

Example Usage

fn main() {
    // Create two vectors
    let v1 = Vector3::from_triple(1.0, 2.0, 3.0);
    let v2 = Vector3::from_triple(4.0, 5.0, 6.0);

    // Vector addition
    let v3 = v1 + v2;
    println!("v1 + v2 = {:?}", v3);

    // Dot product
    let dot_product = v1.dot(&v2);
    println!("v1 · v2 = {}", dot_product);

    // Cross product
    let cross_product = v1.cross(&v2);
    println!("v1 × v2 = {:?}", cross_product);

    // Vector magnitude
    let magnitude = v1.magnitude();
    println!("|v1| = {}", magnitude);

    // Normalize vector
    let normalized = v1.normalize();
    println!("Normalized v1 = {:?}", normalized);

    // Distance between two points
    let distance = distance(&v1, &v2);
    println!("Distance between v1 and v2 = {}", distance);
}

Best Practices and Considerations

  1. Use of Const Functions: Where possible, use const fn for better performance
  2. Error Handling: Consider adding error handling for division by zero
  3. Documentation: Add Rust doc comments for better code documentation
  4. Testing: Write unit tests to verify correctness of vector operations

What’s Next?

Now that we have the vector math operations implemented, we can proceed to create the sphere and plane structures in the next task. These structures will use our vector operations for various calculations like ray intersection and normal vector computations.

Further Reading