rust-render/src/camera.rs

174 lines
4.7 KiB
Rust

use nalgebra::geometry::{Point2, Point3};
use nalgebra::*;
use crate::util::Ray;
#[derive(Debug)]
pub struct Camera {
matrix: Isometry3<f64>, // The transformation that stores the
// position and orientation of the camera. (Not actually a matrix, but w/e)
focal_length: f64, // The distance from the camera origin to the canvas.
canvas_size: Vector2<f64>, // The size of the canvas within the world space.
pub image_size: Vector2<u64>, // The size of the final image in pixels.
}
impl Camera {
// Constructs a new camera from a position and viewing direction.
pub fn new_(
pos: Point3<f64>,
dir: Vector3<f64>,
up: Vector3<f64>,
focal_length: f64,
aspect_ratio: f64,
canvas_y: f64,
image_y: u64,
) -> Self {
let iso = Isometry3::face_towards(&pos, &(pos + dir), &up);
Camera {
matrix: iso,
focal_length: focal_length,
canvas_size: Vector2::new(canvas_y * aspect_ratio, canvas_y),
image_size: Vector2::new((image_y as f64 * aspect_ratio) as u64, image_y),
}
}
// Constructs a new camera from a position and viewing direction
// (assuming the camera is oriented upright).
pub fn new(
pos: Point3<f64>,
dir: Vector3<f64>,
focal_length: f64,
aspect_ratio: f64,
canvas_y: f64,
image_y: u64,
) -> Self {
Camera::new_(
pos,
dir,
Vector3::y(),
focal_length,
aspect_ratio,
canvas_y,
image_y,
)
}
pub fn pos(&self) -> Point3<f64> {
Point3::from(self.matrix.translation.vector)
}
// Takes a 2D point in the image space and
// maps it to the 3D point on the canvas.
fn project(&self, x: u64, y: u64) -> Point3<f64> {
// convert point from raster coordinates to center-based coordinates
let pixelndc = Point2::new(
x as f64 + 0.5 - self.image_size.x as f64 * 0.5,
-(y as f64 + 0.5) + self.image_size.y as f64 * 0.5,
);
let point: Point3<f64> = Point::from(
pixelndc
.coords
.component_div(&self.image_size.map(|x| x as f64))
.component_mul(&self.canvas_size)
.fixed_resize(self.focal_length),
);
self.matrix * point
}
// Takes a 2D point in the image space and
// returns a ray in the world space, for use in raytracing.
pub fn raycast(&self, x: u64, y: u64) -> Ray {
Ray::from_points(self.pos(), self.project(x, y))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn round(point: Point3<f64>) -> Point3<f64> {
Point::from(point.coords.map(|x| x.round()))
}
#[test]
fn camera_pos() {
let camera: Camera = Camera::new(
Point3::new(-5.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
1.0,
1.0,
2.0,
800,
);
assert_eq!(camera.pos(), Point3::new(-5.0, 0.0, 0.0));
}
#[test]
fn camera_matrix1() {
let camera: Camera = Camera::new(
Point3::new(-5.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
1.0,
1.0,
2.0,
800,
);
let point = Point3::new(0.0, 0.0, 4.0);
let point = camera.matrix * point;
let point = round(point); // round to avoid errors
assert_eq!(point, Point3::new(-1.0, 0.0, 0.0));
}
#[test]
fn camera_matrix2() {
let camera: Camera = Camera::new(
Point3::new(-5.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
1.0,
1.0,
2.0,
800,
);
let point = Point3::new(4.0, 0.0, 0.0);
let point = camera.matrix * point;
let point = round(point); // round to avoid errors
assert_eq!(point, Point3::new(-5.0, 0.0, -4.0));
}
#[test]
fn camera_project1() {
let camera: Camera = Camera::new(
Point3::new(-5.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
1.0,
1.0,
2.0,
800,
);
let point = camera.project(400, 400);
let point = round(point); // round to avoid errors
assert_eq!(point, Point3::new(-4.0, 0.0, 0.0));
}
#[test]
fn camera_project2() {
let camera: Camera = Camera::new(
Point3::new(-5.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
1.0,
1.0,
2.0,
800,
);
let point = camera.project(0, 0);
let point = round(point); // round to avoid errors
assert_eq!(point, Point3::new(-4.0, 1.0, 1.0));
}
}