Skip to content
Merged
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
158 changes: 79 additions & 79 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ path = "examples/midi.rs"
name = "gltf_load"
path = "examples/gltf_load.rs"

[[example]]
name = "stroke_2d"
path = "examples/stroke_2d.rs"

[[example]]
name = "stroke_3d"
path = "examples/stroke_3d.rs"

[[example]]
name = "custom_material"
path = "examples/custom_material.rs"
Expand Down
34 changes: 34 additions & 0 deletions crates/processing_ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,32 @@ pub extern "C" fn processing_set_stroke_weight(graphics_id: u64, weight: f32) {
error::check(|| graphics_record_command(graphics_entity, DrawCommand::StrokeWeight(weight)));
}

/// Set the stroke cap mode.
#[unsafe(no_mangle)]
pub extern "C" fn processing_set_stroke_cap(graphics_id: u64, cap: u8) {
error::clear_error();
let graphics_entity = Entity::from_bits(graphics_id);
error::check(|| {
graphics_record_command(
graphics_entity,
DrawCommand::StrokeCap(processing::prelude::StrokeCapMode::from(cap)),
)
});
}

/// Set the stroke join mode.
#[unsafe(no_mangle)]
pub extern "C" fn processing_set_stroke_join(graphics_id: u64, join: u8) {
error::clear_error();
let graphics_entity = Entity::from_bits(graphics_id);
error::check(|| {
graphics_record_command(
graphics_entity,
DrawCommand::StrokeJoin(processing::prelude::StrokeJoinMode::from(join)),
)
});
}

/// Disable fill for subsequent shapes.
///
/// SAFETY:
Expand Down Expand Up @@ -694,6 +720,14 @@ pub const PROCESSING_TOPOLOGY_LINE_STRIP: u8 = 2;
pub const PROCESSING_TOPOLOGY_TRIANGLE_LIST: u8 = 3;
pub const PROCESSING_TOPOLOGY_TRIANGLE_STRIP: u8 = 4;

pub const PROCESSING_STROKE_CAP_ROUND: u8 = 0;
pub const PROCESSING_STROKE_CAP_SQUARE: u8 = 1;
pub const PROCESSING_STROKE_CAP_PROJECT: u8 = 2;

pub const PROCESSING_STROKE_JOIN_ROUND: u8 = 0;
pub const PROCESSING_STROKE_JOIN_MITER: u8 = 1;
pub const PROCESSING_STROKE_JOIN_BEVEL: u8 = 2;

