Skip to content
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

CubicSegment lacks functionality of CubicCurve #17642

Open
hocop opened this issue Feb 2, 2025 · 0 comments · May be fixed by #17645
Open

CubicSegment lacks functionality of CubicCurve #17642

hocop opened this issue Feb 2, 2025 · 0 comments · May be fixed by #17645
Labels
A-Math Fundamental domain-agnostic mathematical operations C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Ready-For-Implementation This issue is ready for an implementation PR. Go for it! X-Uncontroversial This work is generally agreed upon

Comments

@hocop
Copy link

hocop commented Feb 2, 2025

What problem does this solve or what need does it fill?

CubicCurve, which is a Vec<CubicSegment>, can be used in many ways, e.g. creating roads and paths.

However, in many cases CubicSegment is very useful by itself. For example, I want to check if my entity will not collide with anything, while following a curved path. But the check is still local enough, so I don't need the whole CubicCurve

Performance considerations.

CubicCurve contains a Vec, which is heap allocated. It is expensive, if spawned every frame for many entities.

CubicSegment, on the other hand, is fixed size, which is ideal for such scenario.

However, the implementation of CubicSegment::new_bezier includes creating a CubicCurve of size 1, only to take the first element later. It is still an allocation, and in this case, an unnecessary one.

What solution would you like?

  1. I want be able to create CubicSegment from 4 control points, not just 2. Right now it seems like it is just intended for animation easing purposes.
  2. I would like it to be as fast as can be expected from creating a fixed size struct.
  3. I would like CubicSegment::iter_positions and other cool functions that CubicCurve has

What alternative(s) have you considered?

I implemented my own hacky trait to have this functionality:

pub trait CubicSegment4 {
    fn new_bezier_4(p1: impl Into<Vec2>, p2: impl Into<Vec2>, p3: impl Into<Vec2>, p4: impl Into<Vec2>) -> Self;

    fn iter_positions(self, subdivisions: usize) -> impl Iterator<Item=Vec2>;
}

impl CubicSegment4 for CubicSegment<Vec2> {
    fn new_bezier_4(p1: impl Into<Vec2>, p2: impl Into<Vec2>, p3: impl Into<Vec2>, p4: impl Into<Vec2>) -> Self {
        // A derivation for this matrix can be found in "General Matrix Representations for B-splines" by Kaihuai Qin.
        // <https://xiaoxingchen.github.io/2020/03/02/bspline_in_so3/general_matrix_representation_for_bsplines.pdf>
        // See section 4.2 and equation 11.
        let char_matrix = [
            [1., 0., 0., 0.],
            [-3., 3., 0., 0.],
            [3., -6., 3., 0.],
            [-1., 3., -3., 1.],
        ];
        let p: [Vec2; 4] = [p1.into(), p2.into(), p3.into(), p4.into()];
        coefficients(p, char_matrix)
    }

    fn iter_positions(self, subdivisions: usize) -> impl Iterator<Item=Vec2> {
        let step = 1.0 / subdivisions as f32;
        (0..=subdivisions).map(move |i| i as f32 * step)
        .map(move |t| self.position(t))
    }
}

fn coefficients<P: VectorSpace>(p: [P; 4], char_matrix: [[f32; 4]; 4]) -> CubicSegment<P> {
    let [c0, c1, c2, c3] = char_matrix;
    // These are the polynomial coefficients, computed by multiplying the characteristic
    // matrix by the point matrix.
    let coeff = [
        p[0] * c0[0] + p[1] * c0[1] + p[2] * c0[2] + p[3] * c0[3],
        p[0] * c1[0] + p[1] * c1[1] + p[2] * c1[2] + p[3] * c1[3],
        p[0] * c2[0] + p[1] * c2[1] + p[2] * c2[2] + p[3] * c2[3],
        p[0] * c3[0] + p[1] * c3[1] + p[2] * c3[2] + p[3] * c3[3],
    ];
    CubicSegment::<P> { coeff }
}

It does the thing without memory allocations. But has some duplicated code.

Example usage:

pub fn get_curve(
    pos_1: Vec2,
    pos_2: Vec2,
    anchor: Option<Vec2>
) -> CubicSegment<Vec2> {
    if let Some(anchor) = anchor {
        // Curve with anchor point
        CubicSegment::new_bezier_4(
            pos_1,
            pos_1.lerp(anchor, 0.66),
            pos_2.lerp(anchor, 0.66),
            pos_2
        )
    } else {
        // Straight line
        CubicSegment::new_bezier_4(
            pos_1,
            pos_1.lerp(pos_2, 0.33),
            pos_1.lerp(pos_2, 0.66),
            pos_2
        )
    }
}

Additional context

Looking at code, looks like CubicSegment is being sort of ignored in favor of the CubicCurve. I think many functions like the new_bezier and new_bezier_4 should really be implemented on the base structure.

@hocop hocop added C-Feature A new feature, making something new possible S-Needs-Triage This issue needs to be labelled labels Feb 2, 2025
@alice-i-cecile alice-i-cecile added C-Usability A targeted quality-of-life change that makes Bevy easier to use A-Math Fundamental domain-agnostic mathematical operations S-Ready-For-Implementation This issue is ready for an implementation PR. Go for it! X-Uncontroversial This work is generally agreed upon D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes and removed C-Feature A new feature, making something new possible S-Needs-Triage This issue needs to be labelled labels Feb 2, 2025
@hocop hocop linked a pull request Feb 2, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Math Fundamental domain-agnostic mathematical operations C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Ready-For-Implementation This issue is ready for an implementation PR. Go for it! X-Uncontroversial This work is generally agreed upon
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants