Skip to content

Commit 33e981e

Browse files
committed
Add stroke modes.
1 parent 96ca163 commit 33e981e

File tree

15 files changed

+471
-76
lines changed

15 files changed

+471
-76
lines changed

Cargo.lock

Lines changed: 0 additions & 61 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type_complexity = "allow"
2121
too_many_arguments = "allow"
2222

2323
[workspace.dependencies]
24-
bevy = { git = "https://github.com/bevyengine/bevy", branch = "main" }
24+
bevy = { path = "../../bevyengine/bevy" }
2525
processing = { path = "." }
2626
processing_pyo3 = { path = "crates/processing_pyo3" }
2727
processing_render = { path = "crates/processing_render" }
@@ -86,6 +86,17 @@ path = "examples/midi.rs"
8686
name = "gltf_load"
8787
path = "examples/gltf_load.rs"
8888

89+
[[example]]
90+
name = "stroke_2d"
91+
path = "examples/stroke_2d.rs"
92+
93+
[[example]]
94+
name = "stroke_3d"
95+
path = "examples/stroke_3d.rs"
96+
97+
[patch."https://github.com/bevyengine/bevy"]
98+
bevy = { path = "../../bevyengine/bevy" }
99+
89100
[profile.wasm-release]
90101
inherits = "release"
91102
opt-level = "z"

crates/processing_ffi/src/lib.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,32 @@ pub extern "C" fn processing_set_stroke_weight(graphics_id: u64, weight: f32) {
281281
error::check(|| graphics_record_command(graphics_entity, DrawCommand::StrokeWeight(weight)));
282282
}
283283

284+
/// Set the stroke cap mode.
285+
#[unsafe(no_mangle)]
286+
pub extern "C" fn processing_set_stroke_cap(graphics_id: u64, cap: u8) {
287+
error::clear_error();
288+
let graphics_entity = Entity::from_bits(graphics_id);
289+
error::check(|| {
290+
graphics_record_command(
291+
graphics_entity,
292+
DrawCommand::StrokeCap(processing::prelude::StrokeCapMode::from(cap)),
293+
)
294+
});
295+
}
296+
297+
/// Set the stroke join mode.
298+
#[unsafe(no_mangle)]
299+
pub extern "C" fn processing_set_stroke_join(graphics_id: u64, join: u8) {
300+
error::clear_error();
301+
let graphics_entity = Entity::from_bits(graphics_id);
302+
error::check(|| {
303+
graphics_record_command(
304+
graphics_entity,
305+
DrawCommand::StrokeJoin(processing::prelude::StrokeJoinMode::from(join)),
306+
)
307+
});
308+
}
309+
284310
/// Disable fill for subsequent shapes.
285311
///
286312
/// SAFETY:
@@ -694,6 +720,14 @@ pub const PROCESSING_TOPOLOGY_LINE_STRIP: u8 = 2;
694720
pub const PROCESSING_TOPOLOGY_TRIANGLE_LIST: u8 = 3;
695721
pub const PROCESSING_TOPOLOGY_TRIANGLE_STRIP: u8 = 4;
696722

