Complete triangle mesh code (and more refactoring)
This commit is contained in:
parent
cddec468de
commit
4596c117df
|
@ -24,7 +24,7 @@ This list may be changed or extended in the future.
|
||||||
- [x] Triangle normal generation
|
- [x] Triangle normal generation
|
||||||
- [x] Color mapping on triangles
|
- [x] Color mapping on triangles
|
||||||
- [x] Triangle mesh struct
|
- [x] Triangle mesh struct
|
||||||
- [ ] Triangle mesh intersection test
|
- [x] Triangle mesh intersection test
|
||||||
- [ ] Bounding boxes
|
- [ ] Bounding boxes
|
||||||
- [ ] Direct lighting
|
- [ ] Direct lighting
|
||||||
- [ ] Point light sources
|
- [ ] Point light sources
|
||||||
|
|
|
@ -52,13 +52,10 @@ fn render(camera: &Camera, scene: &Scene, filename: &str) -> std::io::Result<()>
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
let camera = Camera::new(Point3::new(0.0,1.0,0.0), Vector3::new(0.0,0.0,1.0), 1.0, 16.0 / 9.0, 2.0, 720);
|
let camera = Camera::new(Point3::new(0.0,0.0,0.0), Vector3::new(0.0,0.0,1.0), 1.0, 16.0 / 9.0, 2.0, 480);
|
||||||
|
|
||||||
let scene = vec![
|
let scene = vec![
|
||||||
Object::new(Plane::new(Point3::new(0.0, 0.0, -1.0), Vector3::x(), Vector3::z(),
|
Object::new(TriangleMesh::singleton(Point3::new(-1.0, -1.0, 3.0), Point3::new(0.0, 1.0, 3.0), Point3::new(1.0, -1.0, 3.0), |t, u, v| Color::new(t, u, v)))
|
||||||
|x, y| Color::gray(y / 30.0)
|
|
||||||
)),
|
|
||||||
Object::new(Sphere::new(0.0, 1.0, 4.0, 1.0, |x, y| Color::new(0.0, x, y)))
|
|
||||||
];
|
];
|
||||||
|
|
||||||
render(&camera, &scene, "out.ppm")
|
render(&camera, &scene, "out.ppm")
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub struct Plane {
|
||||||
// Input coordinates are defined in terms of the axes above.
|
// Input coordinates are defined in terms of the axes above.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
impl Plane {
|
impl Plane {
|
||||||
// Creates a new plane.
|
// Creates a new plane.
|
||||||
pub fn new<F: 'static>(center: Point3<f32>, x_axis: Vector3<f32>, y_axis: Vector3<f32>, texture: F) -> Self
|
pub fn new<F: 'static>(center: Point3<f32>, x_axis: Vector3<f32>, y_axis: Vector3<f32>, texture: F) -> Self
|
||||||
|
|
|
@ -16,6 +16,7 @@ pub struct Sphere {
|
||||||
// Uses spherical coordinates (normalized from 0-1) as input.
|
// Uses spherical coordinates (normalized from 0-1) as input.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
impl Sphere {
|
impl Sphere {
|
||||||
// Creates a new sphere.
|
// Creates a new sphere.
|
||||||
pub fn new<F: 'static>(x: f32, y: f32, z: f32, radius: f32, texture: F) -> Self
|
pub fn new<F: 'static>(x: f32, y: f32, z: f32, radius: f32, texture: F) -> Self
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
extern crate nalgebra as na;
|
extern crate nalgebra as na;
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use na::*;
|
use na::*;
|
||||||
use na::geometry::Point3;
|
use na::geometry::Point3;
|
||||||
|
|
||||||
|
@ -11,6 +13,7 @@ pub struct Triangle {
|
||||||
pub v2: usize,
|
pub v2: usize,
|
||||||
pub v3: usize,
|
pub v3: usize,
|
||||||
|
|
||||||
|
normal: Unit<Vector3<f32>>, // Precalculated normal vector.
|
||||||
area: f32, // Precalculated area for barycentric calculations.
|
area: f32, // Precalculated area for barycentric calculations.
|
||||||
|
|
||||||
texture: Box<dyn Fn(f32, f32, f32) -> Color> // Texture map.
|
texture: Box<dyn Fn(f32, f32, f32) -> Color> // Texture map.
|
||||||
|
@ -18,7 +21,7 @@ pub struct Triangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TriangleMesh {
|
pub struct TriangleMesh {
|
||||||
pub points: Vec<Point3<f32>>,
|
pub vertices: Vec<Point3<f32>>,
|
||||||
pub tris: Vec<Triangle>
|
pub tris: Vec<Triangle>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +50,7 @@ impl Triangle {
|
||||||
(t, u, v)
|
(t, u, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn intersect(&self, vertices: &Vec<Point3<f32>>, ray: Ray) -> Option<f32> {
|
fn intersect_(&self, vertices: &Vec<Point3<f32>>, ray: Ray) -> Option<(f32, f32, f32)> {
|
||||||
let vect2_1 = self.vertex2(vertices) - self.vertex1(vertices);
|
let vect2_1 = self.vertex2(vertices) - self.vertex1(vertices);
|
||||||
let vect3_1 = self.vertex3(vertices) - self.vertex1(vertices);
|
let vect3_1 = self.vertex3(vertices) - self.vertex1(vertices);
|
||||||
|
|
||||||
|
@ -66,53 +69,51 @@ impl Triangle {
|
||||||
|
|
||||||
if v < 0.0 || (u + v) > 1.0 { return None; }
|
if v < 0.0 || (u + v) > 1.0 { return None; }
|
||||||
|
|
||||||
let t = vect3_1.dot(&q_vect) / det;
|
let t = 1.0 - u - v;
|
||||||
|
|
||||||
// Convert from barycentric coordinates
|
Some((t, u, v))
|
||||||
Some(distance(&ray.origin, &self.from_bary(vertices, t, u, v)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normal(&self, vertices: &Vec<Point3<f32>>, _point: Point3<f32>) -> Unit<Vector3<f32>> {
|
fn intersect(&self, vertices: &Vec<Point3<f32>>, ray: Ray) -> Option<f32> {
|
||||||
Unit::new_normalize((self.vertex2(vertices) - self.vertex1(vertices)).cross(&(self.vertex3(vertices) - self.vertex1(vertices))))
|
self.intersect_(vertices, ray).map(|(t, u, v)| distance(&ray.origin, &self.from_bary(vertices, t, u, v)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getcolor(&self, vertices: &Vec<Point3<f32>>, point: Point3<f32>) -> Color {
|
fn getcolor(&self, vertices: &Vec<Point3<f32>>, point: Point3<f32>) -> Color {
|
||||||
// Converting back and forth between barycentric coordinates
|
|
||||||
// like this is terrible, but it's necessary for this object to
|
|
||||||
// match the interface the other objects use.
|
|
||||||
let (t, u, v) = self.to_bary(vertices, point);
|
let (t, u, v) = self.to_bary(vertices, point);
|
||||||
|
|
||||||
(*self.texture)(t, u, v)
|
(*self.texture)(t, u, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
impl TriangleMesh {
|
impl TriangleMesh {
|
||||||
pub fn new(points: Vec<Point3<f32>>, tris: Vec<(usize, usize, usize, Box<dyn Fn(f32, f32, f32) -> Color>)>) -> Self {
|
pub fn new(vertices: Vec<Point3<f32>>, tris: Vec<(usize, usize, usize, Box<dyn Fn(f32, f32, f32) -> Color>)>) -> Self {
|
||||||
let triangles = tris.into_iter()
|
let triangles = tris.into_iter()
|
||||||
.map(|(v1, v2, v3, f)| Triangle {
|
.map(|(v1, v2, v3, f)| Triangle {
|
||||||
v1: v1,
|
v1: v1,
|
||||||
v2: v2,
|
v2: v2,
|
||||||
v3: v3,
|
v3: v3,
|
||||||
area: tri_area(&points[v1], &points[v2], &points[v3]),
|
normal: Unit::new_normalize((&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1]))),
|
||||||
|
area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]),
|
||||||
texture: f
|
texture: f
|
||||||
}).collect();
|
}).collect();
|
||||||
TriangleMesh {
|
TriangleMesh {
|
||||||
points: points,
|
vertices: vertices,
|
||||||
tris: triangles
|
tris: triangles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_solid(points: Vec<Point3<f32>>, tris: Vec<(usize, usize, usize)>, color: Color) -> Self {
|
pub fn new_solid(vertices: Vec<Point3<f32>>, tris: Vec<(usize, usize, usize)>, color: Color) -> Self {
|
||||||
let triangles = tris.into_iter()
|
let triangles = tris.into_iter()
|
||||||
.map(|(v1, v2, v3)| Triangle {
|
.map(|(v1, v2, v3)| Triangle {
|
||||||
v1: v1,
|
v1: v1,
|
||||||
v2: v2,
|
v2: v2,
|
||||||
v3: v3,
|
v3: v3,
|
||||||
area: tri_area(&points[v1], &points[v2], &points[v3]),
|
normal: Unit::new_normalize((&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1]))),
|
||||||
|
area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]),
|
||||||
texture: Box::new(move |_, _, _| color)
|
texture: Box::new(move |_, _, _| color)
|
||||||
}).collect();
|
}).collect();
|
||||||
TriangleMesh {
|
TriangleMesh {
|
||||||
points: points,
|
vertices: vertices,
|
||||||
tris: triangles
|
tris: triangles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,3 +121,82 @@ impl TriangleMesh {
|
||||||
pub fn singleton<F: 'static>(vertex1: Point3<f32>, vertex2: Point3<f32>, vertex3: Point3<f32>, texture: F) -> Self
|
pub fn singleton<F: 'static>(vertex1: Point3<f32>, vertex2: Point3<f32>, vertex3: Point3<f32>, texture: F) -> Self
|
||||||
where F: Fn(f32, f32, f32) -> Color
|
where F: Fn(f32, f32, f32) -> Color
|
||||||
{ TriangleMesh::new(vec![vertex1, vertex2, vertex3], vec![(0, 1, 2, Box::new(texture))]) }
|
{ TriangleMesh::new(vec![vertex1, vertex2, vertex3], vec![(0, 1, 2, Box::new(texture))]) }
|
||||||
|
|
||||||
|
pub fn singleton_solid(vertex1: Point3<f32>, vertex2: Point3<f32>, vertex3: Point3<f32>, color: Color) -> Self
|
||||||
|
{ TriangleMesh::singleton(vertex1, vertex2, vertex3, move |_, _, _| color) }
|
||||||
|
|
||||||
|
|
||||||
|
fn closest_tri(&self, point: Point3<f32>) -> &Triangle {
|
||||||
|
self.tris.iter()
|
||||||
|
.map(move |tri| {
|
||||||
|
|
||||||
|
let rel_pos = point - tri.vertex1(&self.vertices);
|
||||||
|
let proj_point3 = rel_pos - (*tri.normal * tri.normal.dot(&rel_pos));
|
||||||
|
|
||||||
|
let (t, u, v) = tri.to_bary(&self.vertices, Point3::from(proj_point3));
|
||||||
|
|
||||||
|
let t = clamp(t, 0.0, 1.0);
|
||||||
|
let u = clamp(u, 0.0, 1.0);
|
||||||
|
let v = clamp(v, 0.0, 1.0);
|
||||||
|
|
||||||
|
let point_new = tri.from_bary(&self.vertices, t, u, v);
|
||||||
|
|
||||||
|
(tri, distance(&point, &point_new))
|
||||||
|
})
|
||||||
|
.min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal))
|
||||||
|
.unwrap().0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Surface for TriangleMesh {
|
||||||
|
fn intersect(&self, ray: Ray) -> Option<f32> {
|
||||||
|
self.tris.iter()
|
||||||
|
.filter_map(|tri| tri.intersect(&self.vertices, ray))
|
||||||
|
.min_by(|a, b| a.partial_cmp(&b).unwrap_or(Ordering::Equal))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normal(&self, point: Point3<f32>) -> Unit<Vector3<f32>> {
|
||||||
|
self.closest_tri(point).normal
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getcolor(&self, point: Point3<f32>) -> Color {
|
||||||
|
self.closest_tri(point).getcolor(&self.vertices, point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn roundcolor(color: Color) -> Color {
|
||||||
|
Color::new((color.red * 100.0).round() / 100.0, (color.green * 100.0).round() / 100.0, (color.blue * 100.0).round() / 100.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn triangle_intersect() {
|
||||||
|
let triangle = TriangleMesh::singleton_solid(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, 0.0, 1.0), Color::black());
|
||||||
|
|
||||||
|
let ray = Ray::new(Point3::new(0.5, 5.0, 0.3), Vector3::new(0.0, -1.0, 0.0));
|
||||||
|
|
||||||
|
let (t, u, v) = triangle.tris[0].intersect_(&triangle.vertices, ray).unwrap();
|
||||||
|
|
||||||
|
println!("{},{},{}", t, u, v);
|
||||||
|
|
||||||
|
assert!(t >= 0.0 && t <= 1.0);
|
||||||
|
assert!(u >= 0.0 && u <= 1.0);
|
||||||
|
assert!(v >= 0.0 && v <= 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn triangle_getcolor() {
|
||||||
|
let triangle = TriangleMesh::singleton(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, 0.0, 1.0), |t, u, v| Color::new(t, u, v));
|
||||||
|
|
||||||
|
let t = 0.4;
|
||||||
|
let u = 0.1;
|
||||||
|
let v = 1.0 - t - u;
|
||||||
|
|
||||||
|
let point = triangle.tris[0].from_bary(&triangle.vertices, t, u, v);
|
||||||
|
|
||||||
|
assert_eq!(roundcolor(triangle.getcolor(point)), roundcolor(Color::new(t, u, v)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue