diff --git a/crates/processing_pyo3/examples/animated_mesh.py b/crates/processing_pyo3/examples/animated_mesh.py new file mode 100644 index 0000000..829f014 --- /dev/null +++ b/crates/processing_pyo3/examples/animated_mesh.py @@ -0,0 +1,64 @@ +from processing import * +from math import sin, cos + +geometry = None +grid_size = 20 +spacing = 10.0 +offset = (grid_size * spacing) / 2.0; +time = 0.0 + +def setup(): + global geometry + size(800, 600) + mode_3d() + geometry = Geometry() + for z in range(grid_size): + for x in range(grid_size): + px = x * spacing - offset + pz = z * spacing - offset + geometry.color(x/grid_size, 0.5, z/grid_size, 1.0) + geometry.normal(0.0, 1.0, 0.0) + geometry.vertex(px, 0.0, pz) + + for z in range(grid_size-1): + for x in range(grid_size-1): + tl = z * grid_size + x + tr = tl + 1 + bl = (z + 1) * grid_size + x + br = bl + 1 + + geometry.index(tl) + geometry.index(bl) + geometry.index(tr) + + geometry.index(tr) + geometry.index(bl) + geometry.index(br) + + +def draw(): + global geometry + global grid_size + global offset + global spacing + global time + + camera_position(150.0, 150.0, 150.0) + camera_look_at( 0.0, 0.0, 0.0) + background(220, 200, 140) + + for z in range(grid_size): + for x in range(grid_size): + idx = int(z * grid_size + x) + px = x * spacing - offset + pz = z * spacing - offset + wave = sin(px * 0.1 + time) * cos(pz * 0.1 + time) * 20.0 + geometry.set_vertex(idx, px, wave, pz) + + draw_geometry(geometry) + + time += 0.05 + + +# TODO: this should happen implicitly on module load somehow +run() diff --git a/crates/processing_pyo3/examples/box.py b/crates/processing_pyo3/examples/box.py new file mode 100644 index 0000000..32477b5 --- /dev/null +++ b/crates/processing_pyo3/examples/box.py @@ -0,0 +1,25 @@ +from processing import * + +angle = 0.0 + +def setup(): + size(800, 600) + mode_3d() + +def draw(): + global angle + camera_position(100.0, 100.0, 300.0) + camera_look_at(0.0, 0.0, 0.0) + background(220) + + push_matrix() + rotate(angle) + draw_box(100.0, 100.0, 100.0) + pop_matrix() + + angle += 0.02 + + +# TODO: this should happen implicitly on module load somehow +run() + diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index 00245f8..5f7be05 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -1,6 +1,6 @@ use bevy::prelude::Entity; use processing::prelude::*; -use pyo3::{exceptions::PyRuntimeError, prelude::*}; +use pyo3::{exceptions::PyRuntimeError, prelude::*, types::PyDict}; use crate::glfw::GlfwContext; @@ -35,6 +35,71 @@ impl Drop for Image { } } +#[pyclass(unsendable)] +pub struct Geometry { + entity: Entity, +} + +#[pyclass] +pub enum Topology { + PointList = 0, + LineList = 1, + LineStrip = 2, + TriangleList = 3, + TriangleStrip = 4, +} + +impl Topology { + pub fn as_u8(&self) -> u8 { + match self { + Self::PointList => 0, + Self::LineList => 1, + Self::LineStrip => 2, + Self::TriangleList => 3, + Self::TriangleStrip => 4, + } + } +} + +#[pymethods] +impl Geometry { + #[new] + #[pyo3(signature = (**kwargs))] + pub fn new(kwargs: Option<&Bound<'_, PyDict>>) -> PyResult { + let topology = kwargs + .and_then(|k| k.get_item("topology").ok().flatten()) + .and_then(|t| t.cast_into::().ok()) + .and_then(|t| geometry::Topology::from_u8(t.borrow().as_u8())) + .unwrap_or(geometry::Topology::TriangleList); + + let geometry = + geometry_create(topology).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; + Ok(Self { entity: geometry }) + } + + pub fn color(&self, r: f32, g: f32, b: f32, a: f32) -> PyResult<()> { + geometry_color(self.entity, r, g, b, a).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn normal(&self, nx: f32, ny: f32, nz: f32) -> PyResult<()> { + geometry_normal(self.entity, nx, ny, nz) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn vertex(&self, x: f32, y: f32, z: f32) -> PyResult<()> { + geometry_vertex(self.entity, x, y, z).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn index(&self, i: u32) -> PyResult<()> { + geometry_index(self.entity, i).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn set_vertex(&self, i: u32, x: f32, y: f32, z: f32) -> PyResult<()> { + geometry_set_vertex(self.entity, i, x, y, z) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } +} + #[pyclass(unsendable)] pub struct Graphics { entity: Entity, @@ -173,6 +238,17 @@ impl Graphics { .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } + pub fn draw_box(&self, x: f32, y: f32, z: f32) -> PyResult<()> { + let box_geo = geometry_box(x, y, z).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; + graphics_record_command(self.entity, DrawCommand::Geometry(box_geo)) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn draw_geometry(&self, geometry: &Geometry) -> PyResult<()> { + graphics_record_command(self.entity, DrawCommand::Geometry(geometry.entity)) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + pub fn scale(&self, x: f32, y: f32) -> PyResult<()> { graphics_record_command(self.entity, DrawCommand::Scale { x, y }) .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 3e5d07a..3985d24 100644 --- a/crates/processing_pyo3/src/lib.rs +++ b/crates/processing_pyo3/src/lib.rs @@ -11,7 +11,7 @@ mod glfw; mod graphics; -use graphics::{Graphics, Image, get_graphics, get_graphics_mut}; +use graphics::{Geometry, Graphics, Image, Topology, get_graphics, get_graphics_mut}; use pyo3::{exceptions::PyRuntimeError, prelude::*, types::PyTuple}; use std::env; @@ -20,8 +20,17 @@ use std::env; fn processing(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_function(wrap_pyfunction!(size, m)?)?; m.add_function(wrap_pyfunction!(run, m)?)?; + m.add_function(wrap_pyfunction!(mode_3d, m)?)?; + m.add_function(wrap_pyfunction!(camera_position, m)?)?; + m.add_function(wrap_pyfunction!(camera_look_at, m)?)?; + m.add_function(wrap_pyfunction!(push_matrix, m)?)?; + m.add_function(wrap_pyfunction!(pop_matrix, m)?)?; + m.add_function(wrap_pyfunction!(rotate, m)?)?; + m.add_function(wrap_pyfunction!(draw_box, m)?)?; m.add_function(wrap_pyfunction!(background, m)?)?; m.add_function(wrap_pyfunction!(fill, m)?)?; m.add_function(wrap_pyfunction!(no_fill, m)?)?; @@ -30,6 +39,8 @@ fn processing(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(stroke_weight, m)?)?; m.add_function(wrap_pyfunction!(rect, m)?)?; m.add_function(wrap_pyfunction!(image, m)?)?; + m.add_function(wrap_pyfunction!(draw_geometry, m)?)?; + Ok(()) } @@ -97,6 +108,59 @@ fn run(module: &Bound<'_, PyModule>) -> PyResult<()> { }) } +#[pyfunction] +#[pyo3(pass_module)] +fn mode_3d(module: &Bound<'_, PyModule>) -> PyResult<()> { + get_graphics(module)?.mode_3d() +} + +#[pyfunction] +#[pyo3(pass_module)] +fn camera_position(module: &Bound<'_, PyModule>, x: f32, y: f32, z: f32) -> PyResult<()> { + get_graphics(module)?.camera_position(x, y, z) +} + +#[pyfunction] +#[pyo3(pass_module)] +fn camera_look_at( + module: &Bound<'_, PyModule>, + target_x: f32, + target_y: f32, + target_z: f32, +) -> PyResult<()> { + get_graphics(module)?.camera_look_at(target_x, target_y, target_z) +} + +#[pyfunction] +#[pyo3(pass_module)] +fn push_matrix(module: &Bound<'_, PyModule>) -> PyResult<()> { + get_graphics(module)?.push_matrix() +} + +#[pyfunction] +#[pyo3(pass_module)] +fn pop_matrix(module: &Bound<'_, PyModule>) -> PyResult<()> { + get_graphics(module)?.push_matrix() +} + +#[pyfunction] +#[pyo3(pass_module)] +fn rotate(module: &Bound<'_, PyModule>, angle: f32) -> PyResult<()> { + get_graphics(module)?.rotate(angle) +} + +#[pyfunction] +#[pyo3(pass_module)] +fn draw_box(module: &Bound<'_, PyModule>, x: f32, y: f32, z: f32) -> PyResult<()> { + get_graphics(module)?.draw_box(x, y, z) +} + +#[pyfunction] +#[pyo3(pass_module, signature = (geometry))] +fn draw_geometry(module: &Bound<'_, PyModule>, geometry: &Bound<'_, Geometry>) -> PyResult<()> { + get_graphics(module)?.draw_geometry(&*geometry.extract::>()?) +} + #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn background(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> {