Skip to content

Commit

Permalink
Merge pull request #3 from aymey/main
Browse files Browse the repository at this point in the history
Head for vector graphics
  • Loading branch information
nixon-voxell authored Aug 12, 2024
2 parents 18cba86 + 4c308ac commit f606913
Show file tree
Hide file tree
Showing 15 changed files with 1,287 additions and 161 deletions.
Binary file added .github/assets/hello_world.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
549 changes: 547 additions & 2 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ edition = "2021"
license = "MIT OR Apache-2.0"

[dependencies]
bevy = { version = "0.14", default-features = false }
bevy_app = "0.14"
bevy_ecs = "0.14"
bevy_math = "0.14"
bevy_color = "0.14"
bevy_utils = "0.14"
bevy_vello = "0.5"

[dev-dependencies]
bevy = "0.14"

[lints.clippy]
redundant_type_annotations = "warn"
bool_comparison = "allow"
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Bevy Vello Graphics

Bevy Vello Graphics simplifies the creation and rendering of [Vello](https://github.com/linebender/vello) vector graphics in Bevy. Each vector graphic is treated as a component and rendered using [Bevy Vello](https://github.com/linebender/bevy_vello). Vector graphics that are supported include:
Bevy Vello Graphics simplifies the creation and rendering of [Vello](https://github.com/linebender/vello) vector graphics in Bevy.

![hello_world](./.github/assets/hello_world.png)

Each vector graphic is treated as a component and rendered using [Bevy Vello](https://github.com/linebender/bevy_vello). Vector graphics that are supported include:

- Line
- Rect
Expand Down
55 changes: 47 additions & 8 deletions examples/hello_world.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bevy::{color::palettes::css, math::DVec2, prelude::*};
use bevy_vello_graphics::prelude::*;
use bevy_vello_graphics::{bevy_vello::prelude::*, prelude::*};

fn main() {
App::new()
Expand All @@ -8,6 +8,7 @@ fn main() {
// Custom Plugins
.add_plugins(VelloGraphicsPlugin)
.add_systems(Startup, (setup, render_shapes))
.add_systems(Update, animation)
.run();
}

Expand All @@ -16,11 +17,28 @@ fn setup(mut commands: Commands) {
}

fn render_shapes(mut commands: Commands) {
let mut triangle_path = kurbo::BezPath::new();
triangle_path.move_to(kurbo::Point::new(0.0, -10.0));
triangle_path.line_to(kurbo::Point::new(0.0, 10.0));
triangle_path.line_to(kurbo::Point::new(20.0, 0.0));
triangle_path.close_path();

let triangle = VelloBezPath::new().with_path(triangle_path);
let head_style = (
HeadFill(Fill::new().with_color(Color::WHITE.with_alpha(0.6))),
HeadStroke(
Stroke::from_style(kurbo::Stroke::new(4.0).with_join(kurbo::Join::Miter))
.with_color(Color::BLACK.with_alpha(0.6)),
),
);

// Line
let line = (
VelloLine::new(DVec2::new(0.0, 100.0), DVec2::new(0.0, -100.0)),
VelloLine::new(DVec2::new(100.0, 100.0), DVec2::new(0.0, -100.0)),
Stroke::new(5.0).with_color(Color::WHITE),
Transform::from_xyz(-300.0, 0.0, 0.0),
HeadBundle::new(triangle.clone()),
head_style.clone(),
);

// Rectangle
Expand All @@ -29,6 +47,8 @@ fn render_shapes(mut commands: Commands) {
Fill::new().with_color(css::ORANGE.into()),
Stroke::new(5.0).with_color(css::RED.into()),
Transform::from_xyz(-100.0, 0.0, 0.0),
HeadBundle::new(triangle.clone()),
head_style.clone(),
);

// Circle
Expand All @@ -37,20 +57,39 @@ fn render_shapes(mut commands: Commands) {
Fill::new().with_color(css::YELLOW_GREEN.into()),
Stroke::new(5.0).with_color(css::DARK_GREEN.into()),
Transform::from_xyz(100.0, 0.0, 0.0),
HeadBundle::new(triangle.clone()),
head_style.clone(),
);

let mut bez_path = kurbo::BezPath::new();
bez_path.move_to((300.0, 100.0));
bez_path.curve_to((200.0, 50.0), (400.0, -50.0), (300.0, -100.0));
bez_path.move_to((0.0, 100.0));
bez_path.curve_to((-100.0, 50.0), (100.0, -50.0), (0.0, -100.0));
bez_path.close_path();

// Bézier Path
let bezier_path = (
VelloBezPath::new().with_path(bez_path),
Stroke::new(4.0).with_color(css::YELLOW.into()),
Transform::from_xyz(300.0, 0.0, 0.0),
HeadBundle::new(triangle),
head_style,
);

commands.spawn((VelloSceneBundle::default(), line));
commands.spawn((VelloSceneBundle::default(), rect));
commands.spawn((VelloSceneBundle::default(), circle));
commands.spawn((VelloSceneBundle::default(), bezier_path));
commands.spawn(VelloSceneBundle::default()).insert(line);
commands.spawn(VelloSceneBundle::default()).insert(rect);
commands.spawn(VelloSceneBundle::default()).insert(circle);
commands
.spawn(VelloSceneBundle::default())
.insert(bezier_path);
}

fn animation(mut q_heads: Query<&mut Head>, time: Res<Time>) {
// Overshoots for stability check
let mut factor = time.elapsed_seconds_f64() * 0.5;
factor = factor.sin().remap(-1.0, 1.0, -0.2, 1.2);

for mut head in q_heads.iter_mut() {
head.time = factor;
// head.rotation_offset = std::f64::consts::TAU * factor;
}
}
219 changes: 175 additions & 44 deletions src/bezpath.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
use bevy::prelude::*;
use bevy_vello::prelude::*;
//! A Bevy friendly wrapper around [`kurbo::BezPath`] with tracing capability.
use super::VelloVector;
use bevy_ecs::prelude::*;
use bevy_math::DVec2;
use bevy_utils::prelude::*;
use bevy_vello::vello::kurbo;

use super::Vector;

/// Vello Bézier path component.
#[derive(Component, Debug, Clone)]
pub struct VelloBezPath {
/// Bézier path.
pub path: kurbo::BezPath,
pub trace: f32,
/// Tracing percentage from the start to the end of the entire Bézier path.
pub trace: f64,
}

impl VelloBezPath {
Expand All @@ -20,7 +26,7 @@ impl VelloBezPath {
self
}

pub fn with_trace(mut self, trace: f32) -> Self {
pub fn with_trace(mut self, trace: f64) -> Self {
self.trace = trace;
self
}
Expand All @@ -35,27 +41,24 @@ impl Default for VelloBezPath {
}
}

impl VelloVector for VelloBezPath {
impl Vector for VelloBezPath {
fn shape(&self) -> impl kurbo::Shape {
let pathels = self.path.elements();
// TODO(perf): Prevent from creating a new BezPath for each animation change.
// TODO(perf): Prevent from creating a new BezPath for every animation update.
let mut path = kurbo::BezPath::new();

let pathel_count = pathels.len();
let trace_raw = self.trace * pathel_count as f32;
let trace_raw = self.trace * pathel_count as f64;

let mut most_recent_initial = kurbo::Point::new(0.0, 0.0);
let mut most_recent_point = kurbo::Point::new(0.0, 0.0);

for (path_index, pathel) in pathels.iter().enumerate() {
let mut interp_value = trace_raw - path_index as f32;
let mut interp_value = trace_raw - path_index as f64;

// if interp_value <= 0.0 {
// pathels[path_index] = kurbo::PathEl::MoveTo(kurbo::Point::default());
// } else {
if interp_value > 0.0 {
// Clamp value within 1.0
interp_value = f32::min(interp_value, 1.0);
interp_value = f64::min(interp_value, 1.0);

match pathel {
kurbo::PathEl::MoveTo(p) => {
Expand Down Expand Up @@ -96,44 +99,172 @@ impl VelloVector for VelloBezPath {

path
}

fn border_translation(&self, time: f64) -> DVec2 {
let pathels = self.path.elements();
let pathel_count = pathels.len();

let fallback = pathels
.first()
.and_then(|path| path.end_point().map(|point| DVec2::new(point.x, point.y)))
.unwrap_or_default();

// Guarantee to have at least 2 path elements
if pathel_count < 2 {
return fallback;
}

let seg_count = pathel_count - 1;
let trace_raw = time * seg_count as f64;
let trace_index = i64::clamp(trace_raw as i64, 0, seg_count as i64 - 1) as usize;
let seg_index = trace_index + 1;

if let Some(segment) = self.path.get_seg(seg_index) {
let t = trace_raw - trace_index as f64;
return match segment {
kurbo::PathSeg::Line(line) => point_to_vec(kurbo::Point::lerp(line.p0, line.p1, t)),
kurbo::PathSeg::Quad(kurbo::QuadBez { p0, p1, p2 }) => {
point_to_vec(lerp_quad_point(p0, p1, p2, t))
}
kurbo::PathSeg::Cubic(kurbo::CubicBez { p0, p1, p2, p3 }) => {
point_to_vec(lerp_cubic_point(p0, p1, p2, p3, t))
}
};
}

// All else fails..
fallback
}

fn border_rotation(&self, time: f64) -> f64 {
let pathels = self.path.elements();
let pathel_count = pathels.len();

let fallback = 0.0;

// Guarantee to have at least 2 path elements
if pathel_count < 2 {
return fallback;
}

let seg_count = pathel_count - 1;
let trace_raw = time * seg_count as f64;
let trace_index = i64::clamp(trace_raw as i64, 0, seg_count as i64 - 1) as usize;
let seg_index = trace_index + 1;

if let Some(segment) = self.path.get_seg(seg_index) {
let t = trace_raw - trace_index as f64;
return match segment {
kurbo::PathSeg::Line(line) => (line.p1 - line.p0).angle(),
kurbo::PathSeg::Quad(kurbo::QuadBez { p0, p1, p2 }) => {
// kurbo::Point between p0 and p1
let x0 = kurbo::Point::lerp(p0, p1, t);
// kurbo::Point between p1 and p2
let x1 = kurbo::Point::lerp(p1, p2, t);
(x1.y - x0.y).atan2(x1.x - x0.x)
}
kurbo::PathSeg::Cubic(kurbo::CubicBez { p0, p1, p2, p3 }) => {
// point_to_vec(lerp_cubic_point(cubic.p0, cubic.p1, cubic.p2, cubic.p3, t))
// kurbo::Point between p0 and p1
let x0 = kurbo::Point::lerp(p0, p1, t);
// kurbo::Point between p1 and p2
let x1 = kurbo::Point::lerp(p1, p2, t);
// kurbo::Point between p2 and p3
let x2 = kurbo::Point::lerp(p2, p3, t);
// kurbo::Point between x0 and x1
let y0 = kurbo::Point::lerp(x0, x1, t);
// kurbo::Point between x1 and x2
let y1 = kurbo::Point::lerp(x1, x2, t);

(y1.y - y0.y).atan2(y1.x - y0.x)
}
};
}

// All else fails..
fallback
}
}

fn interp_pathel(p0: kurbo::Point, pathel: kurbo::PathEl, t: f32) -> kurbo::PathEl {
/// Interpolate [`kurbo::PathEl`].
fn interp_pathel(p0: kurbo::Point, pathel: kurbo::PathEl, t: f64) -> kurbo::PathEl {
if t == 1.0 {
return pathel;
}

match pathel {
kurbo::PathEl::MoveTo(p1) => kurbo::PathEl::MoveTo(kurbo::Point::lerp(p0, p1, t as f64)),
kurbo::PathEl::LineTo(p1) => kurbo::PathEl::LineTo(kurbo::Point::lerp(p0, p1, t as f64)),
kurbo::PathEl::QuadTo(p1, p2) => {
let t = t as f64;
// Point between p0 and p1
let x0 = kurbo::Point::lerp(p0, p1, t);
// Point between p1 and p2
let x1 = kurbo::Point::lerp(p1, p2, t);
// Point on curve
let end_p = kurbo::Point::lerp(x0, x1, t);

kurbo::PathEl::QuadTo(x0, end_p)
}
kurbo::PathEl::CurveTo(p1, p2, p3) => {
let t = t as f64;
// Point between p0 and p1
let x0 = kurbo::Point::lerp(p0, p1, t);
// Point between p1 and p2
let x1 = kurbo::Point::lerp(p1, p2, t);
// Point between p2 and p3
let x2 = kurbo::Point::lerp(p2, p3, t);
// Point between x0 and x1
let y0 = kurbo::Point::lerp(x0, x1, t);
// Point between x1 and x2
let y1 = kurbo::Point::lerp(x1, x2, t);
// Point on curve
let end_p = kurbo::Point::lerp(y0, y1, t);

kurbo::PathEl::CurveTo(x0, y0, end_p)
}
kurbo::PathEl::MoveTo(p1) => kurbo::PathEl::MoveTo(kurbo::Point::lerp(p0, p1, t)),
kurbo::PathEl::LineTo(p1) => kurbo::PathEl::LineTo(kurbo::Point::lerp(p0, p1, t)),
kurbo::PathEl::QuadTo(p1, p2) => lerp_quad_pathel(p0, p1, p2, t),
kurbo::PathEl::CurveTo(p1, p2, p3) => lerp_cubic_pathel(p0, p1, p2, p3, t),
kurbo::PathEl::ClosePath => kurbo::PathEl::ClosePath,
}
}

fn lerp_quad_pathel(p0: kurbo::Point, p1: kurbo::Point, p2: kurbo::Point, t: f64) -> kurbo::PathEl {
// kurbo::Point between p0 and p1
let x0 = kurbo::Point::lerp(p0, p1, t);
// kurbo::Point between p1 and p2
let x1 = kurbo::Point::lerp(p1, p2, t);
// kurbo::Point on curve
let end_p = kurbo::Point::lerp(x0, x1, t);

kurbo::PathEl::QuadTo(x0, end_p)
}

fn lerp_quad_point(p0: kurbo::Point, p1: kurbo::Point, p2: kurbo::Point, t: f64) -> kurbo::Point {
// kurbo::Point between p0 and p1
let x0 = kurbo::Point::lerp(p0, p1, t);
// kurbo::Point between p1 and p2
let x1 = kurbo::Point::lerp(p1, p2, t);
// kurbo::Point on curve
kurbo::Point::lerp(x0, x1, t)
}

fn lerp_cubic_pathel(
p0: kurbo::Point,
p1: kurbo::Point,
p2: kurbo::Point,
p3: kurbo::Point,
t: f64,
) -> kurbo::PathEl {
// kurbo::Point between p0 and p1
let x0 = kurbo::Point::lerp(p0, p1, t);
// kurbo::Point between p1 and p2
let x1 = kurbo::Point::lerp(p1, p2, t);
// kurbo::Point between p2 and p3
let x2 = kurbo::Point::lerp(p2, p3, t);
// kurbo::Point between x0 and x1
let y0 = kurbo::Point::lerp(x0, x1, t);
// kurbo::Point between x1 and x2
let y1 = kurbo::Point::lerp(x1, x2, t);
// kurbo::Point on curve
let end_p = kurbo::Point::lerp(y0, y1, t);

kurbo::PathEl::CurveTo(x0, y0, end_p)
}

fn lerp_cubic_point(
p0: kurbo::Point,
p1: kurbo::Point,
p2: kurbo::Point,
p3: kurbo::Point,
t: f64,
) -> kurbo::Point {
// kurbo::Point between p0 and p1
let x0 = kurbo::Point::lerp(p0, p1, t);
// kurbo::Point between p1 and p2
let x1 = kurbo::Point::lerp(p1, p2, t);
// kurbo::Point between p2 and p3
let x2 = kurbo::Point::lerp(p2, p3, t);
// kurbo::Point between x0 and x1
let y0 = kurbo::Point::lerp(x0, x1, t);
// kurbo::Point between x1 and x2
let y1 = kurbo::Point::lerp(x1, x2, t);
// kurbo::Point on curve
kurbo::Point::lerp(y0, y1, t)
}

fn point_to_vec(point: kurbo::Point) -> DVec2 {
DVec2::new(point.x, point.y)
}
Loading

0 comments on commit f606913

Please sign in to comment.