Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions crates/processing_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,21 @@ thread_local! {
}

pub fn app_mut<T>(cb: impl FnOnce(&mut App) -> error::Result<T>) -> error::Result<T> {
let res = APP.with(|app_cell| {
let mut app_borrow = app_cell.borrow_mut();
// `try_with` so a `Drop` running after the TLS is destroyed sees an
// `AppAccess` error rather than panicking
let res = APP.try_with(|app_cell| {
let mut app_borrow = app_cell
.try_borrow_mut()
.map_err(|_| error::ProcessingError::AppAccess)?;
let app = app_borrow
.as_mut()
.ok_or(error::ProcessingError::AppAccess)?;
cb(app)
})?;
Ok(res)
});
match res {
Ok(inner) => inner,
Err(_) => Err(error::ProcessingError::AppAccess),
}
}

pub fn is_already_init() -> error::Result<bool> {
Expand Down
2 changes: 1 addition & 1 deletion crates/processing_pyo3/examples/camera_controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def setup():
mode_3d()
orbit_camera()

dir_light = create_directional_light((1.0, 0.98, 0.95), 1500.0)
dir_light = directional_light((1.0, 0.98, 0.95), 1500.0)
dir_light.position(300.0, 400.0, 300.0)
dir_light.look_at(0.0, 0.0, 0.0)

Expand Down
8 changes: 4 additions & 4 deletions crates/processing_pyo3/examples/lights.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ def setup():
mode_3d()

# Directional Light
dir_light = create_directional_light((0.5, 0.24, 1.0), 1500.0)
dir_light = directional_light((0.5, 0.24, 1.0), 1500.0)

# Point Lights
point_light_a = create_point_light((1.0, 0.5, 0.25), 1000000.0, 200.0, 0.5)
point_light_a = point_light((1.0, 0.5, 0.25), 1000000.0, 200.0, 0.5)
point_light_a.position(-25.0, 5.0, 51.0)
point_light_a.look_at(0.0, 0.0, 0.0)

point_light_b = create_point_light((0.0, 0.5, 0.75), 2000000.0, 200.0, 0.25)
point_light_b = point_light((0.0, 0.5, 0.75), 2000000.0, 200.0, 0.25)
point_light_b.position(0.0, 5.0, 50.5)
point_light_b.look_at(0.0, 0.0, 0.0)

# Spot Light
spot_light = create_spot_light((0.25, 0.8, 0.19), 15.0 * 1000000.0, 200.0, 0.84, 0.0, 0.7854)
spot_light = spot_light((0.25, 0.8, 0.19), 15.0 * 1000000.0, 200.0, 0.84, 0.0, 0.7854)
spot_light.position(40.0, 0.0, 70.0)
spot_light.look_at(0.0, 0.0, 0.0)

Expand Down
4 changes: 2 additions & 2 deletions crates/processing_pyo3/examples/materials.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ def setup():
size(800, 600)
mode_3d()

dir_light = create_directional_light((1.0, 0.98, 0.95), 1500.0)
point_light = create_point_light((1.0, 1.0, 1.0), 100000.0, 800.0, 0.0)
dir_light = directional_light((1.0, 0.98, 0.95), 1500.0)
point_light = point_light((1.0, 1.0, 1.0), 100000.0, 800.0, 0.0)
point_light.position(200.0, 200.0, 400.0)

mat = Material()
Expand Down
85 changes: 59 additions & 26 deletions crates/processing_pyo3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,19 @@ mod mewnala {
#[pymodule_export]
use crate::color::PyColor;

#[pyfunction(name = "color")]
#[pyo3(signature = (*args))]
fn color_ctor(py: Python<'_>, args: &Bound<'_, PyTuple>) -> PyResult<PyColor> {
let parent = py.import("mewnala.mewnala")?;
match get_graphics(&parent)? {
Some(g) => g.color(args),
None => {
let mode = crate::color::ColorMode::default();
crate::color::extract_color_with_mode(args, &mode).map(PyColor::from)
}
}
}

#[pyfunction]
fn hex(s: &str) -> PyResult<PyColor> {
PyColor::hex(s)
Expand Down Expand Up @@ -1004,7 +1017,7 @@ mod mewnala {
return Ok(());
}

Python::attach(|py| {
let result: PyResult<()> = Python::attach(|py| {
let builtins = PyModule::import(py, "builtins")?;
let locals = builtins.getattr("locals")?.call0()?;

Expand Down Expand Up @@ -1125,7 +1138,13 @@ mod mewnala {
}

Ok(())
})
});

// tear the app down here while the TLS is still alive; the eager
// TLS destructor aborts inside a Bevy resource drop
let _ = ::processing::exit(0);

result
}

#[pyfunction]
Expand Down Expand Up @@ -1248,21 +1267,6 @@ mod mewnala {
graphics!(module).draw_geometry(&*geometry.extract::<PyRef<Geometry>>()?)
}

#[pyfunction(name = "color")]
#[pyo3(pass_module, signature = (*args))]
fn create_color(
module: &Bound<'_, PyModule>,
args: &Bound<'_, PyTuple>,
) -> PyResult<super::color::PyColor> {
match get_graphics(module)? {
Some(g) => g.color(args),
None => {
let mode = super::color::ColorMode::default();
super::color::extract_color_with_mode(args, &mode).map(super::color::PyColor::from)
}
}
}

#[pyfunction]
#[pyo3(pass_module, signature = (*args))]
fn background(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> {
Expand Down Expand Up @@ -1370,46 +1374,75 @@ mod mewnala {
graphics.create_image(width, height)
}

fn apply_light_transform(
light: &Light,
position: Option<super::math::Vec3Like>,
look_at: Option<super::math::Vec3Like>,
) -> PyResult<()> {
if let Some(p) = position {
::processing::prelude::transform_set_position(light.entity, p.into_vec3())
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
}
if let Some(la) = look_at {
::processing::prelude::transform_look_at(light.entity, la.into_vec3())
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
}
Ok(())
}

#[pyfunction]
#[pyo3(pass_module)]
fn create_directional_light(
#[pyo3(pass_module, signature = (color, illuminance, *, position=None, look_at=None))]
fn directional_light(
module: &Bound<'_, PyModule>,
color: super::color::ColorLike,
illuminance: f32,
position: Option<super::math::Vec3Like>,
look_at: Option<super::math::Vec3Like>,
) -> PyResult<Light> {
let graphics =
get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?;
graphics.light_directional(color, illuminance)
let light = graphics.light_directional(color, illuminance)?;
apply_light_transform(&light, position, look_at)?;
Ok(light)
}

#[pyfunction]
#[pyo3(pass_module)]
fn create_point_light(
#[pyo3(pass_module, signature = (color, intensity, range, radius, *, position=None, look_at=None))]
fn point_light(
module: &Bound<'_, PyModule>,
color: super::color::ColorLike,
intensity: f32,
range: f32,
radius: f32,
position: Option<super::math::Vec3Like>,
look_at: Option<super::math::Vec3Like>,
) -> PyResult<Light> {
let graphics =
get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?;
graphics.light_point(color, intensity, range, radius)
let light = graphics.light_point(color, intensity, range, radius)?;
apply_light_transform(&light, position, look_at)?;
Ok(light)
}

#[pyfunction]
#[pyo3(pass_module)]
fn create_spot_light(
#[pyo3(pass_module, signature = (color, intensity, range, radius, inner_angle, outer_angle, *, position=None, look_at=None))]
fn spot_light(
module: &Bound<'_, PyModule>,
color: super::color::ColorLike,
intensity: f32,
range: f32,
radius: f32,
inner_angle: f32,
outer_angle: f32,
position: Option<super::math::Vec3Like>,
look_at: Option<super::math::Vec3Like>,
) -> PyResult<Light> {
let graphics =
get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?;
graphics.light_spot(color, intensity, range, radius, inner_angle, outer_angle)
let light =
graphics.light_spot(color, intensity, range, radius, inner_angle, outer_angle)?;
apply_light_transform(&light, position, look_at)?;
Ok(light)
}

#[pyfunction(name = "sphere")]
Expand Down
18 changes: 18 additions & 0 deletions crates/processing_pyo3/src/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,11 @@ macro_rules! impl_py_vec {
}

impl_py_vec!(PyVec2, "Vec2", 2, [(x, set_x, 0), (y, set_y, 1)], Vec2, extra {
#[classattr] #[allow(non_snake_case)] fn ZERO() -> Self { Self(Vec2::ZERO) }
#[classattr] #[allow(non_snake_case)] fn ONE() -> Self { Self(Vec2::ONE) }
#[classattr] #[allow(non_snake_case)] fn X() -> Self { Self(Vec2::X) }
#[classattr] #[allow(non_snake_case)] fn Y() -> Self { Self(Vec2::Y) }

fn angle(&self) -> f32 {
self.0.y.atan2(self.0.x)
}
Expand Down Expand Up @@ -543,6 +548,12 @@ impl_py_vec!(PyVec2, "Vec2", 2, [(x, set_x, 0), (y, set_y, 1)], Vec2, extra {
});

impl_py_vec!(PyVec3, "Vec3", 3, [(x, set_x, 0), (y, set_y, 1), (z, set_z, 2)], Vec3, extra {
#[classattr] #[allow(non_snake_case)] fn ZERO() -> Self { Self(Vec3::ZERO) }
#[classattr] #[allow(non_snake_case)] fn ONE() -> Self { Self(Vec3::ONE) }
#[classattr] #[allow(non_snake_case)] fn X() -> Self { Self(Vec3::X) }
#[classattr] #[allow(non_snake_case)] fn Y() -> Self { Self(Vec3::Y) }
#[classattr] #[allow(non_snake_case)] fn Z() -> Self { Self(Vec3::Z) }

fn cross(&self, other: &Self) -> Self {
Self(self.0.cross(other.0))
}
Expand Down Expand Up @@ -570,6 +581,13 @@ impl_py_vec!(
[(x, set_x, 0), (y, set_y, 1), (z, set_z, 2), (w, set_w, 3)],
Vec4,
extra {
#[classattr] #[allow(non_snake_case)] fn ZERO() -> Self { Self(Vec4::ZERO) }
#[classattr] #[allow(non_snake_case)] fn ONE() -> Self { Self(Vec4::ONE) }
#[classattr] #[allow(non_snake_case)] fn X() -> Self { Self(Vec4::X) }
#[classattr] #[allow(non_snake_case)] fn Y() -> Self { Self(Vec4::Y) }
#[classattr] #[allow(non_snake_case)] fn Z() -> Self { Self(Vec4::Z) }
#[classattr] #[allow(non_snake_case)] fn W() -> Self { Self(Vec4::W) }

fn truncate(&self) -> PyVec3 {
PyVec3(self.0.truncate())
}
Expand Down
9 changes: 5 additions & 4 deletions crates/processing_render/src/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,11 +303,12 @@ pub fn mode_3d(
let fov = std::f32::consts::PI / 3.0; // 60 degrees
let aspect = width / height;
let camera_z = (height / 2.0) / (fov / 2.0).tan();
let near = camera_z / 10.0;
// Processing4 uses near = camera_z / 10, but that clips anything closer
// than ~camera_z/10 to the camera. Since `transform_set_position` lets the
// user move the camera without recomputing the projection, a small fixed
// near is safer and matches most engines' defaults.
let near = 1.0;
let far = camera_z * 10.0;

// TODO: Setting this as a default, but we need to think about API around
// a user defined value
let near_clip_plane = vec4(0.0, 0.0, -1.0, -near);

let mut projection = projections
Expand Down
Loading