-
-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Head for vector graphics #3
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
When implementing custom e.g. fn build_fill(&self, fill: &Fill, scene: &mut vello::Scene) {
scene.fill(
fill.style,
default(),
&fill.brush.value,
Some(fill.brush.transform),
&match self.head {
ArrowHead::Triangle => kurbo::Triangle::new(...),
ArrowHead::Square => kurbo::Rect::new(...),
ArrowHead::Circle => kurbo::Circle::new(...),
}
);
} the match expression should evaluate to a single type. So i feel like
yeah this makes sense, however im unsure of a better way to do this other than updating every |
You could do it in a regular system, just like how shapes are being built into bevy_vello_graphics/src/lib.rs Lines 93 to 122 in 539b2b3
Possibly something like this: #[allow(clippy::type_complexity)]
fn build_stroke_only_vector<Vector: VelloVector + Component>(
mut q_vectors: Query<
- (&Vector, &Stroke, &mut VelloScene),
+ (&Vector, &Stroke, Option<&ArrowHead>, &mut VelloScene),
- (Without<Fill>, Or<(Changed<Vector>, Changed<Stroke>)>),
+ (Without<Fill>, Or<(Changed<Vector>, Changed<Stroke>, Changed<ArrowHead>)>),
>,
) { |
Alright ill do that, thank you. although im worried that in the future this might become cluttered if we keep just adding parameters. anyways, It seems like e.g. #[allow(clippy::type_complexity)]
fn build_stroke_only_vector<Vector: VelloVector + Component>(
mut q_vectors: Query<
(&Vector, &Stroke, Option<&ArrowHead>, &mut VelloScene),
(
Without<Fill>,
Or<(Changed<Vector>, Changed<Stroke>)>,
Changed<ArrowHead>,
),
>,
) {
for (vector, stroke, arrow, mut scene) in q_vectors.iter_mut() {
*scene = VelloScene::default();
// Build the vector to the VelloScene
vector.build_stroke(stroke, &mut scene);
// Attach the possible arrow
if let Some(arrow) = arrow {
arrow.build_stroke(stroke, &mut scene);
}
}
} here, // Attach the possible arrow
if let Some(arrow) = arrow {
vello_arrow.attach(vector.shape());
vello_arrow.build_stroke(stroke, &mut scene);
} see 0038a11 |
src/arrow.rs
Outdated
self | ||
} | ||
|
||
/// Attach [`VelloArrow`] to `shape` with its position and rotation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we start doing docs just yet?
src/arrow.rs
Outdated
} | ||
|
||
/// Attach [`VelloArrow`] to `shape` with its position and rotation | ||
pub fn attach<Shape: kurbo::Shape>(&mut self, shape: Shape) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we return self (with or without mutating it?) or just keep it as is? there are benefits to both but im not sure the kinda style you are looking for.
@aymey what do you think if we implement a new trait that specifies how arrows will be drawn for each trait: pub trait VectorBorder {
fn border_position(time: f32) -> Vec2;
fn border_tangent(time: f32) -> Vec2;
// etc.
} Then we create a new system just for arrow: fn build_stroke_only_arrow<Vector: VectorBorder + Component>(
mut q_vectors: Query<
(&ArrowHead, &Vector, &Stroke, &mut VelloScene),
(Without<Fill>, Or<(Changed<ArrowHead>, Changed<Vector>, Changed<Stroke>)
>,
)
// implement for fill only & fill + stroke In these systems, utilize the To prevent cluttering, you can also implement these calculations as an impl ArrowHead {
fn build_line<T: VectorBorder>(&self, vector: T) -> impl Shape {
// ...
}
// implement for circle and line as well
} And include all these into the pub(crate) fn build_vector<Vector: VelloVector + Component>() -> SystemConfigs {
(
(build_fill_only_vector::<Vector>, build_fill_only_arrow::<Vector>).chain(),
(build_stroke_only_vector::<Vector>, build_stroke_only_arrow::<Vector>).chain(),
(build_fill_and_stroke_vector::<Vector>, build_fill_and_stroke_arrow::<Vector>).chain(),
)
.into_configs()
} DiscussionOptionally, I think if it's really hard to work with different shapes, we could split |
I think as a first pass, we can just implement |
if you look at I would rather it be dynamic but i understand the annoyance with it (like possibly wrong data and less elegant code). I do really like your
im slightly against this because i want the possibility in the future to add any shape (even a custom one the user creates) to be dynamically appended to another as a arrow head. Now that im writing this i realise that we could scrap Im still happy to continue with your idea as you've described above, what do you think? |
Yes, this idea has been floating in my mind for a while now since I wrote my last post hahaha. If that's the case, the functionality of this component will be to render at the border of any shape given an My IdeaWe still reuse the We introduce a new #[derive(Component, ...)]
pub struct Head {
pub shape_id: ShapeId,
pub scale: f32,
pub offset: f32,
pub rotation_offset: f32,
} Now, in order to know what to draw during the scene building phase, we introduce a new resource called #[derive(Resource, ...)]
pub struct Shapes {
pub scenes: HashMap<ShapeId, vello::Scene>,
}
pub struct ShapeId(Uuid); Then, during the building phase, we can do something like this: let translation = vector.border_translation(t);
let tangent = vector.border_tangent(t);
let rotation = // calculate rotation using tangent and rotation_offset
let transform = kurbo::Affine::default().with_translation(translation).then_rotate(rotation).then_scale(head.scale);
let head_scene = shapes.scenes.get(head.shape_id);
scene.append(head_scene, Some(transform)); |
Yes that's perfect! ill start implementing this tomorrow |
Hey, sorry Ive been neglecting this pr for a while. Ive had a lot of school stuff to do but ill be fully back after my exams in like two weeks. For now, is the current changes what you had in mind? im a bit unsure especially with the "lib.rs" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume some parts of the code on VectorBoder
trait impls are WIP so I leave them as is for now, I left a few suggestions as well, overall great stuff! Thanks as always!
btw you can also run |
ah yeah sorry i always forget. |
should i revert 0062a9c? |
i think it would also look better if we lerp rotating around the vertices of the rect and bezpath, should i do this? |
for rect it's alright, I think for bezpath, might wanna just leave it as is, some people might not close the path, so it's up to them to handle it I think... |
Doesn't the I think we will need to change that to the more optimized version anyway as no one is going to be reading the code from the user stand point hahahaha. I can get on doing that as well. |
it did until 0062a9c which we needed to do because we had no border impls for the shapes. Now that we have them i will undo it |
i would still need to benchmark but it looks like it wont make a difference, if we expand the e.g. for bezpath PathEl::CurveTo(p1, p2, p3) => {
let current = current.to_vec2();
let p1 = p1.to_vec2();
let p2 = p2.to_vec2();
let p3 = p3.to_vec2();
let d = (1.0 - t) * (1.0 - t) * current + 2.0 * t * (1.0 - t) * p1 + t * t * p2;
let e = (1.0 - t) * (1.0 - t) * p1 + 2.0 * t * (1.0 - t) * p2 + t * t * p3;
(e.y - d.y).atan2(e.x - d.x)
} however, the #[inline]
pub fn lerp(self, other: Point, t: f64) -> Point {
self.to_vec2().lerp(other.to_vec2(), t).to_point()
} and the /// Linearly interpolate between two vectors.
#[inline]
pub fn lerp(self, other: Vec2, t: f64) -> Vec2 {
self + t * (other - self)
} and both of these are inlined so the calls will be optimized away anyways. so overall i dont think there would be any benefit, although i still need to benchmark cause maybe the intermediate point variables of |
honestly i have no clue how this wasnt the first thing i thought of but whatever
also for rect (and bezpath but thats gonna be alot more difficult #3 (comment)) we probably need to keep a consistent speed but im not sure how |
That's a pretty hard and "ugly" problem to solve, I don't think it's in the scope of this PR. The way most people do is to chop the curve up into pieces of straight lines and estimate (yes you did not read wrong, estimate). |
I see thanks for the exploration! I think we can look into optimizations / speed up in a separate PR/issue 🤔 . Let's push for feature completeness first I think haha. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in general most of the rough edges are catered for, right now all that's left is to check on missing docs and update the README 😄 ! Thanks for your awesome contribution @aymey !
No problem it was fun and you did most of the work anyways lol. but whats going on with the example time looping for line and bezpath? shouldn't they continue going backwards after time > 1? |
My reason was that line and bezpath are open ended shapes, so they should be allowed to go off shape? |
yeah you are right it defiantly looks better and if the user wanted to loop around then they can manually adjust time for that. |
migrated from voxell-tech/falcon#49
yeah, we can do that for now.
Oh yeah much better, i hadn't thought of this.
Resolves #1