From 146b80f819ef3915936992ffcfad4503e8f1e709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 22 Dec 2025 23:23:59 -0800 Subject: [PATCH] Add camera functions. --- crates/processing_ffi/src/lib.rs | 61 +++++++++ crates/processing_pyo3/src/graphics.rs | 37 +++++ crates/processing_render/src/graphics.rs | 163 +++++++++++++++++++++++ crates/processing_render/src/lib.rs | 107 +++++++++++++++ 4 files changed, 368 insertions(+) diff --git a/crates/processing_ffi/src/lib.rs b/crates/processing_ffi/src/lib.rs index e236481..8234147 100644 --- a/crates/processing_ffi/src/lib.rs +++ b/crates/processing_ffi/src/lib.rs @@ -524,3 +524,64 @@ pub unsafe extern "C" fn processing_image_readback( Ok(()) }); } + +#[unsafe(no_mangle)] +pub extern "C" fn processing_mode_3d(window_id: u64) { + error::clear_error(); + let window_entity = Entity::from_bits(window_id); + error::check(|| graphics_mode_3d(window_entity)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_mode_2d(window_id: u64) { + error::clear_error(); + let window_entity = Entity::from_bits(window_id); + error::check(|| graphics_mode_2d(window_entity)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_camera_position(window_id: u64, x: f32, y: f32, z: f32) { + error::clear_error(); + let window_entity = Entity::from_bits(window_id); + error::check(|| graphics_camera_position(window_entity, x, y, z)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_camera_look_at( + window_id: u64, + target_x: f32, + target_y: f32, + target_z: f32, +) { + error::clear_error(); + let window_entity = Entity::from_bits(window_id); + error::check(|| graphics_camera_look_at(window_entity, target_x, target_y, target_z)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_perspective( + window_id: u64, + fov: f32, + aspect: f32, + near: f32, + far: f32, +) { + error::clear_error(); + let window_entity = Entity::from_bits(window_id); + error::check(|| graphics_perspective(window_entity, fov, aspect, near, far)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_ortho( + window_id: u64, + left: f32, + right: f32, + bottom: f32, + top: f32, + near: f32, + far: f32, +) { + error::clear_error(); + let window_entity = Entity::from_bits(window_id); + error::check(|| graphics_ortho(window_entity, left, right, bottom, top, near, far)); +} diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index 070df8e..f777edc 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -169,6 +169,43 @@ impl Graphics { pub fn end_draw(&self) -> PyResult<()> { graphics_end_draw(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } + + pub fn mode_3d(&self) -> PyResult<()> { + graphics_mode_3d(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn mode_2d(&self) -> PyResult<()> { + graphics_mode_2d(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn camera_position(&self, x: f32, y: f32, z: f32) -> PyResult<()> { + graphics_camera_position(self.entity, x, y, z) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn camera_look_at(&self, target_x: f32, target_y: f32, target_z: f32) -> PyResult<()> { + graphics_camera_look_at(self.entity, target_x, target_y, target_z) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn perspective(&self, fov: f32, aspect: f32, near: f32, far: f32) -> PyResult<()> { + graphics_perspective(self.entity, fov, aspect, near, far) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + #[allow(clippy::too_many_arguments)] + pub fn ortho( + &self, + left: f32, + right: f32, + bottom: f32, + top: f32, + near: f32, + far: f32, + ) -> PyResult<()> { + graphics_ortho(self.entity, left, right, bottom, top, near, far) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } } // TODO: a real color type. or color parser? idk. color is confusing. let's think diff --git a/crates/processing_render/src/graphics.rs b/crates/processing_render/src/graphics.rs index 5afea47..d2c7314 100644 --- a/crates/processing_render/src/graphics.rs +++ b/crates/processing_render/src/graphics.rs @@ -275,6 +275,169 @@ pub fn resize( } } +pub fn mode_3d( + In(entity): In, + mut projections: Query<&mut Projection>, + mut transforms: Query<&mut Transform>, + sizes: Query<&SurfaceSize>, +) -> Result<()> { + let SurfaceSize(width, height) = sizes + .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(); + let near = camera_z / 10.0; + let far = camera_z * 10.0; + + let mut projection = projections + .get_mut(entity) + .map_err(|_| ProcessingError::GraphicsNotFound)?; + + *projection = Projection::Perspective(PerspectiveProjection { + fov, + aspect_ratio: aspect, + near, + far, + }); + + let mut transform = transforms + .get_mut(entity) + .map_err(|_| ProcessingError::GraphicsNotFound)?; + + *transform = Transform::from_xyz(0.0, 0.0, camera_z).looking_at(Vec3::ZERO, Vec3::Y); + + Ok(()) +} + +pub fn mode_2d( + In(entity): In, + mut projections: Query<&mut Projection>, + mut transforms: Query<&mut Transform>, + sizes: Query<&SurfaceSize>, +) -> Result<()> { + let SurfaceSize(width, height) = sizes + .get(entity) + .map_err(|_| ProcessingError::GraphicsNotFound)?; + + let mut projection = projections + .get_mut(entity) + .map_err(|_| ProcessingError::GraphicsNotFound)?; + + *projection = Projection::custom(ProcessingProjection { + width: *width as f32, + height: *height as f32, + near: 0.0, + far: 1000.0, + }); + + let mut transform = transforms + .get_mut(entity) + .map_err(|_| ProcessingError::GraphicsNotFound)?; + + *transform = Transform::from_xyz(0.0, 0.0, 999.9); + + Ok(()) +} + +pub fn camera_position( + In((entity, x, y, z)): In<(Entity, f32, f32, f32)>, + mut transforms: Query<&mut Transform>, +) -> Result<()> { + let mut transform = transforms + .get_mut(entity) + .map_err(|_| ProcessingError::GraphicsNotFound)?; + + transform.translation = Vec3::new(x, y, z); + + Ok(()) +} + +pub fn camera_look_at( + In((entity, target_x, target_y, target_z)): In<(Entity, f32, f32, f32)>, + mut transforms: Query<&mut Transform>, +) -> Result<()> { + let mut transform = transforms + .get_mut(entity) + .map_err(|_| ProcessingError::GraphicsNotFound)?; + + // TODO: allow specifying up vector? + // does anyone actually use anything other than Vec3::Y here? + let target = Vec3::new(target_x, target_y, target_z); + transform.look_at(target, Vec3::Y); + + Ok(()) +} + +pub fn perspective( + In(( + entity, + PerspectiveProjection { + fov, + aspect_ratio, + near, + far, + }, + )): In<(Entity, PerspectiveProjection)>, + mut projections: Query<&mut Projection>, +) -> Result<()> { + let mut projection = projections + .get_mut(entity) + .map_err(|_| ProcessingError::GraphicsNotFound)?; + + *projection = Projection::Perspective(PerspectiveProjection { + fov, + aspect_ratio, + near, + far, + }); + + Ok(()) +} + +pub struct OrthoArgs { + pub left: f32, + pub right: f32, + pub bottom: f32, + pub top: f32, + pub near: f32, + pub far: f32, +} + +pub fn ortho( + In(( + entity, + OrthoArgs { + left, + right, + bottom, + top, + near, + far, + }, + )): In<(Entity, OrthoArgs)>, + mut projections: Query<&mut Projection>, +) -> Result<()> { + let mut projection = projections + .get_mut(entity) + .map_err(|_| ProcessingError::GraphicsNotFound)?; + + // we need a custom projection to support processing's coordinate system + // but this is in effect an orthographic projection with the given bounds + *projection = Projection::custom(ProcessingProjection { + width: right - left, + height: top - bottom, + near, + far, + }); + + Ok(()) +} + pub fn destroy( In(entity): In, mut commands: Commands, diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index 8db84a3..2beef68 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -17,6 +17,7 @@ use bevy::{ use render::{activate_cameras, clear_transient_meshes, flush_draw_commands}; use tracing::debug; +use crate::graphics::flush; use crate::{ graphics::GraphicsPlugin, image::ImagePlugin, render::command::DrawCommand, surface::SurfacePlugin, @@ -445,6 +446,112 @@ pub fn graphics_record_command(graphics_entity: Entity, cmd: DrawCommand) -> err }) } +pub fn graphics_mode_3d(graphics_entity: Entity) -> error::Result<()> { + app_mut(|app| { + flush(app, graphics_entity)?; + app.world_mut() + .run_system_cached_with(graphics::mode_3d, graphics_entity) + .unwrap() + }) +} + +pub fn graphics_mode_2d(graphics_entity: Entity) -> error::Result<()> { + app_mut(|app| { + flush(app, graphics_entity)?; + app.world_mut() + .run_system_cached_with(graphics::mode_2d, graphics_entity) + .unwrap() + }) +} + +pub fn graphics_camera_position( + graphics_entity: Entity, + x: f32, + y: f32, + z: f32, +) -> error::Result<()> { + app_mut(|app| { + flush(app, graphics_entity)?; + app.world_mut() + .run_system_cached_with(graphics::camera_position, (graphics_entity, x, y, z)) + .unwrap() + }) +} + +pub fn graphics_camera_look_at( + graphics_entity: Entity, + target_x: f32, + target_y: f32, + target_z: f32, +) -> error::Result<()> { + app_mut(|app| { + flush(app, graphics_entity)?; + app.world_mut() + .run_system_cached_with( + graphics::camera_look_at, + (graphics_entity, target_x, target_y, target_z), + ) + .unwrap() + }) +} + +pub fn graphics_perspective( + graphics_entity: Entity, + fov: f32, + aspect_ratio: f32, + near: f32, + far: f32, +) -> error::Result<()> { + app_mut(|app| { + flush(app, graphics_entity)?; + app.world_mut() + .run_system_cached_with( + graphics::perspective, + ( + graphics_entity, + PerspectiveProjection { + fov, + aspect_ratio, + near, + far, + }, + ), + ) + .unwrap() + }) +} + +#[allow(clippy::too_many_arguments)] +pub fn graphics_ortho( + graphics_entity: Entity, + left: f32, + right: f32, + bottom: f32, + top: f32, + near: f32, + far: f32, +) -> error::Result<()> { + app_mut(|app| { + flush(app, graphics_entity)?; + app.world_mut() + .run_system_cached_with( + graphics::ortho, + ( + graphics_entity, + graphics::OrthoArgs { + left, + right, + bottom, + top, + near, + far, + }, + ), + ) + .unwrap() + }) +} + /// Create a new image with given size and data. pub fn image_create( size: Extent3d,