use std::sync::Arc;
use super::{
eye::EyePlace,
math::{Mat4, Vec3},
mesh::{Mesh, MeshHit},
};
use crate::{Color, Point, Rect, Size, transform::ProjectiveTransform};
pub const NEAR_PLANE: f32 = crate::transform::NEAR_W;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Camera {
pub view: Mat4,
pub focal_length: f32,
}
impl Camera {
pub fn look_at(eye: Vec3, target: Vec3, down: Vec3, focal_length: f32) -> Self {
let forward = (target - eye)
.normalized()
.unwrap_or(Vec3::new(0.0, 0.0, 1.0));
let right = down
.cross(forward)
.normalized()
.unwrap_or(Vec3::new(1.0, 0.0, 0.0));
let down = forward.cross(right);
let row = |axis: Vec3| [axis.x, axis.y, axis.z, -axis.dot(eye)];
Self {
view: Mat4::from_rows([row(right), row(down), row(forward), [0.0, 0.0, 0.0, 1.0]]),
focal_length,
}
}
pub fn focal_length_for(height: f32, vertical_fov: f32) -> f32 {
height * 0.5 / (vertical_fov * 0.5).tan()
}
pub fn eye(&self) -> Vec3 {
self.view
.inverse()
.map_or(Vec3::ZERO, |inverse| inverse.transform_point(Vec3::ZERO))
}
pub fn near(&self) -> f32 {
self.focal_length * NEAR_PLANE
}
pub fn project(&self, point: Vec3, size: Size) -> Option<Point> {
let seen = self.view.transform_point(point);
(seen.z >= self.near()).then(|| {
let scale = self.focal_length / seen.z;
Point::new(
size.width * 0.5 + seen.x * scale,
size.height * 0.5 + seen.y * scale,
)
})
}
pub fn doorway(&self, window: Rect) -> Mat4 {
self.view.then(&Mat4::translation(Vec3::new(
window.x + window.width * 0.5,
window.y + window.height * 0.5,
0.0,
)))
}
pub fn ray(&self, point: Point, size: Size) -> Option<Ray> {
let inverse = self.view.inverse()?;
let through = Vec3::new(
(point.x - size.width * 0.5) / self.focal_length,
(point.y - size.height * 0.5) / self.focal_length,
1.0,
);
Some(Ray {
origin: inverse.transform_point(Vec3::ZERO),
direction: inverse.transform_vector(through),
})
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Ray {
pub origin: Vec3,
pub direction: Vec3,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Light {
pub direction: Vec3,
pub ambient: f32,
pub shadows: bool,
pub size: f32,
}
impl Light {
pub fn brightness(&self, normal: Vec3) -> f32 {
let facing = match (self.direction.normalized(), normal.normalized()) {
(Some(direction), Some(normal)) => (-normal.dot(direction)).max(0.0),
_ => 0.0,
};
self.ambient + (1.0 - self.ambient) * facing
}
pub fn shadow_projection<'a>(
&self,
casters: impl IntoIterator<Item = &'a MeshSurface>,
) -> Option<Mat4> {
const MARGIN: f32 = 0.02;
let forward = self.direction.normalized()?;
let hint = if forward.y.abs() < 0.9 {
Vec3::new(0.0, 1.0, 0.0)
} else {
Vec3::new(1.0, 0.0, 0.0)
};
let right = hint.cross(forward).normalized()?;
let up = forward.cross(right);
let view = Mat4::from_rows([
[right.x, right.y, right.z, 0.0],
[up.x, up.y, up.z, 0.0],
[forward.x, forward.y, forward.z, 0.0],
[0.0, 0.0, 0.0, 1.0],
]);
let mut low = Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY);
let mut high = -low;
for caster in casters {
let (caster_low, caster_high) = caster.mesh.bounds_in(&caster.transform.then(&view));
(low, high) = (low.min(caster_low), high.max(caster_high));
}
let extent = high - low;
if !(extent.x.is_finite() && extent.y.is_finite() && extent.z.is_finite()) {
return None;
}
let pad = |extent: f32| (extent * MARGIN).max(1.0);
let (low, high) = (
low - Vec3::new(pad(extent.x), pad(extent.y), pad(extent.z)),
high + Vec3::new(pad(extent.x), pad(extent.y), pad(extent.z)),
);
let extent = high - low;
let fit = Mat4::from_rows([
[2.0 / extent.x, 0.0, 0.0, -1.0 - 2.0 * low.x / extent.x],
[0.0, 2.0 / extent.y, 0.0, -1.0 - 2.0 * low.y / extent.y],
[0.0, 0.0, 1.0 / extent.z, -low.z / extent.z],
[0.0, 0.0, 0.0, 1.0],
]);
Some(view.then(&fit))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Scene3d {
pub camera: Camera,
pub light: Option<Light>,
pub eye: Option<EyePlace>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct MeshSurface {
pub mesh: Arc<Mesh>,
pub transform: Mat4,
pub material: Material,
pub casts_shadow: bool,
pub solid: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Material {
Lit,
Unlit,
Emissive,
Glow,
Glass(Glass),
Volume(Volume),
Shaded(Shaded),
}
#[derive(Clone, Debug)]
pub struct Shaded {
pub shader: Arc<str>,
pub params: [f32; Shaded::PARAMS],
}
impl Shaded {
pub const PARAMS: usize = 32;
}
impl PartialEq for Shaded {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.shader, &other.shader) && self.params == other.params
}
}
#[derive(Clone, Debug)]
pub struct Volume {
pub shader: Arc<str>,
pub params: [f32; Volume::PARAMS],
}
impl Volume {
pub const PARAMS: usize = 32;
}
impl PartialEq for Volume {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.shader, &other.shader) && self.params == other.params
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Glass {
pub index: f32,
pub thickness: f32,
pub tint: Color,
}
impl Glass {
pub const fn clear(thickness: f32) -> Self {
Self {
index: 1.5,
thickness,
tint: Color(1.0, 1.0, 1.0, 1.0),
}
}
}
impl MeshSurface {
pub fn cast(&self, ray: Ray, size: Size) -> Option<SurfaceHit> {
let inverse = self.transform.inverse()?;
let hit = self.mesh.cast(
inverse.transform_point(ray.origin),
inverse.transform_vector(ray.direction),
)?;
Some(SurfaceHit::of(hit, size))
}
pub fn cast_plane(&self, ray: Ray, triangle: u32, size: Size) -> Option<Point> {
let inverse = self.transform.inverse()?;
let uv = self.mesh.cast_plane(
inverse.transform_point(ray.origin),
inverse.transform_vector(ray.direction),
triangle,
)?;
Some(content_point(uv, size))
}
pub fn content_transform(
&self,
camera: &Camera,
viewport: Size,
size: Size,
) -> Option<ProjectiveTransform> {
if !(size.width > 0.0 && size.height > 0.0) {
return None;
}
let plane = self.mesh.texture_plane()?;
let to_camera = self.transform.then(&camera.view);
let origin = to_camera.transform_point(plane.origin);
let right = to_camera.transform_vector(plane.u_axis) * (1.0 / size.width);
let down = to_camera.transform_vector(plane.v_axis) * (1.0 / size.height);
let (center_x, center_y) = (viewport.width * 0.5, viewport.height * 0.5);
let focal_length = camera.focal_length;
let column = |v: Vec3| {
[
v.x + center_x * v.z / focal_length,
v.y + center_y * v.z / focal_length,
v.z / focal_length,
]
};
let [a, b, c] = [column(right), column(down), column(origin)];
Some(ProjectiveTransform::from_rows([
[a[0], b[0], c[0]],
[a[1], b[1], c[1]],
[a[2], b[2], c[2]],
]))
}
pub fn content_to_scene(&self, size: Size) -> Option<Mat4> {
if !(size.width > 0.0 && size.height > 0.0) {
return None;
}
let plane = self.mesh.texture_plane()?;
let right = self.transform.transform_vector(plane.u_axis) * (1.0 / size.width);
let down = self.transform.transform_vector(plane.v_axis) * (1.0 / size.height);
let into = right.cross(down).normalized()? * (right.length() * down.length()).sqrt();
Some(Mat4::from_axes(
right,
down,
into,
self.transform.transform_point(plane.origin),
))
}
pub fn placed_by(&self, to_scene: &Mat4) -> Self {
let material = match &self.material {
Material::Glass(glass) => Material::Glass(Glass {
thickness: glass.thickness * to_scene.length_scale(),
..*glass
}),
material => material.clone(),
};
Self {
mesh: Arc::clone(&self.mesh),
transform: self.transform.then(to_scene),
material,
casts_shadow: self.casts_shadow,
solid: self.solid,
}
}
pub fn point_at(&self, point: Point, size: Size) -> Option<Vec3> {
let uv = [
point.x / size.width.max(f32::MIN_POSITIVE),
point.y / size.height.max(f32::MIN_POSITIVE),
];
self.mesh
.point_at(uv)
.map(|position| self.transform.transform_point(position))
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SurfaceHit {
pub distance: f32,
pub triangle: u32,
pub point: Point,
}
impl SurfaceHit {
fn of(hit: MeshHit, size: Size) -> Self {
Self {
distance: hit.distance,
triangle: hit.triangle,
point: content_point(hit.uv, size),
}
}
}
fn content_point([u, v]: [f32; 2], size: Size) -> Point {
Point::new(u * size.width, v * size.height)
}
#[derive(Clone, Debug, PartialEq)]
pub enum LayerSpace {
Scene(Scene3d),
Surface(MeshSurface),
Transform(Mat4),
}