#[unsafe(no_mangle)]
pub extern "C" fn processing_geometry_layout_create() -> u64 {
error::clear_error();
Expand Down
16 changes: 16 additions & 0 deletions crates/processing_pyo3/src/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,22 @@ impl Graphics {
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}

pub fn stroke_cap(&self, cap: u8) -> PyResult<()> {
graphics_record_command(
self.entity,
DrawCommand::StrokeCap(processing::prelude::StrokeCapMode::from(cap)),
)
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}

pub fn stroke_join(&self, join: u8) -> PyResult<()> {
graphics_record_command(
self.entity,
DrawCommand::StrokeJoin(processing::prelude::StrokeJoinMode::from(join)),
)
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
}

pub fn rect(
&self,
x: f32,
Expand Down
22 changes: 22 additions & 0 deletions crates/processing_pyo3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ fn processing(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(stroke, m)?)?;
m.add_function(wrap_pyfunction!(no_stroke, m)?)?;
m.add_function(wrap_pyfunction!(stroke_weight, m)?)?;
m.add_function(wrap_pyfunction!(stroke_cap, m)?)?;
m.add_function(wrap_pyfunction!(stroke_join, m)?)?;

m.add("ROUND", 0u8)?;
m.add("SQUARE", 1u8)?;
m.add("PROJECT", 2u8)?;

m.add("MITER", 1u8)?;
m.add("BEVEL", 2u8)?;

m.add_function(wrap_pyfunction!(rect, m)?)?;
m.add_function(wrap_pyfunction!(image, m)?)?;
m.add_function(wrap_pyfunction!(draw_geometry, m)?)?;
Expand Down Expand Up @@ -435,6 +445,18 @@ fn stroke_weight(module: &Bound<'_, PyModule>, weight: f32) -> PyResult<()> {
graphics!(module).stroke_weight(weight)
}

#[pyfunction]
#[pyo3(pass_module)]
fn stroke_cap(module: &Bound<'_, PyModule>, cap: u8) -> PyResult<()> {
graphics!(module).stroke_cap(cap)
}

#[pyfunction]
#[pyo3(pass_module)]
fn stroke_join(module: &Bound<'_, PyModule>, join: u8) -> PyResult<()> {
graphics!(module).stroke_join(join)
}

#[pyfunction]
#[pyo3(pass_module, signature = (x, y, w, h, tl=0.0, tr=0.0, br=0.0, bl=0.0))]
fn rect(
Expand Down
1 change: 1 addition & 0 deletions crates/processing_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ fn create_app(config: Config) -> App {
LightPlugin,
material::MaterialPlugin,
MidiPlugin,
bevy::pbr::wireframe::WireframePlugin::default(),
material::custom::CustomMaterialPlugin,
));
app.add_systems(First, (clear_transient_meshes, activate_cameras))
Expand Down
42 changes: 42 additions & 0 deletions crates/processing_render/src/render/command.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
use bevy::prelude::*;

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(u8)]
pub enum StrokeCapMode {
#[default]
Round = 0,
Square = 1,
Project = 2,
}

impl From<u8> for StrokeCapMode {
fn from(v: u8) -> Self {
match v {
0 => Self::Round,
1 => Self::Square,
2 => Self::Project,
_ => Self::default(),
}
}
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(u8)]
pub enum StrokeJoinMode {
#[default]
Round = 0,
Miter = 1,
Bevel = 2,
}

impl From<u8> for StrokeJoinMode {
fn from(v: u8) -> Self {
match v {
0 => Self::Round,
1 => Self::Miter,
2 => Self::Bevel,
_ => Self::default(),
}
}
}

#[derive(Debug, Clone)]
pub enum DrawCommand {
BackgroundColor(Color),
Expand All @@ -9,6 +49,8 @@ pub enum DrawCommand {
StrokeColor(Color),
NoStroke,
StrokeWeight(f32),
StrokeCap(StrokeCapMode),
StrokeJoin(StrokeJoinMode),
Roughness(f32),
Metallic(f32),
Emissive(Color),
Expand Down
71 changes: 64 additions & 7 deletions crates/processing_render/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use bevy::{
};
use command::{CommandBuffer, DrawCommand};
use material::MaterialKey;
use primitive::{TessellationMode, box_mesh, empty_mesh, sphere_mesh};
use primitive::{StrokeConfig, TessellationMode, box_mesh, empty_mesh, sphere_mesh};
use transform::TransformStack;

use crate::{
Expand Down Expand Up @@ -65,6 +65,7 @@ pub struct RenderState {
pub fill_color: Option<Color>,
pub stroke_color: Option<Color>,
pub stroke_weight: f32,
pub stroke_config: StrokeConfig,
pub material_key: MaterialKey,
pub transform: TransformStack,
}
Expand All @@ -75,6 +76,7 @@ impl RenderState {
fill_color: Some(Color::WHITE),
stroke_color: Some(Color::BLACK),
stroke_weight: 1.0,
stroke_config: StrokeConfig::default(),
material_key: MaterialKey::Color {
transparent: false,
background_image: None,
Expand All @@ -87,6 +89,7 @@ impl RenderState {
self.fill_color = Some(Color::WHITE);
self.stroke_color = Some(Color::BLACK);
self.stroke_weight = 1.0;
self.stroke_config = StrokeConfig::default();
self.material_key = MaterialKey::Color {
transparent: false,
background_image: None,
Expand Down Expand Up @@ -152,6 +155,12 @@ pub fn flush_draw_commands(
DrawCommand::StrokeWeight(weight) => {
state.stroke_weight = weight;
}
DrawCommand::StrokeCap(cap) => {
state.stroke_config.line_cap = cap;
}
DrawCommand::StrokeJoin(join) => {
state.stroke_config.line_join = join;
}
DrawCommand::Roughness(r) => {
state.material_key = match state.material_key {
MaterialKey::Pbr {
Expand Down Expand Up @@ -223,11 +232,24 @@ pub fn flush_draw_commands(
};
}
DrawCommand::Rect { x, y, w, h, radii } => {
let stroke_config = state.stroke_config;
add_fill(
&mut res,
&mut batch,
&state,
|mesh, color| rect(mesh, x, y, w, h, radii, color, TessellationMode::Fill),
|mesh, color| {
rect(
mesh,
x,
y,
w,
h,
radii,
color,
TessellationMode::Fill,
&stroke_config,
)
},
&p_material_handles,
);

Expand All @@ -245,6 +267,7 @@ pub fn flush_draw_commands(
radii,
color,
TessellationMode::Stroke(weight),
&stroke_config,
)
},
&p_material_handles,
Expand Down Expand Up @@ -556,34 +579,68 @@ fn add_shape3d(
mesh: Mesh,
material_handles: &Query<&UntypedMaterial>,
) {
use bevy::pbr::wireframe::{Wireframe, WireframeColor, WireframeLineWidth, WireframeTopology};

flush_batch(res, batch, material_handles);

let mesh_handle = res.meshes.add(mesh);
let material_key = material_key_with_fill(state);

let material_handle = match &material_key {
let fill_color = state.fill_color.unwrap_or(Color::WHITE);
let material_handle = match &state.material_key {
MaterialKey::Custom(entity) => match material_handles.get(*entity) {
Ok(handle) => handle.0.clone(),
Err(_) => {
warn!("Custom material entity {:?} not found", entity);
return;
}
},
_ => material_key.to_material(&mut res.materials),
// TODO: in 2d, we use vertex colors. `to_material` becomes complicated if we also encode
// a base color in the material, so for simplicity we just create a new material here
// that is unlit and uses the fill color as the base color
MaterialKey::Color { transparent, .. } => {
let mat = StandardMaterial {
base_color: fill_color,
unlit: true,
cull_mode: None,
alpha_mode: if *transparent {
AlphaMode::Blend
} else {
AlphaMode::Opaque
},
..default()
};
res.materials.add(mat).untyped()
}
_ => {
let key = material_key_with_fill(state);
key.to_material(&mut res.materials)
}
};

let z_offset = -(batch.draw_index as f32 * 0.001);
let mut transform = state.transform.to_bevy_transform();
transform.translation.z += z_offset;

res.commands.spawn((
let mut entity = res.commands.spawn((
Mesh3d(mesh_handle),
UntypedMaterial(material_handle),
BelongsToGraphics(batch.graphics_entity),
transform,
batch.render_layers.clone(),
));

if let Some(stroke_color) = state.stroke_color {
entity.insert((
Wireframe,
WireframeColor {
color: stroke_color,
},
WireframeLineWidth {
width: state.stroke_weight,
},
WireframeTopology::Quads,
));
}

batch.draw_index += 1;
}

Expand Down
Loading
Loading