Skip to content

Commit 4afddf1

Browse files
committed
feat: add camera motion controls
1 parent 2b9cfdd commit 4afddf1

File tree

13 files changed

+23387
-47
lines changed

13 files changed

+23387
-47
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.vox filter=lfs diff=lfs merge=lfs -text
2+
*.png filter=lfs diff=lfs merge=lfs -text

.github/workflows/raydeon-ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ jobs:
2424
target: aarch64
2525
steps:
2626
- uses: actions/checkout@v4
27+
with:
28+
lfs: "true"
2729
- name: Install uv
2830
uses: astral-sh/setup-uv@v3
2931
- name: Install perceptualdiff

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ steal his words (because this port isn't theft enough):
1111
This repository has added support for screen-space hatching based on lights
1212
placed within the scene.
1313

14-
![](/raydeon/examples/cityscape.png)
14+
![](/raydeon/examples/cityscape.png) ![](/raydeon/examples/castle_expected.svg)
1515

1616
## Example
1717

pyraydeon/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ name = "pyraydeon"
99
crate-type = ["cdylib"]
1010

1111
[dependencies]
12+
euclid.workspace = true
1213
pyo3 = { workspace = true, features = ["extension-module"] }
1314
raydeon.workspace = true
1415
numpy.workspace = true

pyraydeon/examples/py_sphere.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ def paths(self, cam):
9090
)
9191

9292

93-
eye = Point3(0, 0, 5)
94-
focus = Vec3(0, 0, 0)
93+
eye = Point3(0, 0, 0)
94+
focus = Point3(0, 0, -1)
9595
up = Vec3(0, 1, 0)
9696

9797
fovy = 50.0
@@ -110,6 +110,7 @@ def paths(self, cam):
110110
.perspective(fovy, width, height, znear, zfar)
111111
.render_options(render_opts)
112112
)
113+
cam.translate((0, 0, 5))
113114

114115
paths = scene.render_with_lighting(cam, seed=5)
115116

pyraydeon/src/camera.rs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use pyo3::prelude::*;
44
use crate::linear::{Point3, Vec3};
55

66
#[derive(Debug, Clone)]
7-
#[pyclass(frozen)]
7+
#[pyclass]
88
pub(crate) struct Camera(pub(crate) raydeon::Camera);
99