723+
pub const PROCESSING_STROKE_CAP_ROUND: u8 = 0;
724+
pub const PROCESSING_STROKE_CAP_SQUARE: u8 = 1;
725+
pub const PROCESSING_STROKE_CAP_PROJECT: u8 = 2;
726+
727+
pub const PROCESSING_STROKE_JOIN_ROUND: u8 = 0;
728+
pub const PROCESSING_STROKE_JOIN_MITER: u8 = 1;
729+
pub const PROCESSING_STROKE_JOIN_BEVEL: u8 = 2;
730+
697731
#[unsafe(no_mangle)]
698732
pub extern "C" fn processing_geometry_layout_create() -> u64 {
699733
error::clear_error();

crates/processing_pyo3/src/gltf.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ impl Gltf {
4646
#[pyfunction]
4747
#[pyo3(pass_module)]
4848
pub fn load_gltf(module: &Bound<'_, PyModule>, path: &str) -> PyResult<Gltf> {
49-
let graphics = get_graphics(module)?
50-
.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?;
49+
let graphics =
50+
get_graphics(module)?.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?;
5151
let entity =
5252
gltf_load(graphics.entity, path).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
5353
Ok(Gltf { entity })

crates/processing_pyo3/src/graphics.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,22 @@ impl Graphics {
319319
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
320320
}
321321

322+
pub fn stroke_cap(&self, cap: u8) -> PyResult<()> {
323+
graphics_record_command(
324+
self.entity,
325+
DrawCommand::StrokeCap(processing::prelude::StrokeCapMode::from(cap)),
326+
)
327+
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
328+
}
329+
330+
pub fn stroke_join(&self, join: u8) -> PyResult<()> {
331+
graphics_record_command(
332+
self.entity,
333+
DrawCommand::StrokeJoin(processing::prelude::StrokeJoinMode::from(join)),
334+
)
335+
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
336+
}
337+
322338
pub fn rect(
323339
&self,
324340
x: f32,

crates/processing_pyo3/src/lib.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ use pyo3::{
2222
};
2323
use std::ffi::{CStr, CString};
2424

25-
use gltf::Gltf;
2625
use bevy::log::warn;
26+
use gltf::Gltf;
2727
use std::env;
2828

2929
/// Get a shared ref to the Graphics context, or return Ok(()) if not yet initialized.
@@ -66,6 +66,16 @@ fn processing(m: &Bound<'_, PyModule>) -> PyResult<()> {
6666
m.add_function(wrap_pyfunction!(stroke, m)?)?;
6767
m.add_function(wrap_pyfunction!(no_stroke, m)?)?;
6868
m.add_function(wrap_pyfunction!(stroke_weight, m)?)?;
69+
m.add_function(wrap_pyfunction!(stroke_cap, m)?)?;
70+
m.add_function(wrap_pyfunction!(stroke_join, m)?)?;
71+
72+
m.add("ROUND", 0u8)?;
73+
m.add("SQUARE", 1u8)?;
74+
m.add("PROJECT", 2u8)?;
75+
76+
m.add("MITER", 1u8)?;
77+
m.add("BEVEL", 2u8)?;
78+
6979
m.add_function(wrap_pyfunction!(rect, m)?)?;
7080
m.add_function(wrap_pyfunction!(image, m)?)?;
7181
m.add_function(wrap_pyfunction!(draw_geometry, m)?)?;
@@ -431,6 +441,18 @@ fn stroke_weight(module: &Bound<'_, PyModule>, weight: f32) -> PyResult<()> {
431441
graphics!(module).stroke_weight(weight)
432442
}
433443

444+
#[pyfunction]
445+
#[pyo3(pass_module)]
446+
fn stroke_cap(module: &Bound<'_, PyModule>, cap: u8) -> PyResult<()> {
447+
graphics!(module).stroke_cap(cap)
448+
}
449+
450+
#[pyfunction]
451+
#[pyo3(pass_module)]
452+
fn stroke_join(module: &Bound<'_, PyModule>, join: u8) -> PyResult<()> {
453+
graphics!(module).stroke_join(join)
454+
}
455+
434456
#[pyfunction]
435457
#[pyo3(pass_module, signature = (x, y, w, h, tl=0.0, tr=0.0, br=0.0, bl=0.0))]
436458
fn rect(

crates/processing_render/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ fn create_app(config: Config) -> App {
282282
LightPlugin,
283283
material::MaterialPlugin,
284284
MidiPlugin,
285+
bevy::pbr::wireframe::WireframePlugin::default(),
285286
));
286287
app.add_systems(First, (clear_transient_meshes, activate_cameras))
287288
.add_systems(

crates/processing_render/src/render/command.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,45 @@
11
use bevy::prelude::*;
22

3+
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
4+
#[repr(u8)]
5+
pub enum StrokeCapMode {
6+
#[default]
7+
Round = 0,
8+
Square = 1,
9+
Project = 2,
10+
}
11+
12+
impl From<u8> for StrokeCapMode {
13+
fn from(v: u8) -> Self {
14+
match v {
15+
0 => Self::Round,
16+
1 => Self::Square,
17+
2 => Self::Project,
18+
_ => Self::default(),
19+
}
20+
}
21+
}
22+
23+
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
24+
#[repr(u8)]
25+
pub enum StrokeJoinMode {
26+
#[default]
27+
Round = 0,
28+
Miter = 1,
29+
Bevel = 2,
30+
}
31+
32+
impl From<u8> for StrokeJoinMode {
33+
fn from(v: u8) -> Self {
34+
match v {
35+
0 => Self::Round,
36+
1 => Self::Miter,
37+
2 => Self::Bevel,
38+
_ => Self::default(),
39+
}
40+
}
41+
}
42+
343
#[derive(Debug, Clone)]
444
pub enum DrawCommand {
545
BackgroundColor(Color),
@@ -9,6 +49,8 @@ pub enum DrawCommand {
949
StrokeColor(Color),
1050
NoStroke,
1151
StrokeWeight(f32),
52+
StrokeCap(StrokeCapMode),
53+
StrokeJoin(StrokeJoinMode),
1254
Roughness(f32),
1355
Metallic(f32),
1456
Emissive(Color),

crates/processing_render/src/render/mod.rs

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use bevy::{
1212
};
1313
use command::{CommandBuffer, DrawCommand};
1414
use material::MaterialKey;
15-
use primitive::{TessellationMode, box_mesh, empty_mesh, sphere_mesh};
15+
use primitive::{StrokeConfig, TessellationMode, box_mesh, empty_mesh, sphere_mesh};
1616
use transform::TransformStack;
1717

1818
use crate::{
@@ -65,6 +65,7 @@ pub struct RenderState {
6565
pub fill_color: Option<Color>,
6666
pub stroke_color: Option<Color>,
6767
pub stroke_weight: f32,
68+
pub stroke_config: StrokeConfig,
6869
pub material_key: MaterialKey,
6970
pub transform: TransformStack,
7071
}
@@ -75,6 +76,7 @@ impl RenderState {
7576
fill_color: Some(Color::WHITE),
7677
stroke_color: Some(Color::BLACK),
7778
stroke_weight: 1.0,
79+
stroke_config: StrokeConfig::default(),
7880
material_key: MaterialKey::Color {
7981
transparent: false,
8082
background_image: None,
@@ -87,6 +89,7 @@ impl RenderState {
8789
self.fill_color = Some(Color::WHITE);
8890
self.stroke_color = Some(Color::BLACK);
8991
self.stroke_weight = 1.0;
92+
self.stroke_config = StrokeConfig::default();
9093
self.material_key = MaterialKey::Color {
9194
transparent: false,
9295
background_image: None,
@@ -152,6 +155,12 @@ pub fn flush_draw_commands(
152155
DrawCommand::StrokeWeight(weight) => {
153156
state.stroke_weight = weight;
154157
}
158+
DrawCommand::StrokeCap(cap) => {
159+
state.stroke_config.line_cap = cap;
160+
}
161+
DrawCommand::StrokeJoin(join) => {
162+
state.stroke_config.line_join = join;
163+
}
155164
DrawCommand::Roughness(r) => {
156165
state.material_key = match state.material_key {
157166
MaterialKey::Pbr {
@@ -223,8 +232,19 @@ pub fn flush_draw_commands(
223232
};
224233
}
225234
DrawCommand::Rect { x, y, w, h, radii } => {
235+
let stroke_config = state.stroke_config;
226236
add_fill(&mut res, &mut batch, &state, |mesh, color| {
227-
rect(mesh, x, y, w, h, radii, color, TessellationMode::Fill)
237+
rect(
238+
mesh,
239+
x,
240+
y,
241+
w,
242+
h,
243+
radii,
244+
color,
245+
TessellationMode::Fill,
246+
&stroke_config,
247+
)
228248
});
229249

230250
add_stroke(&mut res, &mut batch, &state, |mesh, color, weight| {
@@ -237,6 +257,7 @@ pub fn flush_draw_commands(
237257
radii,
238258
color,
239259
TessellationMode::Stroke(weight),
260+
&stroke_config,
240261
)
241262
});
242263
}
@@ -511,24 +532,58 @@ fn flush_batch(res: &mut RenderResources, batch: &mut BatchState) {
511532
}
512533

513534
fn add_shape3d(res: &mut RenderResources, batch: &mut BatchState, state: &RenderState, mesh: Mesh) {
535+
use bevy::pbr::wireframe::{Wireframe, WireframeColor, WireframeLineWidth, WireframeTopology};
536+
514537
flush_batch(res, batch);
515538

516539
let mesh_handle = res.meshes.add(mesh);
517-
let material_key = material_key_with_fill(state);
518-
let material_handle = material_key.to_material(&mut res.materials);
540+
let fill_color = state.fill_color.unwrap_or(Color::WHITE);
541+
let material_handle = match &state.material_key {
542+
MaterialKey::Color { transparent, .. } => {
543+
let mat = StandardMaterial {
544+
base_color: fill_color,
545+
unlit: true,
546+
cull_mode: None,
547+
alpha_mode: if *transparent {
548+
AlphaMode::Blend
549+
} else {
550+
AlphaMode::Opaque
551+
},
552+
..default()
553+
};
554+
res.materials.add(mat).untyped()
555+
}
556+
_ => {
557+
let key = material_key_with_fill(state);
558+
key.to_material(&mut res.materials)
559+
}
560+
};
519561

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

524-
res.commands.spawn((
566+
let mut entity = res.commands.spawn((
525567
Mesh3d(mesh_handle),
526568
UntypedMaterial(material_handle),
527569
BelongsToGraphics(batch.graphics_entity),
528570
transform,
529571
batch.render_layers.clone(),
530572
));
531573

574+
if let Some(stroke_color) = state.stroke_color {
575+
entity.insert((
576+
Wireframe,
577+
WireframeColor {
578+
color: stroke_color,
579+
},
580+
WireframeLineWidth {
581+
width: state.stroke_weight,
582+
},
583+
WireframeTopology::Quads,
584+
));
585+
}
586+
532587
batch.draw_index += 1;
533588
}
534589

0 commit comments

Comments
 (0)