From 5edbafe25155bf4a7454b86a86308e7d3414bdba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 9 Feb 2026 22:46:02 -0800 Subject: [PATCH 1/2] Add generic transform systems. --- crates/processing_ffi/src/lib.rs | 96 +++++++++++++++- crates/processing_pyo3/src/graphics.rs | 4 +- crates/processing_render/src/error.rs | 2 + crates/processing_render/src/graphics.rs | 29 ----- crates/processing_render/src/lib.rs | 134 +++++++++++++++++----- crates/processing_render/src/transform.rs | 123 ++++++++++++++++++++ crates/processing_wasm/src/lib.rs | 91 ++++++++++++++- examples/animated_mesh.rs | 4 +- examples/box.rs | 4 +- examples/custom_attribute.rs | 4 +- 10 files changed, 418 insertions(+), 73 deletions(-) create mode 100644 crates/processing_render/src/transform.rs diff --git a/crates/processing_ffi/src/lib.rs b/crates/processing_ffi/src/lib.rs index b983f4e..dfe9303 100644 --- a/crates/processing_ffi/src/lib.rs +++ b/crates/processing_ffi/src/lib.rs @@ -561,8 +561,8 @@ pub extern "C" fn processing_mode_2d(graphics_id: u64) { #[unsafe(no_mangle)] pub extern "C" fn processing_camera_position(graphics_id: u64, x: f32, y: f32, z: f32) { error::clear_error(); - let graphics_entity = Entity::from_bits(graphics_id); - error::check(|| graphics_camera_position(graphics_entity, x, y, z)); + let entity = Entity::from_bits(graphics_id); + error::check(|| transform_set_position(entity, x, y, z)); } #[unsafe(no_mangle)] @@ -573,8 +573,8 @@ pub extern "C" fn processing_camera_look_at( target_z: f32, ) { error::clear_error(); - let graphics_entity = Entity::from_bits(graphics_id); - error::check(|| graphics_camera_look_at(graphics_entity, target_x, target_y, target_z)); + let entity = Entity::from_bits(graphics_id); + error::check(|| transform_look_at(entity, target_x, target_y, target_z)); } #[unsafe(no_mangle)] @@ -605,6 +605,94 @@ pub extern "C" fn processing_ortho( error::check(|| graphics_ortho(graphics_entity, left, right, bottom, top, near, far)); } +#[unsafe(no_mangle)] +pub extern "C" fn processing_transform_set_position(entity_id: u64, x: f32, y: f32, z: f32) { + error::clear_error(); + let entity = Entity::from_bits(entity_id); + error::check(|| transform_set_position(entity, x, y, z)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_transform_translate(entity_id: u64, x: f32, y: f32, z: f32) { + error::clear_error(); + let entity = Entity::from_bits(entity_id); + error::check(|| transform_translate(entity, x, y, z)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_transform_set_rotation(entity_id: u64, x: f32, y: f32, z: f32) { + error::clear_error(); + let entity = Entity::from_bits(entity_id); + error::check(|| transform_set_rotation(entity, x, y, z)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_transform_rotate_x(entity_id: u64, angle: f32) { + error::clear_error(); + let entity = Entity::from_bits(entity_id); + error::check(|| transform_rotate_x(entity, angle)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_transform_rotate_y(entity_id: u64, angle: f32) { + error::clear_error(); + let entity = Entity::from_bits(entity_id); + error::check(|| transform_rotate_y(entity, angle)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_transform_rotate_z(entity_id: u64, angle: f32) { + error::clear_error(); + let entity = Entity::from_bits(entity_id); + error::check(|| transform_rotate_z(entity, angle)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_transform_rotate_axis( + entity_id: u64, + angle: f32, + axis_x: f32, + axis_y: f32, + axis_z: f32, +) { + error::clear_error(); + let entity = Entity::from_bits(entity_id); + error::check(|| transform_rotate_axis(entity, angle, axis_x, axis_y, axis_z)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_transform_set_scale(entity_id: u64, x: f32, y: f32, z: f32) { + error::clear_error(); + let entity = Entity::from_bits(entity_id); + error::check(|| transform_set_scale(entity, x, y, z)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_transform_scale(entity_id: u64, x: f32, y: f32, z: f32) { + error::clear_error(); + let entity = Entity::from_bits(entity_id); + error::check(|| transform_scale(entity, x, y, z)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_transform_look_at( + entity_id: u64, + target_x: f32, + target_y: f32, + target_z: f32, +) { + error::clear_error(); + let entity = Entity::from_bits(entity_id); + error::check(|| transform_look_at(entity, target_x, target_y, target_z)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_transform_reset(entity_id: u64) { + error::clear_error(); + let entity = Entity::from_bits(entity_id); + error::check(|| transform_reset(entity)); +} + pub const PROCESSING_ATTR_FORMAT_FLOAT: u8 = 1; pub const PROCESSING_ATTR_FORMAT_FLOAT2: u8 = 2; pub const PROCESSING_ATTR_FORMAT_FLOAT3: u8 = 3; diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index 5f7be05..95bef31 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -281,12 +281,12 @@ impl Graphics { } pub fn camera_position(&self, x: f32, y: f32, z: f32) -> PyResult<()> { - graphics_camera_position(self.entity, x, y, z) + transform_set_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) + transform_look_at(self.entity, target_x, target_y, target_z) .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } diff --git a/crates/processing_render/src/error.rs b/crates/processing_render/src/error.rs index cac70eb..aca2f20 100644 --- a/crates/processing_render/src/error.rs +++ b/crates/processing_render/src/error.rs @@ -28,4 +28,6 @@ pub enum ProcessingError { GeometryNotFound, #[error("Layout not found")] LayoutNotFound, + #[error("Transform not found")] + TransformNotFound, } diff --git a/crates/processing_render/src/graphics.rs b/crates/processing_render/src/graphics.rs index d2c7314..b6b5b80 100644 --- a/crates/processing_render/src/graphics.rs +++ b/crates/processing_render/src/graphics.rs @@ -344,35 +344,6 @@ pub fn mode_2d( 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, diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index 6f60575..3b37229 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -5,6 +5,7 @@ mod graphics; pub mod image; pub mod render; mod surface; +pub mod transform; use std::{cell::RefCell, num::NonZero, path::PathBuf, sync::OnceLock}; @@ -483,37 +484,6 @@ pub fn graphics_mode_2d(graphics_entity: Entity) -> error::Result<()> { }) } -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, @@ -571,6 +541,108 @@ pub fn graphics_ortho( }) } +pub fn transform_set_position(entity: Entity, x: f32, y: f32, z: f32) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(transform::set_position, (entity, x, y, z)) + .unwrap() + }) +} + +pub fn transform_translate(entity: Entity, x: f32, y: f32, z: f32) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(transform::translate, (entity, x, y, z)) + .unwrap() + }) +} + +pub fn transform_set_rotation(entity: Entity, x: f32, y: f32, z: f32) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(transform::set_rotation, (entity, x, y, z)) + .unwrap() + }) +} + +pub fn transform_rotate_x(entity: Entity, angle: f32) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(transform::rotate_x, (entity, angle)) + .unwrap() + }) +} + +pub fn transform_rotate_y(entity: Entity, angle: f32) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(transform::rotate_y, (entity, angle)) + .unwrap() + }) +} + +pub fn transform_rotate_z(entity: Entity, angle: f32) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(transform::rotate_z, (entity, angle)) + .unwrap() + }) +} + +pub fn transform_rotate_axis( + entity: Entity, + angle: f32, + axis_x: f32, + axis_y: f32, + axis_z: f32, +) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with( + transform::rotate_axis, + (entity, angle, axis_x, axis_y, axis_z), + ) + .unwrap() + }) +} + +pub fn transform_set_scale(entity: Entity, x: f32, y: f32, z: f32) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(transform::set_scale, (entity, x, y, z)) + .unwrap() + }) +} + +pub fn transform_scale(entity: Entity, x: f32, y: f32, z: f32) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(transform::scale, (entity, x, y, z)) + .unwrap() + }) +} + +pub fn transform_look_at( + entity: Entity, + target_x: f32, + target_y: f32, + target_z: f32, +) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(transform::look_at, (entity, target_x, target_y, target_z)) + .unwrap() + }) +} + +pub fn transform_reset(entity: Entity) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(transform::reset, entity) + .unwrap() + }) +} + /// Create a new image with given size and data. pub fn image_create( size: Extent3d, diff --git a/crates/processing_render/src/transform.rs b/crates/processing_render/src/transform.rs new file mode 100644 index 0000000..9209c77 --- /dev/null +++ b/crates/processing_render/src/transform.rs @@ -0,0 +1,123 @@ +use bevy::prelude::*; + +use crate::error::{ProcessingError, Result}; + +pub fn set_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::TransformNotFound)?; + transform.translation = Vec3::new(x, y, z); + Ok(()) +} + +pub fn translate( + 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::TransformNotFound)?; + transform.translation += Vec3::new(x, y, z); + Ok(()) +} + +pub fn set_rotation( + 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::TransformNotFound)?; + transform.rotation = Quat::from_euler(EulerRot::XYZ, x, y, z); + Ok(()) +} + +pub fn rotate_x( + In((entity, angle)): In<(Entity, f32)>, + mut transforms: Query<&mut Transform>, +) -> Result<()> { + let mut transform = transforms + .get_mut(entity) + .map_err(|_| ProcessingError::TransformNotFound)?; + transform.rotate_x(angle); + Ok(()) +} + +pub fn rotate_y( + In((entity, angle)): In<(Entity, f32)>, + mut transforms: Query<&mut Transform>, +) -> Result<()> { + let mut transform = transforms + .get_mut(entity) + .map_err(|_| ProcessingError::TransformNotFound)?; + transform.rotate_y(angle); + Ok(()) +} + +pub fn rotate_z( + In((entity, angle)): In<(Entity, f32)>, + mut transforms: Query<&mut Transform>, +) -> Result<()> { + let mut transform = transforms + .get_mut(entity) + .map_err(|_| ProcessingError::TransformNotFound)?; + transform.rotate_z(angle); + Ok(()) +} + +pub fn rotate_axis( + In((entity, angle, axis_x, axis_y, axis_z)): In<(Entity, f32, f32, f32, f32)>, + mut transforms: Query<&mut Transform>, +) -> Result<()> { + let mut transform = transforms + .get_mut(entity) + .map_err(|_| ProcessingError::TransformNotFound)?; + let axis = Vec3::new(axis_x, axis_y, axis_z).normalize(); + transform.rotate(Quat::from_axis_angle(axis, angle)); + Ok(()) +} + +pub fn set_scale( + 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::TransformNotFound)?; + transform.scale = Vec3::new(x, y, z); + Ok(()) +} + +pub fn scale( + 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::TransformNotFound)?; + transform.scale *= Vec3::new(x, y, z); + Ok(()) +} + +pub fn 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::TransformNotFound)?; + let target = Vec3::new(target_x, target_y, target_z); + transform.look_at(target, Vec3::Y); + Ok(()) +} + +pub fn reset(In(entity): In, mut transforms: Query<&mut Transform>) -> Result<()> { + let mut transform = transforms + .get_mut(entity) + .map_err(|_| ProcessingError::TransformNotFound)?; + *transform = Transform::IDENTITY; + Ok(()) +} diff --git a/crates/processing_wasm/src/lib.rs b/crates/processing_wasm/src/lib.rs index 53c3bda..8f269ac 100644 --- a/crates/processing_wasm/src/lib.rs +++ b/crates/processing_wasm/src/lib.rs @@ -5,7 +5,9 @@ use processing_render::{ config::Config, exit, graphics_begin_draw, graphics_end_draw, graphics_flush, graphics_record_command, image_create, image_destroy, image_load, image_readback, image_resize, init, render::command::DrawCommand, surface_create_from_canvas, surface_destroy, - surface_resize, + surface_resize, transform_look_at, transform_reset, transform_rotate_axis, transform_rotate_x, + transform_rotate_y, transform_rotate_z, transform_scale, transform_set_position, + transform_set_rotation, transform_set_scale, transform_translate, }; use wasm_bindgen::prelude::*; @@ -253,3 +255,90 @@ pub fn js_image_readback(image_id: u64) -> Result, JsValue> { pub fn js_image_destroy(image_id: u64) -> Result<(), JsValue> { check(image_destroy(Entity::from_bits(image_id))) } + +#[wasm_bindgen(js_name = "transformSetPosition")] +pub fn js_transform_set_position(entity_id: u64, x: f32, y: f32, z: f32) -> Result<(), JsValue> { + check(transform_set_position( + Entity::from_bits(entity_id), + x, + y, + z, + )) +} + +#[wasm_bindgen(js_name = "transformTranslate")] +pub fn js_transform_translate(entity_id: u64, x: f32, y: f32, z: f32) -> Result<(), JsValue> { + check(transform_translate(Entity::from_bits(entity_id), x, y, z)) +} + +#[wasm_bindgen(js_name = "transformSetRotation")] +pub fn js_transform_set_rotation(entity_id: u64, x: f32, y: f32, z: f32) -> Result<(), JsValue> { + check(transform_set_rotation( + Entity::from_bits(entity_id), + x, + y, + z, + )) +} + +#[wasm_bindgen(js_name = "transformRotateX")] +pub fn js_transform_rotate_x(entity_id: u64, angle: f32) -> Result<(), JsValue> { + check(transform_rotate_x(Entity::from_bits(entity_id), angle)) +} + +#[wasm_bindgen(js_name = "transformRotateY")] +pub fn js_transform_rotate_y(entity_id: u64, angle: f32) -> Result<(), JsValue> { + check(transform_rotate_y(Entity::from_bits(entity_id), angle)) +} + +#[wasm_bindgen(js_name = "transformRotateZ")] +pub fn js_transform_rotate_z(entity_id: u64, angle: f32) -> Result<(), JsValue> { + check(transform_rotate_z(Entity::from_bits(entity_id), angle)) +} + +#[wasm_bindgen(js_name = "transformRotateAxis")] +pub fn js_transform_rotate_axis( + entity_id: u64, + angle: f32, + axis_x: f32, + axis_y: f32, + axis_z: f32, +) -> Result<(), JsValue> { + check(transform_rotate_axis( + Entity::from_bits(entity_id), + angle, + axis_x, + axis_y, + axis_z, + )) +} + +#[wasm_bindgen(js_name = "transformSetScale")] +pub fn js_transform_set_scale(entity_id: u64, x: f32, y: f32, z: f32) -> Result<(), JsValue> { + check(transform_set_scale(Entity::from_bits(entity_id), x, y, z)) +} + +#[wasm_bindgen(js_name = "transformScale")] +pub fn js_transform_scale(entity_id: u64, x: f32, y: f32, z: f32) -> Result<(), JsValue> { + check(transform_scale(Entity::from_bits(entity_id), x, y, z)) +} + +#[wasm_bindgen(js_name = "transformLookAt")] +pub fn js_transform_look_at( + entity_id: u64, + target_x: f32, + target_y: f32, + target_z: f32, +) -> Result<(), JsValue> { + check(transform_look_at( + Entity::from_bits(entity_id), + target_x, + target_y, + target_z, + )) +} + +#[wasm_bindgen(js_name = "transformReset")] +pub fn js_transform_reset(entity_id: u64) -> Result<(), JsValue> { + check(transform_reset(Entity::from_bits(entity_id))) +} diff --git a/examples/animated_mesh.rs b/examples/animated_mesh.rs index 4245229..0b7b4bb 100644 --- a/examples/animated_mesh.rs +++ b/examples/animated_mesh.rs @@ -69,8 +69,8 @@ fn sketch() -> error::Result<()> { } graphics_mode_3d(graphics)?; - graphics_camera_position(graphics, 150.0, 150.0, 150.0)?; - graphics_camera_look_at(graphics, 0.0, 0.0, 0.0)?; + transform_set_position(graphics, 150.0, 150.0, 150.0)?; + transform_look_at(graphics, 0.0, 0.0, 0.0)?; let mut time = 0.0f32; diff --git a/examples/box.rs b/examples/box.rs index c1a79fb..bf9b75a 100644 --- a/examples/box.rs +++ b/examples/box.rs @@ -30,8 +30,8 @@ fn sketch() -> error::Result<()> { let box_geo = geometry_box(100.0, 100.0, 100.0)?; graphics_mode_3d(graphics)?; - graphics_camera_position(graphics, 100.0, 100.0, 300.0)?; - graphics_camera_look_at(graphics, 0.0, 0.0, 0.0)?; + transform_set_position(graphics, 100.0, 100.0, 300.0)?; + transform_look_at(graphics, 0.0, 0.0, 0.0)?; let mut angle = 0.0; diff --git a/examples/custom_attribute.rs b/examples/custom_attribute.rs index ccdf412..51cb55b 100644 --- a/examples/custom_attribute.rs +++ b/examples/custom_attribute.rs @@ -58,8 +58,8 @@ fn sketch() -> error::Result<()> { geometry_index(mesh, 0)?; graphics_mode_3d(graphics)?; - graphics_camera_position(graphics, 0.0, 0.0, 200.0)?; - graphics_camera_look_at(graphics, 0.0, 0.0, 0.0)?; + transform_set_position(graphics, 0.0, 0.0, 200.0)?; + transform_look_at(graphics, 0.0, 0.0, 0.0)?; while glfw_ctx.poll_events() { graphics_begin_draw(graphics)?; From 314439e221690a7f81a0a4175cffd771d6f05aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 9 Feb 2026 22:47:58 -0800 Subject: [PATCH 2/2] Remove old ffi. --- crates/processing_ffi/src/lib.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/crates/processing_ffi/src/lib.rs b/crates/processing_ffi/src/lib.rs index dfe9303..cebbef7 100644 --- a/crates/processing_ffi/src/lib.rs +++ b/crates/processing_ffi/src/lib.rs @@ -558,25 +558,6 @@ pub extern "C" fn processing_mode_2d(graphics_id: u64) { error::check(|| graphics_mode_2d(graphics_entity)); } -#[unsafe(no_mangle)] -pub extern "C" fn processing_camera_position(graphics_id: u64, x: f32, y: f32, z: f32) { - error::clear_error(); - let entity = Entity::from_bits(graphics_id); - error::check(|| transform_set_position(entity, x, y, z)); -} - -#[unsafe(no_mangle)] -pub extern "C" fn processing_camera_look_at( - graphics_id: u64, - target_x: f32, - target_y: f32, - target_z: f32, -) { - error::clear_error(); - let entity = Entity::from_bits(graphics_id); - error::check(|| transform_look_at(entity, target_x, target_y, target_z)); -} - #[unsafe(no_mangle)] pub extern "C" fn processing_perspective( graphics_id: u64,