Skip to content
Open
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
39 changes: 39 additions & 0 deletions crates/processing_pyo3/examples/particles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from processing import *
from random import gauss

geometry = None

def setup():
global geometry
size(800, 600)
mode_3d()
create_geometry()

def draw():
global geometry

camera_position(150.0, 150.0, 150.0)
camera_look_at( 0.0, 0.0, 0.0)
background(220, 200, 140)

draw_geometry(geometry)

def create_geometry():
global geometry

begin_geometry()

for i in range(60):
x = gauss(400, 200)
y = gauss(350, 175)
z = gauss(0, 100)

push_matrix()
translate_3d(x, y, z)
sphere(10)
pop_matrix()

geometry = end_geometry()

# TODO: this should happen implicitly on module load somehow
run()
20 changes: 20 additions & 0 deletions crates/processing_pyo3/src/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ impl Graphics {
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}

pub fn translate_3d(&self, x: f32, y: f32, z: f32) -> PyResult<()> {
graphics_record_command(self.entity, DrawCommand::Translate3D { x, y, z })
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}

pub fn rotate(&self, angle: f32) -> PyResult<()> {
graphics_record_command(self.entity, DrawCommand::Rotate { angle })
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
Expand Down Expand Up @@ -308,6 +313,21 @@ impl Graphics {
graphics_ortho(self.entity, left, right, bottom, top, near, far)
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}

pub fn begin_geometry(&self) -> PyResult<()> {
geometry_begin(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}

pub fn end_geometry(&self) -> PyResult<Geometry> {
match geometry_end(self.entity) {
Ok(geometry) => Ok(Geometry { entity: geometry }),
Err(e) => Err(PyRuntimeError::new_err(format!("{e}"))),
}
}

pub fn sphere(&self, radius: f32) -> PyResult<()> {
geometry_sphere(self.entity, radius).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}
}

// TODO: a real color type. or color parser? idk. color is confusing. let's think
Expand Down
35 changes: 35 additions & 0 deletions crates/processing_pyo3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ fn processing(m: &Bound<'_, PyModule>) -> PyResult<()> {
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!(translate, m)?)?;
m.add_function(wrap_pyfunction!(translate_3d, m)?)?;
m.add_function(wrap_pyfunction!(draw_box, m)?)?;
m.add_function(wrap_pyfunction!(background, m)?)?;
m.add_function(wrap_pyfunction!(fill, m)?)?;
Expand All @@ -40,6 +42,9 @@ fn processing(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(rect, m)?)?;
m.add_function(wrap_pyfunction!(image, m)?)?;
m.add_function(wrap_pyfunction!(draw_geometry, m)?)?;
m.add_function(wrap_pyfunction!(begin_geometry, m)?)?;
m.add_function(wrap_pyfunction!(end_geometry, m)?)?;
m.add_function(wrap_pyfunction!(sphere, m)?)?;

Ok(())
}
Expand Down Expand Up @@ -149,6 +154,18 @@ fn rotate(module: &Bound<'_, PyModule>, angle: f32) -> PyResult<()> {
get_graphics(module)?.rotate(angle)
}

#[pyfunction]
#[pyo3(pass_module)]
fn translate(module: &Bound<'_, PyModule>, x: f32, y: f32) -> PyResult<()> {
get_graphics(module)?.translate(x, y)
}

#[pyfunction]
#[pyo3(pass_module)]
fn translate_3d(module: &Bound<'_, PyModule>, x: f32, y: f32, z: f32) -> PyResult<()> {
get_graphics(module)?.translate_3d(x, y, z)
}

