-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement camera-space hatching based on lighting
Geometry can be associated with arbitrary metadata; however, if associated with a Material, the scene can be rendered usng raydeon's lighting model. The lighting model implements diffuse and specular lighting, with hatch line subsegments removed stochastically based on the brightess at the associated point in world space.
- Loading branch information
Showing
31 changed files
with
2,988 additions
and
228 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import math | ||
import svg | ||
import numpy as np | ||
|
||
from pyraydeon import ( | ||
Camera, | ||
Point3, | ||
Scene, | ||
Sphere, | ||
Vec3, | ||
Geometry, | ||
Material, | ||
PointLight, | ||
Plane, | ||
LineSegment3D, | ||
) | ||
|
||
|
||
class PySphere(Geometry): | ||
def __init__(self, point, radius, material=None): | ||
if material is not None: | ||
self._material = material | ||
|
||
self.sphere = Sphere(point, radius) | ||
|
||
@property | ||
def material(self): | ||
return self._material | ||
|
||
def collision_geometry(self): | ||
return [self.sphere] | ||
|
||
def paths(self, cam): | ||
hyp = np.linalg.norm(self.sphere.center - cam.eye) | ||
opp = self.sphere.radius | ||
|
||
theta = math.asin(opp / hyp) | ||
adj = opp / math.tan(theta) | ||
d = math.cos(theta) * adj | ||
r = math.sin(theta) * adj | ||
|
||
w = self.sphere.center - cam.eye | ||
w = w / np.linalg.norm(w) | ||
|
||
u = np.cross(w, cam.up) | ||
u = u / np.linalg.norm(u) | ||
|
||
v = np.cross(w, u) | ||
v = v / np.linalg.norm(v) | ||
|
||
points = [] | ||
c = cam.eye + d * w | ||
for i in range(0, 180): | ||
a = math.radians(float(i)) | ||
p = c | ||
p = p + u * (math.cos(a) * r) | ||
p = p + v * (math.sin(a) * r) | ||
|
||
push = p - self.sphere.center | ||
push = push / np.linalg.norm(push) | ||
|
||
p += push * 0.00015 | ||
points.append(p) | ||
|
||
paths = [] | ||
for i in range(0, len(points) - 1): | ||
paths.append(LineSegment3D(points[i], points[(i + 1)])) | ||
|
||
return paths | ||
|
||
|
||
class PyPlane(Geometry): | ||
def __init__(self, point, normal, material=None): | ||
if material is not None: | ||
self._material = material | ||
|
||
self.plane = Plane(point, normal) | ||
|
||
@property | ||
def material(self): | ||
return self._material | ||
|
||
def collision_geometry(self): | ||
return [self.plane] | ||
|
||
def paths(self, cam): | ||
return [] | ||
|
||
|
||
scene = Scene( | ||
geometry=[ | ||
PySphere(Point3(0, 0, 0), 1.0, Material(3.0, 3.0, 3)), | ||
PyPlane(Point3(0, -2, 0), Vec3(0, 1, 0), Material(9000.0, 3.0, 3)), | ||
], | ||
lights=[PointLight((4, 3, 10), 4.0, 2.0, 0.15, 0.4, 0.11)], | ||
) | ||
|
||
|
||
eye = Point3(0, 0, 5) | ||
focus = Vec3(0, 0, 0) | ||
up = Vec3(0, 1, 0) | ||
|
||
fovy = 50.0 | ||
width = 1024 | ||
height = 1024 | ||
znear = 0.1 | ||
zfar = 100.0 | ||
|
||
cam = Camera().look_at(eye, focus, up).perspective(fovy, width, height, znear, zfar) | ||
|
||
paths = scene.render_with_lighting(cam, seed=5) | ||
|
||
canvas = svg.SVG( | ||
width="8in", | ||
height="8in", | ||
viewBox="0 0 1024 1024", | ||
) | ||
backing_rect = svg.Rect( | ||
x=0, | ||
y=0, | ||
width="100%", | ||
height="100%", | ||
fill="white", | ||
) | ||
svg_lines = [ | ||
svg.Line( | ||
x1=f"{path.p1[0]}", | ||
y1=f"{path.p1[1]}", | ||
x2=f"{path.p2[0]}", | ||
y2=f"{path.p2[1]}", | ||
stroke_width="0.7mm", | ||
stroke="black", | ||
) | ||
for path in paths | ||
] | ||
line_group = svg.G(transform=f"translate(0, {height}) scale(1, -1)", elements=svg_lines) | ||
canvas.elements = [backing_rect, line_group] | ||
|
||
|
||
print(canvas) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
use crate::linear::Point3; | ||
use pyo3::prelude::*; | ||
|
||
pywrap!(PointLight, raydeon::lights::PointLight); | ||
|
||
#[pymethods] | ||
impl PointLight { | ||
#[new] | ||
#[pyo3(signature = ( | ||
position, | ||
intensity=0.0, | ||
specular_intensity=0.0, | ||
constant_attenuation=0.0, | ||
linear_attenuation=0.0, | ||
quadratic_attenuation=0.0 | ||
))] | ||
fn new( | ||
position: &Bound<'_, PyAny>, | ||
intensity: f64, | ||
specular_intensity: f64, | ||
constant_attenuation: f64, | ||
linear_attenuation: f64, | ||
quadratic_attenuation: f64, | ||
) -> PyResult<Self> { | ||
let position = Point3::try_from(position)?; | ||
Ok(raydeon::lights::PointLight::new( | ||
intensity, | ||
specular_intensity, | ||
position.0.cast_unit(), | ||
constant_attenuation, | ||
linear_attenuation, | ||
quadratic_attenuation, | ||
) | ||
.into()) | ||
} | ||
|
||
#[getter] | ||
fn intensity(&self) -> f64 { | ||
self.0.intensity() | ||
} | ||
|
||
#[getter] | ||
fn specular(&self) -> f64 { | ||
self.0.specular() | ||
} | ||
|
||
#[getter] | ||
fn position(&self) -> Point3 { | ||
self.0.position().cast_unit().into() | ||
} | ||
|
||
#[getter] | ||
fn constant_attenuation(&self) -> f64 { | ||
self.0.constant_attenuation() | ||
} | ||
|
||
#[getter] | ||
fn linear_attenuation(&self) -> f64 { | ||
self.0.linear_attenuation() | ||
} | ||
|
||
#[getter] | ||
fn quadratic_attenuation(&self) -> f64 { | ||
self.0.quadratic_attenuation() | ||
} | ||
|
||
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> { | ||
let class_name = slf.get_type().qualname()?; | ||
Ok(format!("{}<{:#?}>", class_name, slf.borrow().0)) | ||
} | ||
} | ||
|
||
impl Default for PointLight { | ||
fn default() -> Self { | ||
raydeon::lights::PointLight::default().into() | ||
} | ||
} | ||
|
||
pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { | ||
m.add_class::<PointLight>()?; | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
use pyo3::prelude::*; | ||
|
||
pywrap!(Material, raydeon::material::Material); | ||
|
||
#[pymethods] | ||
impl Material { | ||
#[new] | ||
#[pyo3(signature = (diffuse=0.0, specular=0.0, shininess=0.0, tag=0))] | ||
fn new(diffuse: f64, specular: f64, shininess: f64, tag: usize) -> PyResult<Self> { | ||
Ok(raydeon::material::Material::new(diffuse, specular, shininess, tag).into()) | ||
} | ||
|
||
#[getter] | ||
fn diffuse(&self) -> f64 { | ||
self.diffuse | ||
} | ||
|
||
#[getter] | ||
fn specular(&self) -> f64 { | ||
self.specular | ||
} | ||
|
||
#[getter] | ||
fn shininess(&self) -> f64 { | ||
self.shininess | ||
} | ||
|
||
#[getter] | ||
fn tag(&self) -> usize { | ||
self.tag | ||
} | ||
|
||
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> { | ||
let class_name = slf.get_type().qualname()?; | ||
Ok(format!("{}<{:#?}>", class_name, slf.borrow().0)) | ||
} | ||
} | ||
|
||
impl Default for Material { | ||
fn default() -> Self { | ||
raydeon::material::Material::default().into() | ||
} | ||
} | ||
|
||
pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { | ||
m.add_class::<Material>()?; | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.