1010
impl ::std::ops::Deref for Camera {
@@ -55,19 +55,45 @@ impl Camera {
5555
ncam.into()
5656
}
5757

58+
fn translate(&mut self, trans: &Bound<'_, PyAny>) -> PyResult<()> {
59+
let trans = Vec3::try_from(trans)?;
60+
self.0.translate(trans.0.cast_unit());
61+
Ok(())
62+
}
63+
64+
fn adjust_yaw(&mut self, yaw: f64) -> PyResult<()> {
65+
self.0.adjust_yaw(euclid::Angle::degrees(yaw));
66+
Ok(())
67+
}
68+
69+
fn adjust_pitch(&mut self, pitch: f64) -> PyResult<()> {
70+
self.0.adjust_pitch(euclid::Angle::degrees(pitch));
71+
Ok(())
72+
}
73+
74+
fn adjust_roll(&mut self, roll: f64) -> PyResult<()> {
75+
self.0.adjust_roll(euclid::Angle::degrees(roll));
76+
Ok(())
77+
}
78+
5879
#[getter]
5980
fn eye<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<f64, Ix1>> {
60-
PyArray::from_slice_bound(py, &self.0.observation.eye.to_array())
81+
PyArray::from_slice_bound(py, &self.0.observation.eye().to_array())
6182
}
6283

6384
#[getter]
64-
fn focus<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<f64, Ix1>> {
65-
PyArray::from_slice_bound(py, &self.0.observation.center.to_array())
85+
fn up<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<f64, Ix1>> {
86+
PyArray::from_slice_bound(py, &self.0.observation.up().to_array())
6687
}
6788

6889
#[getter]
69-
fn up<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<f64, Ix1>> {
70-
PyArray::from_slice_bound(py, &self.0.observation.up.to_array())
90+
fn right<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<f64, Ix1>> {
91+
PyArray::from_slice_bound(py, &self.0.observation.right().to_array())
92+
}
93+
94+
#[getter]
95+
fn look<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<f64, Ix1>> {
96+
PyArray::from_slice_bound(py, &self.0.observation.look().to_array())
7197
}
7298

7399
#[getter]
@@ -102,7 +128,7 @@ impl Camera {
102128

103129
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
104130
let class_name = slf.get_type().qualname()?;
105-
Ok(format!("{}<{:?}>", class_name, slf.borrow().0))
131+
Ok(format!("{}<{:#?}>", class_name, slf.borrow().0))
106132
}
107133
}
108134

raydeon/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ authors = ["cbgbt <[email protected]>"]
55
edition = "2021"
66

77
[dependencies]
8-
anyhow.workspace = true
98
bon.workspace = true
109
cgmath.workspace = true
1110
collision.workspace = true

raydeon/examples/assets/castle.vox

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:9c348a638b094730ca5bfd362735ba8b4aa5b65e0c29ea12243bcf1ac972a60a
3+
size 33209

raydeon/examples/castle.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
use dot_vox;
2+
use euclid::Angle;
3+
use raydeon::lights::PointLight;
4+
use raydeon::shapes::AxisAlignedCuboid;
5+
use raydeon::{Camera, CameraOptions, DrawableShape, Material, Scene, SceneLighting};
6+
use std::sync::Arc;
7+
8+
const CASTLE_VOX: &[u8] = include_bytes!("./assets/castle.vox");
9+
10+
fn main() {
11+
env_logger::Builder::from_default_env()
12+
.format_timestamp_nanos()
13+
.init();
14+
15+
let castle_vox = dot_vox::load_bytes(CASTLE_VOX).expect("Could not load castle.vox");
16+
17+
let geometry: Vec<_> = castle_vox.models[0]
18+
.voxels
19+
.iter()
20+
.map(|v| {
21+
let x = v.x as f64;
22+
let y = v.y as f64;
23+
let z = v.z as f64;
24+
DrawableShape::new()
25+
.geometry(Arc::new(
26+
AxisAlignedCuboid::new()
27+
.min((x + 0.05, y + 0.05, z + 0.05))
28+
.max((x + 0.95, y + 0.95, z + 0.95))
29+
.build(),
30+
))
31+
.material(Material::new().diffuse(5.0).build())
32+
.build()
33+
})
34+
.collect();
35+
36+
let eye = (10.0, -20.0, 0.0);
37+
let focus = (10.0, 0.0, 0.0);
38+
let up = (0.0, 0.0, 1.0);
39+
40+
let fovy = 40.0;
41+
let width = 2048;
42+
let height = 2048;
43+
let znear = 0.1;
44+
let zfar = 200.0;
45+
46+
let mut camera = Camera::configure()
47+
.observation(Camera::look_at(eye, focus, up))
48+
.perspective(Camera::perspective(fovy, width, height, znear, zfar))
49+
.render_options(CameraOptions::configure().pen_px_size(4.0).build())
50+
.build();
51+
52+
camera.translate((-15.0, 10.75, 0.0));
53+
camera.adjust_yaw(Angle::degrees(-45.0));
54+
camera.translate((-10.5, 0.0, 12.0));
55+
56+
let scene = Scene::new()
57+
.geometry(geometry)
58+
.lighting(
59+
SceneLighting::new()
60+
.with_ambient_lighting(0.37)
61+
.with_lights(vec![Arc::new(PointLight::new(
62+
55.0,
63+
10.0,
64+
(-10.81, -20.0, 30.0),
65+
0.0,
66+
0.13,
67+
0.19,
68+
))]),
69+
)
70+
.construct();
71+
72+
let paths = scene
73+
.attach_camera(camera)
74+
.with_seed(0)
75+
.render_with_lighting();
76+
77+
let mut svg_doc = svg::Document::new()
78+
.set("width", "8in")
79+
.set("height", "8in")
80+
.set("viewBox", (0, 0, width, height))
81+
.set("stroke-width", "0.7mm")
82+
.set("stroke", "black")
83+
.set("fill", "none")
84+
.add(
85+
svg::node::element::Group::new().add(
86+
svg::node::element::Rectangle::new()
87+
.set("x", 0)
88+
.set("y", 0)
89+
.set("width", "100%")
90+
.set("height", "100%")
91+
.set("fill", "white"),
92+
),
93+
);
94+
95+
// We have to flip the y-axis in our svg...
96+
let mut item_group = svg::node::element::Group::new()
97+
.set("transform", format!("translate(0, {}) scale(1,-1)", height));
98+
99+
for path in paths {
100+
let (p1, p2) = (path.p1, path.p2);
101+
item_group = item_group.add(
102+
svg::node::element::Line::new()
103+
.set("x1", p1.x)
104+
.set("y1", p1.y)
105+
.set("x2", p2.x)
106+
.set("y2", p2.y),
107+
);
108+
}
109+
110+
svg_doc = svg_doc.add(item_group);
111+
println!("{}", svg_doc);
112+
}

0 commit comments

Comments
 (0)