#[pyfunction]
#[pyo3(pass_module)]
fn draw_box(module: &Bound<'_, PyModule>, x: f32, y: f32, z: f32) -> PyResult<()> {
Expand Down Expand Up @@ -223,3 +240,21 @@ fn rect(
fn image(module: &Bound<'_, PyModule>, image_file: &str) -> PyResult<Image> {
get_graphics(module)?.image(image_file)
}

#[pyfunction]
#[pyo3(pass_module)]
fn begin_geometry(module: &Bound<'_, PyModule>) -> PyResult<()> {
get_graphics(module)?.begin_geometry()
}

#[pyfunction]
#[pyo3(pass_module)]
fn end_geometry(module: &Bound<'_, PyModule>) -> PyResult<Geometry> {
get_graphics(module)?.end_geometry()
}

#[pyfunction]
#[pyo3(pass_module, signature = (radius))]
fn sphere(module: &Bound<'_, PyModule>, radius: f32) -> PyResult<()> {
get_graphics(module)?.sphere(radius)
}
118 changes: 116 additions & 2 deletions crates/processing_render/src/geometry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ use bevy::{
render::render_resource::PrimitiveTopology,
};

use crate::error::{ProcessingError, Result};
use crate::{
error::{ProcessingError, Result},
render::RenderState,
};

pub struct GeometryPlugin;

Expand Down Expand Up @@ -60,7 +63,7 @@ impl Topology {
}
}

#[derive(Component)]
#[derive(Debug, Component)]
pub struct Geometry {
pub handle: Handle<Mesh>,
pub layout: Entity,
Expand Down Expand Up @@ -365,3 +368,114 @@ pub fn destroy(
commands.entity(entity).despawn();
Ok(())
}

pub fn begin(
In(entity): In<Entity>,
mut commands: Commands,
mut state_query: Query<&mut RenderState>,
mut meshes: ResMut<Assets<Mesh>>,
builtins: Res<BuiltinAttributes>,
) -> Result<()> {
let layout_entity = commands
.spawn(VertexLayout::with_attributes(vec![
builtins.position,
builtins.normal,
builtins.color,
builtins.uv,
]))
.id();

let mesh = Mesh::new(
Topology::TriangleList.to_primitive_topology(),
RenderAssetUsages::default(),
);
let handle = meshes.add(mesh);

let mut state = state_query
.get_mut(entity)
.map_err(|_| ProcessingError::GraphicsNotFound)?;

state.running_geometry = Some(Geometry::new(handle, layout_entity));
Ok(())
}

pub fn end(
In(entity): In<Entity>,
mut commands: Commands,
mut state_query: Query<&mut RenderState>,
) -> Result<Entity> {
let geometry = state_query
.get_mut(entity)
.map_err(|_| ProcessingError::GraphicsNotFound)?
.running_geometry
.take()
.ok_or(ProcessingError::GeometryNotFound)?;

Ok(commands.spawn(geometry).id())
}

pub fn sphere(
In((entity, radius)): In<(Entity, f32)>,
state_query: Query<&mut RenderState>,
mut meshes: ResMut<Assets<Mesh>>,
) -> Result<()> {
let geometry = state_query
.get(entity)
.map_err(|_| ProcessingError::GraphicsNotFound)?
.running_geometry
.as_ref()
.ok_or(ProcessingError::GeometryNotFound)?;

let mesh = meshes
.get_mut(&geometry.handle)
.ok_or(ProcessingError::GeometryNotFound)?;

let base_index = mesh.count_vertices() as u32;
let sphere_mesh = Sphere::new(radius).mesh().build();

// Append positions
if let Some(VertexAttributeValues::Float32x3(new_pos)) =
sphere_mesh.attribute(Mesh::ATTRIBUTE_POSITION)
&& let Some(VertexAttributeValues::Float32x3(positions)) =
mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION)
{
positions.extend_from_slice(new_pos);
}

// Append normals
if let Some(VertexAttributeValues::Float32x3(new_normals)) =
sphere_mesh.attribute(Mesh::ATTRIBUTE_NORMAL)
&& let Some(VertexAttributeValues::Float32x3(normals)) =
mesh.attribute_mut(Mesh::ATTRIBUTE_NORMAL)
{
normals.extend_from_slice(new_normals);
}

// Append UVs
if let Some(VertexAttributeValues::Float32x2(new_uvs)) =
sphere_mesh.attribute(Mesh::ATTRIBUTE_UV_0)
&& let Some(VertexAttributeValues::Float32x2(uvs)) =
mesh.attribute_mut(Mesh::ATTRIBUTE_UV_0)
{
uvs.extend_from_slice(new_uvs);
}

// Append indices with offset
let new_indices: Vec<u32> = match sphere_mesh.indices() {
Some(Indices::U16(vec)) => vec.iter().map(|&i| i as u32 + base_index).collect(),
Some(Indices::U32(vec)) => vec.iter().map(|&i| i + base_index).collect(),
None => Vec::new(),
};

match mesh.indices_mut() {
Some(Indices::U32(indices)) => indices.extend(new_indices),
Some(Indices::U16(indices)) => {
indices.extend(new_indices.iter().map(|&i| i as u16));
}
None => {
mesh.insert_indices(Indices::U32(new_indices));
}
}

Ok(())
}
24 changes: 24 additions & 0 deletions crates/processing_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1071,3 +1071,27 @@ pub fn geometry_box(width: f32, height: f32, depth: f32) -> error::Result<Entity
.unwrap())
})
}

pub fn geometry_begin(graphics_entity: Entity) -> error::Result<()> {
app_mut(|app| {
app.world_mut()
.run_system_cached_with(geometry::begin, graphics_entity)
.unwrap()
})
}

pub fn geometry_end(graphics_entity: Entity) -> error::Result<Entity> {
app_mut(|app| {
app.world_mut()
.run_system_cached_with(geometry::end, graphics_entity)
.unwrap()
})
}

pub fn geometry_sphere(graphics_entity: Entity, radius: f32) -> error::Result<()> {
app_mut(|app| {
app.world_mut()
.run_system_cached_with(geometry::sphere, (graphics_entity, radius))
.unwrap()
})
}
5 changes: 5 additions & 0 deletions crates/processing_render/src/render/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ pub enum DrawCommand {
x: f32,
y: f32,
},
Translate3D {
x: f32,
y: f32,
z: f32,
},
Rotate {
angle: f32,
},
Expand Down
3 changes: 3 additions & 0 deletions crates/processing_render/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ pub struct RenderState {
pub stroke_color: Option<Color>,
pub stroke_weight: f32,
pub transform: TransformStack,
pub running_geometry: Option<Geometry>,
}

impl Default for RenderState {
Expand All @@ -69,6 +70,7 @@ impl Default for RenderState {
stroke_color: Some(Color::BLACK),
stroke_weight: 1.0,
transform: TransformStack::new(),
running_geometry: None,
}
}
}
Expand Down Expand Up @@ -200,6 +202,7 @@ pub fn flush_draw_commands(
DrawCommand::PopMatrix => state.transform.pop(),
DrawCommand::ResetMatrix => state.transform.reset(),
DrawCommand::Translate { x, y } => state.transform.translate(x, y),
DrawCommand::Translate3D { x, y, z } => state.transform.translate_3d(x, y, z),
DrawCommand::Rotate { angle } => state.transform.rotate(angle),
DrawCommand::Scale { x, y } => state.transform.scale(x, y),
DrawCommand::ShearX { angle } => state.transform.shear_x(angle),
Expand Down