From 88d0db7156c8d95608b2168cfc5bce868a7cfa40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Fri, 25 Sep 2026 21:23:06 -0700 Subject: [PATCH] Add texture commands and image fixes. --- crates/processing_pyo3/src/graphics.rs | 15 +++++++++++++ crates/processing_pyo3/src/lib.rs | 21 +++++++++++++++++++ crates/processing_pyo3/src/webcam.rs | 14 ++++++------- crates/processing_render/src/image.rs | 8 +++++++ .../processing_render/src/render/command.rs | 4 ++++ crates/processing_render/src/render/mod.rs | 19 +++++++++++++++++ crates/processing_webcam/src/lib.rs | 5 +++++ 7 files changed, 78 insertions(+), 8 deletions(-) diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index 3e0447f6..2b808c99 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -2169,6 +2169,21 @@ impl Graphics { .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } + pub fn texture(&self, source: ImageRef) -> PyResult<()> { + graphics_record_command(self.entity, DrawCommand::Texture(source.entity)) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn no_texture(&self) -> PyResult<()> { + graphics_record_command(self.entity, DrawCommand::NoTexture) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn texture_transform(&self, transform: crate::math::PyAffine2) -> PyResult<()> { + graphics_record_command(self.entity, DrawCommand::TextureTransform(transform.0)) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + pub fn unlit(&self) -> PyResult<()> { graphics_record_command(self.entity, DrawCommand::Unlit) .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) diff --git a/crates/processing_pyo3/src/lib.rs b/crates/processing_pyo3/src/lib.rs index f8378bbe..891cfed2 100644 --- a/crates/processing_pyo3/src/lib.rs +++ b/crates/processing_pyo3/src/lib.rs @@ -1824,6 +1824,27 @@ mod mewnala { graphics!(module).emissive(args) } + #[pyfunction] + #[pyo3(pass_module)] + fn texture(module: &Bound<'_, PyModule>, source: graphics::ImageRef) -> PyResult<()> { + graphics!(module).texture(source) + } + + #[pyfunction] + #[pyo3(pass_module)] + fn no_texture(module: &Bound<'_, PyModule>) -> PyResult<()> { + graphics!(module).no_texture() + } + + #[pyfunction] + #[pyo3(pass_module)] + fn texture_transform( + module: &Bound<'_, PyModule>, + transform: crate::math::PyAffine2, + ) -> PyResult<()> { + graphics!(module).texture_transform(transform) + } + #[pyfunction] #[pyo3(pass_module)] fn unlit(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/processing_pyo3/src/webcam.rs b/crates/processing_pyo3/src/webcam.rs index 3cc06ba7..f60fca1f 100644 --- a/crates/processing_pyo3/src/webcam.rs +++ b/crates/processing_pyo3/src/webcam.rs @@ -5,13 +5,17 @@ use processing_webcam::{ }; use pyo3::{exceptions::PyRuntimeError, prelude::*}; -use crate::graphics::Image; - #[pyclass(unsendable)] pub struct Webcam { entity: Entity, } +impl Webcam { + pub(crate) fn image_entity(&self) -> PyResult { + webcam_image(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } +} + #[pymethods] impl Webcam { #[new] @@ -40,12 +44,6 @@ impl Webcam { pub fn resolution(&self) -> PyResult<(u32, u32)> { webcam_resolution(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } - - pub fn image(&self) -> PyResult { - let entity = - webcam_image(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; - Ok(Image::from_entity(entity)) - } } impl Drop for Webcam { diff --git a/crates/processing_render/src/image.rs b/crates/processing_render/src/image.rs index e7a4fcd7..4945b3b5 100644 --- a/crates/processing_render/src/image.rs +++ b/crates/processing_render/src/image.rs @@ -100,6 +100,14 @@ pub fn blit( Ok(()) } +#[derive(Component)] +#[relationship(relationship_target = LinkedImageTarget)] +pub struct LinkedImage(pub Entity); + +#[derive(Component)] +#[relationship_target(relationship = LinkedImage)] +pub struct LinkedImageTarget(Entity); + #[derive(Component)] pub struct Image { pub handle: Handle, diff --git a/crates/processing_render/src/render/command.rs b/crates/processing_render/src/render/command.rs index d514982b..7efecf56 100644 --- a/crates/processing_render/src/render/command.rs +++ b/crates/processing_render/src/render/command.rs @@ -1,3 +1,4 @@ +use bevy::math::Affine2; use bevy::math::Affine3A; use bevy::prelude::*; use bevy::render::render_resource::{BlendComponent, BlendFactor, BlendOperation, BlendState}; @@ -515,6 +516,9 @@ pub enum DrawCommand { Roughness(f32), Metallic(f32), Emissive(Color), + Texture(Entity), + NoTexture, + TextureTransform(Affine2), Unlit, Tint(Color), NoTint, diff --git a/crates/processing_render/src/render/mod.rs b/crates/processing_render/src/render/mod.rs index e841461b..0e797032 100644 --- a/crates/processing_render/src/render/mod.rs +++ b/crates/processing_render/src/render/mod.rs @@ -222,6 +222,25 @@ pub fn flush_draw_commands( pbr.blend_state = None; state.style.material_key = pbr.into(); } + DrawCommand::Texture(entity) => { + if let Ok(img) = p_images.get(entity) { + let mut pbr = state.style.material_key.as_pbr(); + pbr.base_color_texture = Some(img.handle.clone()); + state.style.material_key = pbr.into(); + } + } + DrawCommand::NoTexture => { + if let MaterialKey::Pbr { .. } = &state.style.material_key { + let mut pbr = state.style.material_key.as_pbr(); + pbr.base_color_texture = None; + state.style.material_key = pbr.into(); + } + } + DrawCommand::TextureTransform(transform) => { + let mut pbr = state.style.material_key.as_pbr(); + pbr.uv_transform = transform; + state.style.material_key = pbr.into(); + } DrawCommand::Unlit => { state.style.material_key = MaterialKey::Color { transparent: state.fill_is_transparent(), diff --git a/crates/processing_webcam/src/lib.rs b/crates/processing_webcam/src/lib.rs index 7b66dfcc..04166aec 100644 --- a/crates/processing_webcam/src/lib.rs +++ b/crates/processing_webcam/src/lib.rs @@ -31,6 +31,10 @@ fn create_with_format(In(format): In, mut commands: Commands) -> E } fn create_image(In(entity): In, world: &mut World) -> Result { + if let Some(linked) = world.get::(entity) { + return Ok(linked.0); + } + let stream = world .get::(entity) .ok_or(ProcessingError::WebcamNotConnected)?; @@ -39,6 +43,7 @@ fn create_image(In(entity): In, world: &mut World) -> Result { let child = world .run_system_once_with(image::from_handle, handle) .unwrap()?; + world.entity_mut(entity).insert(image::LinkedImage(child)); world.entity_mut(entity).add_child(child); Ok(child) }