Skip to content
Draft
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
2 changes: 1 addition & 1 deletion crates/processing_ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1963,7 +1963,7 @@ pub extern "C" fn processing_perspective(
graphics_perspective(
graphics_entity,
fov,
aspect,
Some(aspect),
near,
far,
bevy::math::Vec4::new(0.0, 0.0, -1.0, -near),
Expand Down
2 changes: 1 addition & 1 deletion crates/processing_node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1345,7 +1345,7 @@ pub fn js_perspective(
check(graphics_perspective(
ent(graphics_id),
fov as f32,
aspect as f32,
Some(aspect as f32),
near as f32,
far as f32,
Vec4::new(0.0, 0.0, -1.0, -near as f32),
Expand Down
76 changes: 65 additions & 11 deletions crates/processing_pyo3/src/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,11 @@ impl Drop for Graphics {
}

impl Graphics {
/// The `mode_3d()` view at the canvas's current size.
fn view_defaults(&self) -> PyResult<processing_render::graphics::ViewDefaults> {
graphics_view_defaults(self.entity).map_err(rt_err)
}

pub(crate) fn current_color_mode(&self) -> PyResult<ColorMode> {
graphics_get_color_mode(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}
Expand Down Expand Up @@ -2607,30 +2612,79 @@ impl Graphics {
transform_look_at(self.entity, v).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}

pub fn perspective(&self, fov: f32, aspect: f32, near: f32, far: f32) -> PyResult<()> {
/// `camera()` restores the default `mode_3d()` view. `camera(eye, center, up)`
/// takes three vectors, or nine numbers as in Processing.
#[pyo3(signature = (*args))]
pub fn camera(&self, args: &Bound<'_, PyTuple>) -> PyResult<()> {
let (eye, center, up) = match args.len() {
0 => (
Vec3::new(0.0, 0.0, self.view_defaults()?.camera_z),
Vec3::ZERO,
Vec3::Y,
),
3 | 9 => {
let n = args.len() / 3;
let part = |i: usize| extract_vec3(&args.get_slice(i * n, (i + 1) * n));
(part(0)?, part(1)?, part(2)?)
}
n => {
return Err(pyo3::exceptions::PyTypeError::new_err(format!(
"camera() takes 0, 3 or 9 arguments ({n} given)"
)));
}
};
graphics_camera(self.entity, eye, center, up)
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}

/// Arguments left out take the `mode_3d()` defaults: 60 degree `fov`, the
/// canvas aspect, `near` 1 and `far` ten times the camera distance.
#[pyo3(signature = (fov=None, aspect=None, near=None, far=None))]
pub fn perspective(
&self,
fov: Option<f32>,
aspect: Option<f32>,
near: Option<f32>,
far: Option<f32>,
) -> PyResult<()> {
let defaults = self.view_defaults()?;
let near = near.unwrap_or(defaults.near);
graphics_perspective(
self.entity,
fov,
fov.unwrap_or(defaults.fov),
aspect,
near,
far,
far.unwrap_or(defaults.far),
Vec4::new(0.0, 0.0, -1.0, -near),
)
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}

/// Defaults to the canvas bounds, `near` 0 and `far` ten times the camera
/// distance, as Processing's `ortho()`.
#[allow(clippy::too_many_arguments)]
#[pyo3(signature = (left=None, right=None, bottom=None, top=None, near=None, far=None))]
pub fn ortho(
&self,
left: f32,
right: f32,
bottom: f32,
top: f32,
near: f32,
far: f32,
left: Option<f32>,
right: Option<f32>,
bottom: Option<f32>,
top: Option<f32>,
near: Option<f32>,
far: Option<f32>,
) -> PyResult<()> {
graphics_ortho(self.entity, left, right, bottom, top, near, far)
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
let defaults = self.view_defaults()?;
let (half_w, half_h) = (defaults.width / 2.0, defaults.height / 2.0);
graphics_ortho(
self.entity,
left.unwrap_or(-half_w),
right.unwrap_or(half_w),
bottom.unwrap_or(-half_h),
top.unwrap_or(half_h),
near.unwrap_or(0.0),
far.unwrap_or(defaults.far),
)
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}

pub fn orbit_camera(&self) -> PyResult<()> {
Expand Down
33 changes: 33 additions & 0 deletions crates/processing_pyo3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1765,6 +1765,39 @@ pub mod processing_py {
graphics!(module).blend_mode(&*mode.extract::<PyRef<PyBlendMode>>()?)
}

#[pyfunction]
#[pyo3(pass_module, signature = (*args))]
fn camera(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> {
graphics!(module).camera(args)
}

#[pyfunction]
#[pyo3(pass_module, signature = (fov=None, aspect=None, near=None, far=None))]
fn perspective(
module: &Bound<'_, PyModule>,
fov: Option<f32>,
aspect: Option<f32>,
near: Option<f32>,
far: Option<f32>,
) -> PyResult<()> {
graphics!(module).perspective(fov, aspect, near, far)
}

#[allow(clippy::too_many_arguments)]
#[pyfunction]
#[pyo3(pass_module, signature = (left=None, right=None, bottom=None, top=None, near=None, far=None))]
fn ortho(
module: &Bound<'_, PyModule>,
left: Option<f32>,
right: Option<f32>,
bottom: Option<f32>,
top: Option<f32>,
near: Option<f32>,
far: Option<f32>,
) -> PyResult<()> {
graphics!(module).ortho(left, right, bottom, top, near, far)
}

#[pyfunction]
#[pyo3(pass_module, signature = (intensity=processing_render::graphics::DEFAULT_BLOOM_INTENSITY, threshold=0.0))]
fn bloom(module: &Bound<'_, PyModule>, intensity: f32, threshold: f32) -> PyResult<()> {
Expand Down
2 changes: 1 addition & 1 deletion crates/processing_render/src/gltf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ pub fn camera(In((gltf_entity, index)): In<(Entity, usize)>, world: &mut World)
match projection {
Projection::Perspective(p) => {
world
.run_system_cached_with(graphics::perspective, (graphics_entity, p))
.run_system_cached_with(graphics::perspective, (graphics_entity, p, false))
.unwrap()?;
}
Projection::Orthographic(o) => {
Expand Down
68 changes: 51 additions & 17 deletions crates/processing_render/src/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,47 @@ pub fn sync_to_surface(
}
}

/// The view `mode_3d()` sets up for a canvas, which `camera()`, `perspective()` and `ortho()`
/// fall back to for arguments left out.
#[derive(Debug, Clone, Copy)]
pub struct ViewDefaults {
pub width: f32,
pub height: f32,
pub fov: f32,
pub aspect: f32,
pub near: f32,
pub far: f32,
/// Distance at which the canvas fills the view.
pub camera_z: f32,
}

impl ViewDefaults {
pub fn new(width: f32, height: f32) -> Self {
let fov = std::f32::consts::PI / 3.0; // 60 degrees
let camera_z = (height / 2.0) / (fov / 2.0).tan();
Self {
width,
height,
fov,
aspect: width / height,
// 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.
near: 1.0,
far: camera_z * 10.0,
camera_z,
}
}
}

pub fn view_defaults(In(entity): In<Entity>, sizes: Query<&SurfaceSize>) -> Result<ViewDefaults> {
let SurfaceSize(width, height) = sizes
.get(entity)
.map_err(|_| ProcessingError::GraphicsNotFound)?;
Ok(ViewDefaults::new(*width as f32, *height as f32))
}

pub fn mode_3d(
In(entity): In<Entity>,
mut projections: Query<&mut Projection>,
Expand All @@ -361,18 +402,14 @@ pub fn mode_3d(
.get(entity)
.map_err(|_| ProcessingError::GraphicsNotFound)?;

let width = *width as f32;
let height = *height as f32;

let fov = std::f32::consts::PI / 3.0; // 60 degrees
let aspect = width / height;
let camera_z = (height / 2.0) / (fov / 2.0).tan();
// 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;
let ViewDefaults {
fov,
aspect,
near,
far,
camera_z,
..
} = ViewDefaults::new(*width as f32, *height as f32);
let near_clip_plane = vec4(0.0, 0.0, -1.0, -near);

let mut projection = projections
Expand Down Expand Up @@ -427,17 +464,14 @@ pub fn mode_2d(
}

pub fn perspective(
In((entity, inner)): In<(Entity, PerspectiveProjection)>,
In((entity, inner, fit_canvas)): In<(Entity, PerspectiveProjection, bool)>,
mut projections: Query<&mut Projection>,
) -> Result<()> {
let mut projection = projections
.get_mut(entity)
.map_err(|_| ProcessingError::GraphicsNotFound)?;

*projection = Projection::custom(ProcessingPerspective {
inner,
fit_canvas: false,
});
*projection = Projection::custom(ProcessingPerspective { inner, fit_canvas });

Ok(())
}
Expand Down
22 changes: 20 additions & 2 deletions crates/processing_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -864,28 +864,44 @@ pub fn camera_reset(entity: Entity) -> error::Result<()> {
})
}

/// The `mode_3d()` view of this graphics at its current size.
pub fn graphics_view_defaults(graphics_entity: Entity) -> error::Result<graphics::ViewDefaults> {
app_mut(|app| {
app.world_mut()
.run_system_cached_with(graphics::view_defaults, graphics_entity)
.unwrap()
})
}

/// `aspect_ratio: None` follows the canvas, including later resizes.
pub fn graphics_perspective(
graphics_entity: Entity,
fov: f32,
aspect_ratio: f32,
aspect_ratio: Option<f32>,
near: f32,
far: f32,
near_clip_plane: Vec4,
) -> error::Result<()> {
app_mut(|app| {
flush(app, graphics_entity)?;
let canvas_aspect = app
.world_mut()
.run_system_cached_with(graphics::view_defaults, graphics_entity)
.unwrap()?
.aspect;
app.world_mut()
.run_system_cached_with(
graphics::perspective,
(
graphics_entity,
PerspectiveProjection {
fov,
aspect_ratio,
aspect_ratio: aspect_ratio.unwrap_or(canvas_aspect),
near,
far,
near_clip_plane,
},
aspect_ratio.is_none(),
),
)
.unwrap()
Expand Down Expand Up @@ -1060,6 +1076,8 @@ pub fn transform_look_at(entity: Entity, target: Vec3) -> error::Result<()> {
/// Position the camera at `eye`, looking at `center` with the given `up`.
pub fn graphics_camera(entity: Entity, eye: Vec3, center: Vec3, up: Vec3) -> error::Result<()> {
app_mut(|app| {
// draws queued so far keep the view they were issued under
flush(app, entity)?;
app.world_mut()
.run_system_cached_with(transform::camera, (entity, eye, center, up))
.unwrap()
Expand Down
2 changes: 1 addition & 1 deletion crates/processing_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,7 @@ pub fn js_perspective(
check(graphics_perspective(
graphics_entity,
fov,
aspect,
Some(aspect),
near,
far,
bevy::math::Vec4::new(0.0, 0.0, -1.0, -near),
Expand Down
Loading