Skip to content

Commit 084343f

Browse files
committed
Start rewriting using custom SVG tesselator using lyon
1 parent 0b2c802 commit 084343f

File tree

8 files changed

+415
-34
lines changed

8 files changed

+415
-34
lines changed

Cargo.toml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,22 @@ travis-ci = {repository = "tversteeg/castle-game"}
1818
is-it-maintained-issue-resolution = { repository = "tversteeg/castle-game" }
1919

2020
[dependencies]
21+
anyhow = "1.0.55"
2122
bevy = { version = "0.6.1", features = ["trace"] }
2223
bevy-inspector-egui = "0.8.2"
2324
bevy_easings = "0.6.0"
2425
bevy_egui = "0.11.1"
2526
bevy_rapier2d = { version = "0.12.1", features = ["simd-stable", "wasm-bindgen", "render"] }
26-
bevy_svg = { version = "0.6.0", features = ["2d"], default-features = false }
2727
earcutr = "0.2.0"
2828
geo = "0.19.0"
2929
geo-booleanop = { git = "https://github.com/21re/rust-geo-booleanop.git" }
3030
geo-types = "0.7.3"
3131
itertools = "0.10.3"
32+
lyon_tessellation = "0.17.10"
3233
nalgebra = "0.30.1"
3334
rand = "0.8.5"
3435
tracing-subscriber = "0.3.9"
36+
usvg = "0.22.0"
3537

3638
[target.'cfg(target_arch = "wasm32")'.dependencies]
3739
tracing-wasm = "0.2.1"
@@ -41,10 +43,6 @@ console_error_panic_hook = "0.1.7"
4143
default = ["bevy_dynamic"]
4244
bevy_dynamic = ["bevy/dynamic"]
4345

44-
# Always use the latest version for bevy
45-
[patch.crates-io]
46-
bevy = { git = "https://github.com/bevyengine/bevy", branch = "main" }
47-
4846
# Don't make debug builds painfully slow
4947
[profile.dev]
5048
opt-level = 1

