-
-
Couldn't load subscription status.
- Fork 4.2k
Implement TryStableInterpolate.
#21633
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -396,7 +396,7 @@ impl NormedVectorSpace for f64 { | |
| /// ```text | ||
| /// top curve = u.interpolate_stable(v, t) | ||
| /// | ||
| /// t0 => p t1 => q | ||
| /// t0 => p t1 => q | ||
| /// |-------------|---------|-------------| | ||
| /// 0 => u / \ 1 => v | ||
| /// / \ | ||
|
|
@@ -538,6 +538,58 @@ all_tuples_enumerated!( | |
| T | ||
| ); | ||
|
|
||
| /// Why the interpolation failed. | ||
| #[derive(Clone, Debug)] | ||
| pub enum InterpolationError { | ||
| /// The values to be interpolated are not in the same units. | ||
| MismatchedUnits, | ||
| } | ||
|
|
||
| /// A trait that indicates that a value _may_ be interpolable via [`StableInterpolate`]. An | ||
| /// interpolation may fail if the values have different units - for example, attempting to | ||
| /// interpolate between [`Val::Px`] and [`Val::Percent`] will fail, | ||
| /// even though they are the same Rust type. | ||
| /// | ||
| /// The motivating case for this trait is animating UI entities with [`Val`] | ||
| /// properties, which, because they are enums, cannot be interpolated in the normal way. This same | ||
| /// concept can be extended to other types as well. | ||
| /// | ||
| /// Fallible interpolation can be used for animated transitions, which can be set up to fail | ||
| /// gracefully if there's a mismatch of units. For example, the a transition could smoothly | ||
| /// go from `Val::Px(10)` to `Val::Px(20)`, but if the user attempts to go from `Val::Px(10)` to | ||
| /// `Val::Percent(10)`, the animation player can detect the failure and simply snap to the new | ||
| /// value without interpolating. | ||
| /// | ||
| /// An animation clip system can incorporate fallible interpolation to support a broad set of | ||
| /// sequenced parameter values. This can include numeric types, which always interpolate, | ||
| /// enum types, which may or may not interpolate depending on the units, and non-interpolable | ||
| /// types, which always jump immediately to the new value without interpolation. This meaas, for | ||
| /// example, that you can have an animation track whose value type is a boolean or a string. | ||
| /// | ||
| /// Interpolation for simple number and coordinate types will always succeed, as will any type | ||
| /// that implements [`StableInterpolate`]. Types which have different variants such as | ||
| /// [`Val`] and [`Color`] will only fail if the units are different. | ||
| /// Note that [`Color`] has its own, non-fallible mixing methods, but those entail | ||
| /// automatically converting between different color spaces, and is both expensive and complex. | ||
| /// [`TryStableInterpolate`] is more conservative, and doesn't automatically convert between | ||
| /// color spaces. This produces a color interpolation that is has more predictable performance. | ||
| /// | ||
| /// [`Val::Px`]: https://docs.rs/bevy/latest/bevy/ui/enum.Val.html | ||
| /// [`Val::Percent`]: https://docs.rs/bevy/latest/bevy/ui/enum.Val.html | ||
| /// [`Val`]: https://docs.rs/bevy/latest/bevy/ui/struct.enum.html | ||
| /// [`Color`]: https://docs.rs/bevy/latest/bevy/color/enum.Color.html | ||
| pub trait TryStableInterpolate: Clone { | ||
| /// Attempt to interpolate the value. This may fail if the two interpolation values have | ||
| /// different units, or if the type is not interpolable. | ||
| fn try_interpolate_stable(&self, other: &Self, t: f32) -> Result<Self, InterpolationError>; | ||
| } | ||
|
|
||
| impl<T: StableInterpolate> TryStableInterpolate for T { | ||
| fn try_interpolate_stable(&self, other: &Self, t: f32) -> Result<Self, InterpolationError> { | ||
| Ok(self.interpolate_stable(other, t)) | ||
| } | ||
| } | ||
|
Comment on lines
+587
to
+591
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Huh, so this ended up working? I'm curious what was different about how you initially tested it, that caused the error you were reporting. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it has to do with the fact that the impls are in the same module as the target types instead of being in the same module as the trait definition. But to be honest, I'm not really sure why it works. |
||
|
|
||
| /// A type that has tangents. | ||
| pub trait HasTangent { | ||
| /// The tangent type. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| --- | ||
| title: Fallible Interpolation | ||
| authors: ["@viridia"] | ||
| pull_requests: [21633] | ||
| --- | ||
|
|
||
| ## Fallible Interpolation | ||
|
|
||
| The `StableInterpolate` trait is great, but sadly there's one important type that it doesn't work | ||
| with: The `Val` type from `bevy_ui`. The reason is that `Val` is an enum, representing different | ||
| length units such as pixels and percentages, and it's not generally possible or even meaningful to | ||
| try and interpolate between different units. | ||
|
|
||
| However, the use cases for wanting to animate `Val` don't require mixing units: often we just want | ||
| to slide or stretch the length of a widget such as a toggle switch. We can do this so long as we | ||
| check at runtime that both interpolation control points are in the same units. | ||
|
|
||
| The new `TryStableInterpolate` trait introduces the idea of interpolation that can fail, by returning | ||
| a `Result`. Note that "failure" in this case is not necessarily bad: it just means that the | ||
| animation player will need to modify the parameter in some other way, such as "snapping" or | ||
| "jumping" to the new keyframe without smoothly interpolating. This lets us create complex animations | ||
| that incorporate both kinds of parameters: ones that interpolate, and ones that don't. | ||
|
|
||
| There's a blanket implementation of `TryStableInterpolate` for all types that impl | ||
| `StableInterpolate`, and these can never fail. There are additional impls for `Color` and `Val` | ||
| which can fail if the control points are not in the same units / color space. |
Uh oh!
There was an error while loading. Please reload this page.
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.
Me thinks it's best to let the user specify an error type, since interpolation can fail for reasons specific to the type being interpolated.
This is similar to what
TryFromdoes, since conversion can fail for a variety of reasons, usually reasons specific to the types being converted.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.
Making the error type depend on the value type would defeat one of my purposes: to have a single uniform animation clip system that can mix both interpolable and non-interpolable types. Basically something like
AnimatablePropertywhich can work forVal.To be perfectly honest, we don't actually need an error at all, we could instead just make it an
Optionand returnNone. We never actually look at the error code, it's only there for documentation purposes and because thetry_name prefix suggest aResultreturn type. The animation only cares that the interpolation succeeded, it's not like it actually logs an error or anything.Uh oh!
There was an error while loading. Please reload this page.
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.
Could you elaborate? I'm not sure how an error type that can be specified by each impl would defeat that purpose.
Also, I would recommend keeping it as a
Result, even if our error type is(). This is becauseOptionimplies that trying will always succeed but may sometimes return nothing, whereasResulttells the user that the operation may fail.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 guess that it could work, since the error value is ignored, but it seems a bit dodgy. Here's the way the animation code looks now:
Where
startandendare generics of typeTransitionProperty::ValueType:Adding an extra generic parameter for error type would mean that I wouldn't be able to treat different value types quite as uniformly as before.
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'm not sure what's meant by this. Can you give me an example of what is meant here?
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.
Poking @viridia - I wonder if you saw my comment just above this one.
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.
Sorry, I was waiting to see if anyone else had any thoughts on this issue.
For now I would like to keep this as simple as possible. I don't want to add an extra generic parameter unless there's an actual need for it, especially with the latest change where we only have one error type.
Uh oh!
There was an error while loading. Please reload this page.
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.
You don't need an extra generic parameter to do this. The only time you'd need to specify the Error type as part of the type signature, is if you're targeting a specific error type.