>();
diff --git a/raydeon/src/camera.rs b/raydeon/src/camera.rs
index 45d9ab3..9b793f8 100644
--- a/raydeon/src/camera.rs
+++ b/raydeon/src/camera.rs
@@ -1,4 +1,4 @@
-use euclid::Transform3D;
+use euclid::{Point3D, Transform3D};
use path::SlicedSegment3D;
use crate::*;
@@ -55,8 +55,8 @@ impl Observation {
#[derive(Debug, Copy, Clone)]
pub struct Perspective {
pub fovy: f64,
- pub width: f64,
- pub height: f64,
+ pub width: usize,
+ pub height: usize,
pub aspect: f64,
pub znear: f64,
pub zfar: f64,
@@ -64,7 +64,7 @@ pub struct Perspective {
impl Default for Perspective {
fn default() -> Self {
- Self::new(45.0, 1920.0, 1080.0, 0.1, 100.0)
+ Self::new(45.0, 1920, 1080, 0.1, 100.0)
}
}
@@ -73,8 +73,8 @@ impl Default for Perspective {
pub struct NoPerspective;
impl Perspective {
- pub fn new(fovy: f64, width: f64, height: f64, znear: f64, zfar: f64) -> Self {
- let aspect = width / height;
+ pub fn new(fovy: f64, width: usize, height: usize, znear: f64, zfar: f64) -> Self {
+ let aspect = width as f64 / height as f64;
Self {
fovy,
width,
@@ -128,8 +128,8 @@ impl Camera
{
pub fn perspective(
self,
fovy: f64,
- width: f64,
- height: f64,
+ width: usize,
+ height: usize,
znear: f64,
zfar: f64,
) -> Camera {
@@ -158,9 +158,9 @@ impl Camera {
self.canvas_transformation()
.then_translate(Vec3::new(1.0, 1.0, 0.0))
.then_scale(
- self.perspective.width / 2.0,
- self.perspective.height / 2.0,
- 0.0,
+ self.perspective.width as f64 / 2.0,
+ self.perspective.height as f64 / 2.0,
+ 1.0,
)
.with_destination()
}
@@ -183,13 +183,9 @@ impl Camera {
.map(|p2t| (p1t.xy(), p2t.xy()))
});
- // The pixel fidelity of the drawing instrument.
- // TODO: Make this configurable
- let pen_px_size = 4;
-
let chunk_count = canvas_points
.map(|(p1t, p2t)| {
- let rough_chop_size = (p2t - p1t).length() / (pen_px_size as f64 / 2.0);
+ let rough_chop_size = (p2t - p1t).length() / (PEN_PX_SIZE / 2.0);
rough_chop_size.round_ties_even() as usize
})
.unwrap_or_else(|| {
@@ -203,6 +199,22 @@ impl Camera {
Some(SlicedSegment3D::new(chunk_count, segment))
}
}
+
+ pub fn ray_for_px_coords(&self, x: f64, y: f64) -> Ray {
+ let pix_ndc = Point3D::new(x, y, 0.0);
+
+ let world_coord = self
+ .camera_transformation()
+ .inverse()
+ .unwrap()
+ .transform_point3d(pix_ndc)
+ .unwrap();
+
+ Ray {
+ point: self.observation.eye,
+ dir: (world_coord.to_vector() - self.observation.eye.to_vector()).normalize(),
+ }
+ }
}
impl Camera {
@@ -213,7 +225,7 @@ impl Camera {
let xmax = ymax * p.aspect;
// TODO: We can apply scaling here based on pen size
- let effective_dims: Vec2<()> = Vec2::new(p.width, p.height);
+ let effective_dims: Vec2<()> = Vec2::new(p.width as f64, p.height as f64);
let znear_dims = Vec2::new(xmax, ymax) * 2.0;
let est_min_pix = znear_dims.component_div(effective_dims);
diff --git a/raydeon/src/lib.rs b/raydeon/src/lib.rs
index 9227a9d..c14076d 100644
--- a/raydeon/src/lib.rs
+++ b/raydeon/src/lib.rs
@@ -1,6 +1,9 @@
+#[allow(clippy::needless_doctest_main)]
#[doc = include_str!("../../README.md")]
pub(crate) mod bvh;
pub mod camera;
+pub mod lights;
+pub mod material;
pub mod path;
pub mod ray;
pub mod scene;
@@ -8,11 +11,15 @@ pub mod shapes;
use std::sync::Arc;
+pub use camera::{Camera, NoObservation, NoPerspective, Observation, Perspective};
+pub use lights::Light;
pub use path::{LineSegment3D, PathMeta};
pub use ray::{HitData, Ray};
+pub use scene::{Scene, SceneGeometry, SceneLighting};
-pub use camera::{Camera, NoObservation, NoPerspective, Observation, Perspective};
-pub use scene::Scene;
+// The pixel fidelity of the drawing instrument.
+// TODO: Make this configurable
+pub const PEN_PX_SIZE: f64 = 4.0;
#[cfg(test)]
pub(crate) static EPSILON: f64 = 0.004;
@@ -50,6 +57,7 @@ where
Meta: PathMeta,
{
fn collision_geometry(&self) -> Option>>>;
+ fn metadata(&self) -> Meta;
fn paths(&self, cam: &Camera) -> Vec>;
}
diff --git a/raydeon/src/lights.rs b/raydeon/src/lights.rs
new file mode 100644
index 0000000..47e2a53
--- /dev/null
+++ b/raydeon/src/lights.rs
@@ -0,0 +1,148 @@
+use material::Material;
+use scene::{SceneGeometry, SceneLighting};
+
+use crate::*;
+
+type LitScene = Scene, SceneLighting>;
+
+pub trait Light: std::fmt::Debug + Send + Sync + 'static {
+ fn compute_illumination(
+ &self,
+ scene: &Scene, SceneLighting>,
+ hitpoint: HitData,
+ shape: &Arc>,
+ ) -> f64;
+}
+
+#[derive(Debug, Copy, Clone, Default)]
+pub struct PointLight {
+ intensity: f64,
+ specular_intensity: f64,
+ position: WPoint3,
+
+ constant_attenuation: f64,
+ linear_attenuation: f64,
+ quadratic_attenuation: f64,
+}
+
+impl Light for PointLight {
+ fn compute_illumination(
+ &self,
+ scene: &LitScene,
+ hitpoint: HitData,
+ shape: &Arc>,
+ ) -> f64 {
+ let _light_hitpoint = match self.light_hitpoint_for_hit(scene, hitpoint, shape) {
+ Some(hit) => hit,
+ None => return 0.0,
+ };
+
+ let mut illum = 0.0;
+ let material = shape.metadata();
+
+ illum += self.diffuse_illumination(hitpoint, &material);
+ let specular = self.specular_illumination(hitpoint, &material);
+ tracing::debug!("specular: {}", specular);
+ illum += specular;
+
+ let atten = self.attenuation(hitpoint);
+ tracing::debug!("pre-attenuated illum: {}", illum);
+ tracing::debug!("atten: {}", atten);
+ let illum = illum * atten;
+
+ tracing::debug!("illum: {}", illum);
+ illum
+ }
+}
+
+impl PointLight {
+ pub fn new(
+ intensity: f64,
+ specular_intensity: f64,
+ position: impl Into,
+ constant_attenuation: f64,
+ linear_attenuation: f64,
+ quadratic_attenuation: f64,
+ ) -> Self {
+ let position = position.into();
+ Self {
+ intensity,
+ specular_intensity,
+ position,
+ constant_attenuation,
+ linear_attenuation,
+ quadratic_attenuation,
+ }
+ }
+
+ pub fn intensity(&self) -> f64 {
+ self.intensity
+ }
+
+ pub fn specular(&self) -> f64 {
+ self.specular_intensity
+ }
+
+ pub fn position(&self) -> WPoint3 {
+ self.position
+ }
+
+ pub fn constant_attenuation(&self) -> f64 {
+ self.constant_attenuation
+ }
+
+ pub fn linear_attenuation(&self) -> f64 {
+ self.linear_attenuation
+ }
+
+ pub fn quadratic_attenuation(&self) -> f64 {
+ self.quadratic_attenuation
+ }
+
+ fn diffuse_illumination(&self, hitpoint: HitData, material: &Material) -> f64 {
+ let to_light = (self.position - hitpoint.hit_point).normalize();
+ let diffuse_scale = to_light.dot(hitpoint.normal).max(0.0);
+ material.diffuse * self.intensity * diffuse_scale
+ }
+
+ fn specular_illumination(&self, hitpoint: HitData, material: &Material) -> f64 {
+ let to_light = (self.position - hitpoint.hit_point).normalize();
+
+ let v = hitpoint.hit_point.to_vector() * -1.0;
+ let h = (to_light + v).normalize();
+
+ let ps = material.specular * self.specular_intensity;
+ let blinn_phong = h.dot(hitpoint.normal).max(0.0).powf(material.shininess);
+ let blinn_phong = blinn_phong / (8.0 * std::f64::consts::PI / (material.shininess + 2.0));
+
+ ps * blinn_phong
+ }
+
+ fn attenuation(&self, hitpoint: HitData) -> f64 {
+ let distance = (self.position - hitpoint.hit_point).length();
+ let attenuation = self.constant_attenuation
+ + self.linear_attenuation * distance
+ + self.quadratic_attenuation * distance * distance;
+ 1.0 / attenuation
+ }
+
+ fn light_hitpoint_for_hit(
+ &self,
+ scene: &LitScene,
+ hitpoint: HitData,
+ shape: &Arc>,
+ ) -> Option {
+ let to_light = (self.position - hitpoint.hit_point).normalize();
+ if to_light.dot(hitpoint.normal) < 0.0 {
+ return None;
+ }
+
+ let to_hitpoint = (hitpoint.hit_point - self.position).normalize();
+ let light_ray = Ray::new(self.position, to_hitpoint);
+ scene
+ .intersects(light_ray)
+ .and_then(|(light_hitpoint, light_shape)| {
+ Arc::ptr_eq(&light_shape, shape).then_some(light_hitpoint)
+ })
+ }
+}
diff --git a/raydeon/src/material.rs b/raydeon/src/material.rs
new file mode 100644
index 0000000..6a0eafd
--- /dev/null
+++ b/raydeon/src/material.rs
@@ -0,0 +1,18 @@
+#[derive(Debug, Clone, Copy, Default)]
+pub struct Material {
+ pub diffuse: f64,
+ pub specular: f64,
+ pub shininess: f64,
+ pub tag: usize,
+}
+
+impl Material {
+ pub fn new(diffuse: f64, specular: f64, shininess: f64, tag: usize) -> Self {
+ Self {
+ diffuse,
+ specular,
+ shininess,
+ tag,
+ }
+ }
+}
diff --git a/raydeon/src/path.rs b/raydeon/src/path.rs
index a4f9122..2a9fd2c 100644
--- a/raydeon/src/path.rs
+++ b/raydeon/src/path.rs
@@ -175,15 +175,45 @@ where
}
pub fn join_slices(&self) -> Vec> {
+ self.join_slices_with_forgiveness(0)
+ }
+
+ /// Joins slices, ignoring gaps of size `forgiveness` or smaller
+ pub fn join_slices_with_forgiveness(
+ &self,
+ forgiveness: usize,
+ ) -> Vec> {
if self.included.is_empty() {
return Vec::new();
}
+ let mut included = self.included.clone();
+
+ let mut gap_start = None;
+ let mut last_filled = None;
+ for curr in 0..self.num_chops {
+ let empty = !included.contains(&curr);
+ match gap_start {
+ Some(start) if empty && curr - start > forgiveness => gap_start = None,
+ Some(start) if !empty => (start..curr).for_each(|ndx| {
+ included.insert(ndx);
+ gap_start = None
+ }),
+ None if empty && curr > 0 && last_filled == Some(curr - 1) => {
+ gap_start = Some(curr);
+ }
+ _ => (),
+ }
+ if !empty {
+ last_filled = Some(curr);
+ }
+ }
+
let mut ndx_groups = HashSet::new();
- let mut first = *self.included.first().unwrap();
+ let mut first = *included.first().unwrap();
let mut last = first;
- self.included.iter().for_each(|ndx| {
+ included.iter().for_each(|ndx| {
if *ndx == first {
return;
}
diff --git a/raydeon/src/ray.rs b/raydeon/src/ray.rs
index e6c58c9..41e9f68 100644
--- a/raydeon/src/ray.rs
+++ b/raydeon/src/ray.rs
@@ -34,11 +34,18 @@ pub struct HitData {
pub hit_point: WPoint3,
/// The distance that a ray travelled to hit this shape.
pub dist_to: f64,
+ pub normal: WVec3,
}
impl HitData {
- pub fn new(hit_point: WPoint3, dist_to: f64) -> HitData {
- HitData { hit_point, dist_to }
+ pub fn new(hit_point: impl Into, dist_to: f64, normal: impl Into) -> HitData {
+ let hit_point = hit_point.into();
+ let normal = normal.into();
+ HitData {
+ hit_point,
+ dist_to,
+ normal,
+ }
}
}
@@ -58,6 +65,8 @@ impl ApproxEq for &HitData {
fn approx_eq>(self, other: Self, margin: M) -> bool {
let margin = margin.into();
- self.hit_point.approx_eq(&other.hit_point) && self.dist_to.approx_eq(other.dist_to, margin)
+ self.hit_point.approx_eq(&other.hit_point)
+ && self.dist_to.approx_eq(other.dist_to, margin)
+ && self.normal.approx_eq(&other.normal)
}
}
diff --git a/raydeon/src/scene.rs b/raydeon/src/scene.rs
index 5fc217a..88af416 100644
--- a/raydeon/src/scene.rs
+++ b/raydeon/src/scene.rs
@@ -1,34 +1,257 @@
-use bvh::BVHTree;
+use bvh::{BVHTree, Collidable};
use camera::{Observation, Perspective};
+use collision::Continuous;
+use euclid::{Point2D, Vector2D};
+use material::Material;
use path::{LineSegment2D, SlicedSegment3D};
+use rand::distributions::Distribution;
+use rand::SeedableRng;
use rayon::prelude::*;
use std::sync::Arc;
use tracing::info;
use crate::*;
-pub struct SceneCamera<'s, P>
+const VERTICAL_HATCH_SCALING: f64 = 0.8;
+const DIAGONAL_HATCH_SCALING: f64 = 0.46;
+const HATCH_SPACING: f64 = PEN_PX_SIZE * 2.0;
+const HATCH_PIXEL_CHOP_SIZE: f64 = PEN_PX_SIZE / 3.0;
+const HATCHING_SLICE_FORGIVENESS: usize = 1;
+
+#[derive(Debug)]
+pub struct Scene {
+ geometry: G,
+ lighting: L,
+}
+
+#[derive(Debug)]
+pub struct SceneGeometry {
+ geometry: Vec>>,
+ bvh: BVHTree,
+}
+
+impl SceneGeometry {
+ pub fn new() -> SceneGeometry
{
+ Default::default()
+ }
+
+ pub fn with_geometry(mut self, geometry: Vec>>) -> Self {
+ let bvh = Self::create_bvh(&geometry);
+ self.geometry = geometry;
+ self.bvh = bvh;
+ self
+ }
+
+ pub fn push_geometry(mut self, geometry: Arc>) -> Self {
+ self.geometry.push(geometry);
+ self.bvh = Self::create_bvh(&self.geometry);
+ self
+ }
+
+ pub fn concat_geometry(mut self, geometry: &[Arc>]) -> Self {
+ self.geometry.extend_from_slice(geometry);
+ self.bvh = Self::create_bvh(&self.geometry);
+ self
+ }
+
+ fn create_bvh(geometry: &[Arc>]) -> BVHTree {
+ let collision_geometry: Vec<_> = geometry
+ .iter()
+ .filter_map(|s| {
+ s.collision_geometry().map(|collision_geom| {
+ collision_geom
+ .into_iter()
+ .map(|geom| (s.clone(), geom))
+ .collect::>()
+ })
+ })
+ .flatten()
+ .map(|(s, collision_geom)| Collidable::new(s, collision_geom))
+ .collect();
+ BVHTree::new(&collision_geometry)
+ }
+}
+
+impl Default for SceneGeometry {
+ fn default() -> Self {
+ Self {
+ geometry: Vec::new(),
+ bvh: BVHTree::new(&[]),
+ }
+ }
+}
+
+impl + 'static> From>> for SceneGeometry {
+ fn from(geometry: Vec>) -> Self {
+ SceneGeometry::new().with_geometry(
+ geometry
+ .into_iter()
+ .map(|s| s as Arc>)
+ .collect::>(),
+ )
+ }
+}
+
+impl From>>> for SceneGeometry {
+ fn from(geometry: Vec>>) -> Self {
+ SceneGeometry::new().with_geometry(geometry)
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct SceneLighting {
+ lights: Vec>,
+ ambient: f64,
+}
+
+impl SceneLighting {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ pub fn with_lights(mut self, lights: Vec>) -> Self {
+ self.lights = lights;
+ self
+ }
+
+ pub fn push_light(mut self, light: Arc) -> Self {
+ self.lights.push(light);
+ self
+ }
+
+ pub fn concat_lights(mut self, lights: &[Arc]) -> Self {
+ self.lights.extend_from_slice(lights);
+ self
+ }
+
+ pub fn with_ambient_lighting(mut self, ambient: f64) -> Self {
+ self.ambient = ambient;
+ self
+ }
+}
+
+impl From>> for SceneLighting {
+ fn from(lights: Vec>) -> Self {
+ let lights = lights
+ .into_iter()
+ .map(|l| l as Arc