Skip to content

Commit

Permalink
add support for tagged paths
Browse files Browse the repository at this point in the history
This allows a simple material implementation
  • Loading branch information
cbgbt committed Nov 15, 2024
1 parent 1d78de3 commit 7d8f023
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 86 deletions.
3 changes: 2 additions & 1 deletion examples/cube.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ fn main() -> Result<()> {
let mut item_group = svg::node::element::Group::new()
.set("transform", format!("translate(0, {}) scale(1,-1)", height));

for (p1, p2) in paths {
for path in paths {
let (p1, p2) = (path.p1, path.p2);
item_group = item_group.add(
svg::node::element::Line::new()
.set("x1", p1.x)
Expand Down
3 changes: 2 additions & 1 deletion examples/geom_perf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ fn main() -> Result<()> {
let mut item_group = svg::node::element::Group::new()
.set("transform", format!("translate(0, {}) scale(1,-1)", height));

for (p1, p2) in paths {
for path in paths {
let (p1, p2) = (path.p1, path.p2);
item_group = item_group.add(
svg::node::element::Line::new()
.set("x1", p1.x)
Expand Down
3 changes: 2 additions & 1 deletion examples/triangles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ fn main() -> Result<()> {
let mut item_group = svg::node::element::Group::new()
.set("transform", format!("translate(0, {}) scale(1,-1)", height));

for (p1, p2) in paths {
for path in paths {
let (p1, p2) = (path.p1, path.p2);
item_group = item_group.add(
svg::node::element::Line::new()
.set("x1", p1.x)
Expand Down
38 changes: 17 additions & 21 deletions src/bvh.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::shapes::RectPrism;
use crate::{HitData, Ray, Shape, WorldSpace, AABB};
use euclid::Point3D;
use rayon::prelude::*;
Expand All @@ -15,7 +14,7 @@ pub(crate) enum Axis {
#[derive(Debug)]
pub(crate) struct BVHTree<Space>
where
Space: Send + Sync + Sized + std::fmt::Debug + 'static,
Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static,
{
aabb: AABB<Space>,
root: Option<Node<Space>>,
Expand All @@ -24,7 +23,7 @@ where

impl<Space> BVHTree<Space>
where
Space: Send + Sync + Sized + std::fmt::Debug + 'static,
Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static,
{
pub(crate) fn new(shapes: &[Arc<dyn Shape<Space>>]) -> Self {
info!(
Expand All @@ -34,7 +33,7 @@ where
let mut bounded = Vec::with_capacity(shapes.len());
let mut unbounded = Vec::with_capacity(shapes.len());

for shape in shapes.into_iter() {
for shape in shapes.iter() {
let aabb = shape.bounding_box();

let shape = Arc::clone(shape);
Expand Down Expand Up @@ -65,7 +64,7 @@ impl BVHTree<WorldSpace> {
self.intersects_unbounded_volume(ray),
]
.into_iter()
.filter_map(std::convert::identity)
.flatten()
.min_by(|hit1, hit2| hit1.dist_to.partial_cmp(&hit2.dist_to).unwrap())
}

Expand All @@ -89,9 +88,9 @@ impl BVHTree<WorldSpace> {
}

#[derive(Debug)]
pub(crate) enum Node<Space>
enum Node<Space>
where
Space: Send + Sync + Sized + std::fmt::Debug + 'static,
Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static,
{
Parent(ParentNode<Space>),
Leaf(LeafNode<Space>),
Expand All @@ -100,7 +99,7 @@ where
#[derive(Debug)]
struct ParentNode<Space>
where
Space: Send + Sync + Sized + std::fmt::Debug + 'static,
Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static,
{
axis: Axis,
point: f64,
Expand Down Expand Up @@ -162,21 +161,18 @@ impl ParentNode<WorldSpace> {
#[derive(Debug)]
struct LeafNode<Space>
where
Space: Send + Sync + Sized + std::fmt::Debug + 'static,
Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static,
{
shapes: Vec<Arc<BoundedShape<Space>>>,
}

type PartitionedSegments<Space> = (Vec<Arc<BoundedShape<Space>>>, Vec<Arc<BoundedShape<Space>>>);

impl<Space> LeafNode<Space>
where
Space: Send + Sync + Sized + std::fmt::Debug + 'static,
Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static,
{
fn partition(
&self,
best: u64,
best_axis: Axis,
best_point: f64,
) -> (Vec<Arc<BoundedShape<Space>>>, Vec<Arc<BoundedShape<Space>>>) {
fn partition(&self, best: u64, best_axis: Axis, best_point: f64) -> PartitionedSegments<Space> {
let mut left = Vec::with_capacity(best as usize);
let mut right = Vec::with_capacity(best as usize);
for shape in &self.shapes {
Expand Down Expand Up @@ -222,7 +218,7 @@ impl LeafNode<WorldSpace> {

impl<Space> Node<Space>
where
Space: Send + Sync + Sized + std::fmt::Debug + 'static,
Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static,
{
fn new(shapes: Vec<Arc<BoundedShape<Space>>>) -> (Self, usize) {
let mut node = Self::Leaf(LeafNode { shapes });
Expand Down Expand Up @@ -309,15 +305,15 @@ impl Node<WorldSpace> {
#[derive(Debug)]
struct BoundedShape<Space>
where
Space: Send + Sync + Sized + std::fmt::Debug + 'static,
Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static,
{
shape: Arc<dyn Shape<Space>>,
aabb: AABB<Space>,
}

fn bounding_box_for_shapes<Space>(shapes: &[Arc<BoundedShape<Space>>]) -> AABB<Space>
where
Space: Send + Sync + Sized + std::fmt::Debug + 'static,
Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static,
{
let aabb = AABB::new(Point3D::splat(f64::MAX), Point3D::splat(f64::MIN));
let bounding_boxes = shapes.iter().map(|shape| shape.aabb).collect::<Vec<_>>();
Expand All @@ -335,7 +331,7 @@ where

fn partition_bounding_box<Space>(axis: Axis, aabb: AABB<Space>, point: f64) -> (bool, bool)
where
Space: Send + Sync + Sized + std::fmt::Debug + 'static,
Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static,
{
match axis {
Axis::X => (aabb.min.x <= point, aabb.max.x >= point),
Expand All @@ -361,7 +357,7 @@ fn median(nums: &[f64]) -> f64 {
match len {
0 => 0.0,
n if n % 2 == 1 => nums[len / 2],
n => {
_ => {
let a = nums[len / 2 - 1];
let b = nums[len / 2 - 1];
(a + b) / 2.0
Expand Down
8 changes: 4 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ pub use scene::{Camera, Scene};
#[cfg(test)]
pub(crate) static EPSILON: f64 = 0.004;

#[derive(Debug)]
#[derive(Debug, Copy, Clone)]
pub struct WorldSpace;
#[derive(Debug)]
#[derive(Debug, Copy, Clone)]
pub struct CameraSpace;

#[derive(Debug)]
#[derive(Debug, Copy, Clone)]
pub struct CanvasSpace;

pub type WVec3 = Vector3D<f64, WorldSpace>;
Expand All @@ -36,7 +36,7 @@ pub type CCTransform = Transform3D<f64, CameraSpace, CameraSpace>;

pub trait Shape<Space>: Send + Sync + std::fmt::Debug
where
Space: Sized + Send + Sync + std::fmt::Debug,
Space: Sized + Send + Sync + std::fmt::Debug + Copy + Clone,
{
fn hit_by(&self, ray: &Ray) -> Option<HitData>;
fn paths(&self) -> Vec<LineSegment<Space>>;
Expand Down
89 changes: 59 additions & 30 deletions src/path.rs
Original file line number Diff line number Diff line change
@@ -1,70 +1,99 @@
use euclid::approxeq::ApproxEq;
use euclid::*;
use rayon::prelude::*;

pub type LineSegment<Space> = (Point3D<f64, Space>, Point3D<f64, Space>);
#[derive(Debug, Copy, Clone)]
pub struct LineSegment<Space>
where
Space: Copy + Clone + std::fmt::Debug,
{
pub p1: Point3D<f64, Space>,
pub p2: Point3D<f64, Space>,
pub tag: usize,
}

pub fn transform_segment<T, Dst>(
path: LineSegment<T>,
transformation: &Transform3D<f64, T, Dst>,
) -> Option<LineSegment<Dst>>
impl<Space> LineSegment<Space>
where
T: Send + Sync + Sized,
Dst: Send + Sync + Sized,
Space: Copy + Clone + std::fmt::Debug,
{
let (p1, p2) = path;
let p1t = transformation.transform_point3d(p1);
let p2t = transformation.transform_point3d(p2);
p1t.and_then(|p1| p2t.map(|p2| (p1, p2)))
pub fn new(p1: Point3D<f64, Space>, p2: Point3D<f64, Space>) -> Self {
Self::tagged(p1, p2, 0)
}

pub fn tagged(p1: Point3D<f64, Space>, p2: Point3D<f64, Space>, tag: usize) -> Self {
Self { p1, p2, tag }
}

pub fn transform<Dst>(
&self,
transformation: &Transform3D<f64, Space, Dst>,
) -> Option<LineSegment<Dst>>
where
Dst: Copy + Clone + std::fmt::Debug,
{
let (p1, p2) = (self.p1, self.p2);
let p1t = transformation.transform_point3d(p1);
let p2t = transformation.transform_point3d(p2);
p1t.and_then(|p1| p2t.map(|p2| LineSegment::tagged(p1, p2, self.tag)))
}
}

pub fn simplify_segments<T>(paths: &[LineSegment<T>], threshold: f64) -> Vec<LineSegment<T>> {
pub fn simplify_segments<T>(paths: &[LineSegment<T>], threshold: f64) -> Vec<LineSegment<T>>
where
T: Copy + Clone + std::fmt::Debug,
{
let eps: Point3D<f64, T> = Point3D::new(threshold, threshold, threshold);
let mut npaths = Vec::new();
let mut curr_line: Option<(Point3D<f64, T>, Point3D<f64, T>)> = None;
let mut curr_line: Option<LineSegment<T>> = None;
let mut curr_pushed = true;
for (v1, v2) in paths {
let v1 = *v1;
let v2 = *v2;
for path in paths {
let v1 = path.p1;
let v2 = path.p2;
if curr_line.is_none() {
curr_line = Some((v1, v2));
curr_line = Some(*path);
curr_pushed = false;
} else {
let (cv1, cv2) = curr_line.unwrap();
let (cv1, cv2) = curr_line
.as_ref()
.map(|curr_line| (curr_line.p1, curr_line.p2))
.unwrap();
let curr_line_dir = (cv2 - cv1).normalize();
let nline_dir = (v2 - v1).normalize();

let same_dir = curr_line_dir.approx_eq_eps(&nline_dir, &eps.to_vector())
|| curr_line_dir.approx_eq_eps(&-nline_dir, &eps.to_vector());

if same_dir {
let same_tag = curr_line.as_ref().unwrap().tag == path.tag;

if same_tag && same_dir {
if cv1.approx_eq_eps(&v1, &eps) {
curr_line = Some((v2, cv2));
curr_line = Some(LineSegment::tagged(v2, cv2, path.tag));
curr_pushed = false;
} else if cv1.approx_eq_eps(&v2, &eps) {
curr_line = Some((v1, cv2));
curr_line = Some(LineSegment::tagged(v1, cv2, path.tag));
curr_pushed = false;
} else if cv2.approx_eq_eps(&v1, &eps) {
curr_line = Some((v2, cv1));
curr_line = Some(LineSegment::tagged(v2, cv1, path.tag));
curr_pushed = false;
} else if cv2.approx_eq_eps(&v2, &eps) {
curr_line = Some((v1, cv1));
curr_line = Some(LineSegment::tagged(v1, cv1, path.tag));
curr_pushed = false;
} else {
npaths.push((cv1, cv2));
curr_line = Some((v1, v2));
npaths.push(curr_line.unwrap());
curr_line = Some(LineSegment::tagged(v1, v2, path.tag));
curr_pushed = true;
}
} else {
npaths.push((cv1, cv2));
curr_line = Some((v1, v2));
npaths.push(curr_line.unwrap());
curr_line = Some(*path);
curr_pushed = false;
}
}
}

if curr_line.is_some() && !curr_pushed {
npaths.push(curr_line.unwrap());
if let Some(curr_line) = curr_line {
if !curr_pushed {
npaths.push(curr_line);
}
}

npaths
Expand Down
25 changes: 12 additions & 13 deletions src/scene.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bvh::BVHTree;
use path::{simplify_segments, transform_segment};
use path::simplify_segments;
use rayon::prelude::*;
use std::sync::Arc;
use tracing::info;
Expand Down Expand Up @@ -67,9 +67,8 @@ impl Camera {
/// Chops a line segment into subsegments based on distance from camera
pub fn chop_segment(&self, segment: &LineSegment<WorldSpace>) -> Vec<LineSegment<WorldSpace>> {
// linearly interpolate step_size based on closest point to the camera
let (p1, p2) = segment;
let p1 = p1.to_vector();
let p2 = p2.to_vector();
let p1 = segment.p1.to_vector();
let p2 = segment.p2.to_vector();
let segment_diff = p2 - p1;
let midpoint = p1 + (segment_diff / 2.0);

Expand Down Expand Up @@ -98,7 +97,7 @@ impl Camera {
1,
);
if chunk_count == 1 {
return vec![(p1.to_point(), p2.to_point())];
return vec![*segment];
}

let true_chunk_len = segment_length / chunk_count as f64;
Expand All @@ -108,9 +107,9 @@ impl Camera {
let chunk_vec = segment_dir * true_chunk_len;
(0..chunk_count)
.map(|segment_ndx| {
let p1 = segment.0 + (chunk_vec * (segment_ndx as f64));
let p1 = segment.p1 + (chunk_vec * (segment_ndx as f64));
let p2 = p1 + chunk_vec;
(p1, p2)
LineSegment::tagged(p1, p2, segment.tag)
})
.collect()
}
Expand Down Expand Up @@ -156,8 +155,8 @@ impl LookingCamera {
let effective_height = height;
let znear_width = 2.0 * xmax;
let znear_height = 2.0 * ymax;
let est_min_pix_height = (znear_height / effective_height);
let est_min_pix_width = (znear_width / effective_width);
let est_min_pix_height = znear_height / effective_height;
let est_min_pix_width = znear_width / effective_width;

let min_step_size = f64::min(est_min_pix_height, est_min_pix_width);

Expand Down Expand Up @@ -197,8 +196,8 @@ pub struct SceneCamera<'s> {

impl<'a> SceneCamera<'a> {
fn clip_filter(&self, path: &LineSegment<WorldSpace>) -> bool {
let (p1, p2) = path;
let midpoint = *p1 + ((*p2 - *p1) / 2.0);
let (p1, p2) = (path.p1, path.p2);
let midpoint = p1 + ((p2 - p1) / 2.0);
self.scene.visible(self.camera.eye, midpoint)
}

Expand All @@ -222,7 +221,7 @@ impl<'a> SceneCamera<'a> {
path_group
.par_iter()
.filter(|path| {
let close_enough = (path.0.to_vector() - self.camera.eye.to_vector())
let close_enough = (path.p1.to_vector() - self.camera.eye.to_vector())
.length()
< self.camera.zfar;
close_enough && self.clip_filter(path)
Expand All @@ -231,7 +230,7 @@ impl<'a> SceneCamera<'a> {
.collect::<Vec<_>>()
})
.flat_map(|path_group| simplify_segments(&path_group, 1.0e-6))
.filter_map(|path| transform_segment(path, &transformation))
.filter_map(|path| path.transform(&transformation))
.collect();

info!("{} paths remain after clipping", paths.len());
Expand Down
Loading

0 comments on commit 7d8f023

Please sign in to comment.