src/draw/mesh.rs

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
use std::slice::Iter;
2+
3+
use bevy::{
4+
prelude::{Color, Mesh},
5+
render::{mesh::Indices, render_resource::PrimitiveTopology},
6+
utils::tracing,
7+
};
8+
use lyon_tessellation::{
9+
geom::euclid::default::Transform2D, math::Point, path::PathEvent, BuffersBuilder, FillOptions,
10+
FillTessellator, FillVertex, FillVertexConstructor, StrokeOptions, StrokeTessellator,
11+
StrokeVertex, StrokeVertexConstructor, VertexBuffers,
12+
};
13+
use usvg::{NodeKind, Paint, Path, PathSegment, Transform, Tree};
14+
15+
/// A custom vertex constructor for lyon, creates bevy vertices.
16+
struct BevyVertexConstructor {
17+
/// The transform to apply to all vertices.
18+
transform: Transform,
19+
}
20+
21+
impl FillVertexConstructor<[f32; 3]> for BevyVertexConstructor {
22+
fn new_vertex(&mut self, vertex: FillVertex) -> [f32; 3] {
23+
let pos = vertex.position();
24+
25+
// Transform the 2D point
26+
// TODO: remove ugly casts
27+
let (x, y) = self.transform.apply(pos.x as f64, pos.y as f64);
28+
29+
[x as f32, y as f32, 0.0]
30+
}
31+
}
32+
33+
impl StrokeVertexConstructor<[f32; 3]> for BevyVertexConstructor {
34+
fn new_vertex(&mut self, vertex: StrokeVertex) -> [f32; 3] {
35+
let pos = vertex.position();
36+
37+
// Transform the 2D point
38+
// TODO: remove ugly casts
39+
let (x, y) = self.transform.apply(pos.x as f64, pos.y as f64);
40+
41+
[x as f32, y as f32, 0.0]
42+
}
43+
}
44+
45+
/// Convert a SVG to a mesh.
46+
pub fn svg_to_mesh(svg: &Tree) -> Mesh {
47+
bevy::log::trace!("Converting SVG paths to mesh");
48+
49+
// The resulting vertex and index buffers
50+
let mut vertices = Vec::new();
51+
let mut indices = Vec::new();
52+
let mut colors = Vec::new();
53+
54+
for node in svg.root().descendants() {
55+
if let NodeKind::Path(ref path) = *node.borrow() {
56+
bevy::log::trace!("Parsing SVG path node");
57+
58+
// Convert the fill to a polygon
59+
if let Some(ref fill) = path.fill {
60+
let mut buffers = svg_path_fill_to_vertex_buffers(&path, vertices.len() as u32);
61+
62+
// Merge the buffers
63+
vertices.append(&mut buffers.vertices);
64+
indices.append(&mut buffers.indices);
65+
// Fill the buffer with the same size as the vertices with colors
66+
colors.resize(
67+
vertices.len(),
68+
svg_color_to_bevy(&fill.paint, fill.opacity.to_u8()),
69+
);
70+
}
71+
72+
// Convert the stroke to a polygon
73+
if let Some(ref stroke) = path.stroke {
74+
let mut buffers = svg_path_stroke_to_vertex_buffers(&path, vertices.len() as u32);
75+
76+
// Merge the buffers
77+
vertices.append(&mut buffers.vertices);
78+
indices.append(&mut buffers.indices);
79+
// Fill the buffer with the same size as the vertices with colors
80+
colors.resize(
81+
vertices.len(),
82+
svg_color_to_bevy(&stroke.paint, stroke.opacity.to_u8()),
83+
);
84+
}
85+
}
86+
}
87+
88+
convert_buffers_into_mesh(vertices, indices, colors)
89+
}
90+
91+
/// Convert a SVG path fill to a mesh.
92+
#[tracing::instrument(name = "converting SVG path fill to vertex buffers")]
93+
fn svg_path_fill_to_vertex_buffers(
94+
path: &Path,
95+
indices_offset: u32,
96+
) -> VertexBuffers<[f32; 3], u32> {
97+
bevy::log::trace!("Converting SVG path fill to vertex buffers");
98+
99+
// The resulting vertex and index buffers
100+
let mut buffers: VertexBuffers<[f32; 3], u32> = VertexBuffers::new();
101+
102+
// Use our custom vertex constructor to create a bevy vertex buffer
103+
let mut vertex_builder = BuffersBuilder::new(
104+
&mut buffers,
105+
BevyVertexConstructor {
106+
transform: path.transform,
107+
},
108+
);
109+
110+
// Tesselate the fill
111+
let mut tessellator = FillTessellator::new();
112+
let result = tessellator.tessellate(
113+
PathConvIter::from_svg_path(&path),
114+
&FillOptions::default(),
115+
&mut vertex_builder,
116+
);
117+
assert!(result.is_ok());
118+
119+
// Add the offset so multiple items can be merged
120+
if indices_offset != 0 {
121+
buffers
122+
.indices
123+
.iter_mut()
124+
.for_each(|index| *index += indices_offset);
125+
}
126+
127+
buffers
128+
}
129+
130+
/// Convert a SVG path stroke to a mesh.
131+
#[tracing::instrument(name = "converting SVG path stroke to vertex buffers")]
132+
fn svg_path_stroke_to_vertex_buffers(
133+
path: &Path,
134+
indices_offset: u32,
135+
) -> VertexBuffers<[f32; 3], u32> {
136+
bevy::log::trace!("Converting SVG path stroke to vertex buffers");
137+
138+
// The resulting vertex and index buffers
139+
let mut buffers: VertexBuffers<[f32; 3], u32> = VertexBuffers::new();
140+
141+
// Use our custom vertex constructor to create a bevy vertex buffer
142+
let mut vertex_builder = BuffersBuilder::new(
143+
&mut buffers,
144+
BevyVertexConstructor {
145+
transform: path.transform,
146+
},
147+
);
148+
149+
// Tesselate the fill
150+
let mut tessellator = StrokeTessellator::new();
151+
let result = tessellator.tessellate(
152+
PathConvIter::from_svg_path(&path),
153+
&StrokeOptions::default(),
154+
&mut vertex_builder,
155+
);
156+
assert!(result.is_ok());
157+
158+
// Add the offset so multiple items can be merged
159+
if indices_offset != 0 {
160+
buffers
161+
.indices
162+
.iter_mut()
163+
.for_each(|index| *index += indices_offset);
164+
}
165+
166+
buffers
167+
}
168+
169+
/// Convert the vertex buffers to a mesh.
170+
#[tracing::instrument(name = "converting vertex and index buffers into mesh")]
171+
fn convert_buffers_into_mesh(
172+
vertices: Vec<[f32; 3]>,
173+
indices: Vec<u32>,
174+
colors: Vec<[f32; 4]>,
175+
) -> Mesh {
176+
bevy::log::trace!("Creating mesh");
177+
178+
// Create the mesh
179+
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
180+
181+
// Set the indices
182+
mesh.set_indices(Some(Indices::U32(indices)));
183+
184+
// Set the vertices
185+
mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vertices);
186+
187+
// Set the colors
188+
//mesh.set_attribute(Mesh::ATTRIBUTE_COLOR, colors);
189+
190+
mesh
191+
}
192+
193+
// Taken from https://github.com/nical/lyon/blob/74e6b137fea70d71d3b537babae22c6652f8843e/examples/wgpu_svg/src/main.rs
194+
struct PathConvIter<'a> {
195+
iter: Iter<'a, PathSegment>,
196+
prev: Point,
197+
first: Point,
198+
needs_end: bool,
199+
deferred: Option<PathEvent>,
200+
scale: Transform2D<f32>,
201+
}
202+
203+
impl<'a> PathConvIter<'a> {
204+
/// Convert a SVG path to the iterator for the tessellator.
205+
pub fn from_svg_path(path: &'a Path) -> Self {
206+
Self {
207+
iter: path.data.iter(),
208+
first: Point::zero(),
209+
prev: Point::zero(),
210+
deferred: None,
211+
needs_end: false,
212+
// For some reason the local transform of some paths has negative scale values
213+
// Here we correct to positive values
214+
scale: Transform2D::scale(
215+
if path.transform.a < 0.0 { -1.0 } else { 1.0 },
216+
if path.transform.d < 0.0 { -1.0 } else { 1.0 },
217+
),
218+
}
219+
}
220+
}
221+
222+
impl<'l> Iterator for PathConvIter<'l> {
223+
type Item = PathEvent;
224+
225+
fn next(&mut self) -> Option<Self::Item> {
226+
if self.deferred.is_some() {
227+
return self.deferred.take();
228+
}
229+
let mut return_event = None;
230+
let next = self.iter.next();
231+
match next {
232+
Some(PathSegment::MoveTo { x, y }) => {
233+
if self.needs_end {
234+
let last = self.prev;
235+
let first = self.first;
236+
self.needs_end = false;
237+
self.prev = Point::new(*x as f32, *y as f32);
238+
self.deferred = Some(PathEvent::Begin { at: self.prev });
239+
self.first = self.prev;
240+
return_event = Some(PathEvent::End {
241+
last,
242+
first,
243+
close: false,
244+
});
245+
} else {
246+
self.first = Point::new(*x as f32, *y as f32);
247+
return_event = Some(PathEvent::Begin { at: self.first });
248+
}
249+
}
250+
Some(PathSegment::LineTo { x, y }) => {
251+
self.needs_end = true;
252+
let from = self.prev;
253+
self.prev = Point::new(*x as f32, *y as f32);
254+
return_event = Some(PathEvent::Line {
255+
from,
256+
to: self.prev,
257+
});
258+
}
259+
Some(PathSegment::CurveTo {
260+
x1,
261+
y1,
262+
x2,
263+
y2,
264+
x,
265+
y,
266+
}) => {
267+
self.needs_end = true;
268+
let from = self.prev;
269+
self.prev = Point::new(*x as f32, *y as f32);
270+
return_event = Some(PathEvent::Cubic {
271+
from,
272+
ctrl1: Point::new(*x1 as f32, *y1 as f32),
273+
ctrl2: Point::new(*x2 as f32, *y2 as f32),
274+
to: self.prev,
275+
});
276+
}
277+
Some(PathSegment::ClosePath) => {
278+
self.needs_end = false;
279+
self.prev = self.first;
280+
return_event = Some(PathEvent::End {
281+
last: self.prev,
282+
first: self.first,
283+
close: true,
284+
});
285+
}
286+
None => {
287+
if self.needs_end {
288+
self.needs_end = false;
289+
let last = self.prev;
290+
let first = self.first;
291+
return_event = Some(PathEvent::End {
292+
last,
293+
first,
294+
close: false,
295+
});
296+
}
297+
}
298+
}
299+
300+
return return_event.map(|event| event.transformed(&self.scale));
301+
}
302+
}
303+
304+
/// Convert an SVG color to a Bevy color.
305+
fn svg_color_to_bevy(paint: &Paint, opacity: u8) -> [f32; 4] {
306+
return match paint {
307+
Paint::Color(color) => dbg!(Color::rgba_u8(color.red, color.green, color.blue, opacity)),
308+
// We only support plain colors
309+
_ => Color::default(),
310+
}
311+
.as_rgba_f32();
312+
}

src/draw/mod.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
use bevy::prelude::{App, Msaa, Plugin};
2-
use bevy_svg::prelude::SvgPlugin;
1+
pub mod mesh;
2+
pub mod svg;
3+
4+
use self::svg::SvgAssetLoader;
5+
use bevy::prelude::{AddAsset, App, Msaa, Plugin};
36

47
/// The plugin to manage rendering.
58
pub struct DrawPlugin;
@@ -8,6 +11,7 @@ impl Plugin for DrawPlugin {
811
fn build(&self, app: &mut App) {
912
// Smooth anti aliasing
1013
app.insert_resource(Msaa { samples: 4 })
11-
.add_plugin(SvgPlugin);
14+
.init_asset_loader::<SvgAssetLoader>()
15+
.add_startup_system(svg::setup);
1216
}
1317
}

0 commit comments

Comments
 (0)