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
61 changes: 0 additions & 61 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 @@ -86,6 +86,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"

[profile.wasm-release]
inherits = "release"
opt-level = "z"
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
4 changes: 2 additions & 2 deletions crates/processing_pyo3/src/gltf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ impl Gltf {
#[pyfunction]
#[pyo3(pass_module)]
pub fn load_gltf(module: &Bound<'_, PyModule>, path: &str) -> PyResult<Gltf> {
let graphics = get_graphics(module)?
.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?;
let graphics =
get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?;
let entity =
gltf_load(graphics.entity, path).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
Ok(Gltf { entity })
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
24 changes: 23 additions & 1 deletion crates/processing_pyo3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ use pyo3::{
};
use std::ffi::{CStr, CString};

use gltf::Gltf;
use bevy::log::warn;
use gltf::Gltf;
use std::env;

/// Get a shared ref to the Graphics context, or return Ok(()) if not yet initialized.
Expand Down Expand Up @@ -66,6 +66,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 @@ -431,6 +441,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 @@ -282,6 +282,7 @@ fn create_app(config: Config) -> App {
LightPlugin,
material::MaterialPlugin,
MidiPlugin,
bevy::pbr::wireframe::WireframePlugin::default(),
));
app.add_systems(First, (clear_transient_meshes, activate_cameras))
.add_systems(
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
68 changes: 63 additions & 5 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,8 +232,19 @@ 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)
rect(
mesh,
x,
y,
w,
h,
radii,
color,
TessellationMode::Fill,
&stroke_config,
)
});

add_stroke(&mut res, &mut batch, &state, |mesh, color, weight| {
Expand All @@ -237,6 +257,7 @@ pub fn flush_draw_commands(
radii,
color,
TessellationMode::Stroke(weight),
&stroke_config,
)
});
}
Expand Down Expand Up @@ -511,24 +532,61 @@ fn flush_batch(res: &mut RenderResources, batch: &mut BatchState) {
}

fn add_shape3d(res: &mut RenderResources, batch: &mut BatchState, state: &RenderState, mesh: Mesh) {
use bevy::pbr::wireframe::{Wireframe, WireframeColor, WireframeLineWidth, WireframeTopology};

flush_batch(res, batch);

let mesh_handle = res.meshes.add(mesh);
let material_key = material_key_with_fill(state);
let material_handle = material_key.to_material(&mut res.materials);
let fill_color = state.fill_color.unwrap_or(Color::WHITE);
let material_handle = match &state.material_key {
// 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