-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Color maths 3 #12553
Color maths 3 #12553
Conversation
Co-Authored-By: Nathan Graule <me@solarliner.dev>
Between the current implementation and @IQuick143's comment on the previous PR here (which I still to ruminate over, but I think wrt. his argument over the color space issue that he is ultimately correct; though I don't like the typetag approach he proposes personally), I'm beggining to be seriously convinced that the better thing to do is to have an impl for |
Co-Authored-By: Nathan Graule <me@solarliner.dev>
I think this is an improvement over approach 2. Thank you again for your tireless iteration on this issue, and your willingness to engage with feedback. I really do appreciate it.
The core math traits provide a notational convenience, pure operator overloading, with no predefined semantics. We are talking about notation here, not well definedness. Either we are adding math or we aren't. If we are, we should use the math operators. Hiding math operators behind a less obvious api doesn't make anything any more or less correct. Please review my comments to the previous PR. Not letting us use standard math notation in bevy_math remains a blocker for me. |
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
/// is also a part of that set. For example the set of all points inside a square is a convex set as you can draw a line between any two points in the square and never leave it. | ||
/// For more information, please see [this wikipedia article](https://en.wikipedia.org/wiki/Convex_set). | ||
/// | ||
/// The space must also be linear, allowing you to interpolate between any two points `A` and `B` via the parameter |
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.
Math nitpick:
This is not exactly what linearity means, as it goes backwards, a line is usually defined using operations on a linear space.
The properties that actually make for a Linear space (also known as a Vector space) are:
(a
and b
are arbitrary scalars, x
and y
arbitrary vectors.)
A) Addition is commutative
B) Addition is associative
C) 0 * x
produces an identity element (0
).
D) 1 * x = x
E) Scalar multiplication associates with number multiplication (ab)*x = a*(b*x)
F) Scalar multiplication distributes over addition (a+b)*(x+y) = a*x + a*y + b*x + b*y
(I hope I didn't miss anything.)
These properties make up a vector space, which is what API consumers expect. (I believe these properties could form their own trait and convexity would be a separate marker trait. But that's maybe a next-PR task.)
As far as convexity goes, you're right, although we could note that the line between points A and B
is defined as all the points A*t + B*(1-t)
because it might not be imideatly obvious what is a line segment in an abstract space.
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.
Yep, I think I'd prefer to include this style of definition.
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 someone write this as a suggestion please? I am not a native speaker and I don't think I could write good docs for what is described above although I agree with the spirit. :/
/// no matter what value of `t` between 0 (yielding B) and 1 (yielding A) is selected. | ||
/// | ||
/// By implementing this trait, you guarantee that the above conditions hold true. | ||
pub trait LinearConvexSpace: Default + Copy + Clone { |
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 see the Default
constraint, is it needed/should we constrain it to have a pre-defined value? (like the 0 vector?)
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.
It is needed within the splines. Constraining it to be the 0 vector might be a good idea though. I'll get to that tomorrow.
I believe the reasoning for this was laid out by @viridia in the first PR #12460. It boils down to the fact that rust operators should be well defined and have an obvious and agreed upon definition. Componentwise operations make sense for colors but are not an immediately obvious nor an agreed upon standard for all color spaces. As such they do not meet the "style guide" requirements a type should fulllfill for As someone has already said: Intuitively, adding red and yellow should produce a valid orange. Yet no addition proposed so far and none that I can think of produce that result. By not allowing |
Not allowing |
An uncharitable reading of this argument might call it opting for an intentionally obtuse api in the hopes that beginners won't try to use it. Little prevents them from assuming After three PRs, I don't think any of us would disagree that color math is hard to get right. New users are going to make mistakes and misunderstand things, and I think that's unavoidable. We are going to have to write really great docs to help them learn. I grant using I'm sorry, on the one hand I feel like I am derailing your PRs, on the other hand I think this is a serious issue. This represent a noticeable decrease in quality for the spline code, which is a complex feature prone to subtle mathematical errors. I am actively writing additional tests for splines and a PR that adds closed splines, and this will make them difficult to read and correct. |
I think what you are referring to is the first PR #12460 which would be pretty much what you are arguing for here. It is most certainly the most obvious in terms of using the API. But as discussed in #12460 a big portion of people don't think that I personally prefer this PR, as we could still implement a wrapper type to make cubic splines less complex and because
In the end this is up for debate though and I will do what most agree upon. These PRs are suggestions for what such an API could look like and intend to enable a more practical view on the real life consequences of these implementations. Please feel free to critique or support any of them. |
Going to put forth an odd suggestion: for non-linear color spaces like HSL, it might make sense to think of spline animations in terms of separate animation tracks for each color component rather than a combined animation track for a color object. Interpolating hue or saturation, by itself, is fairly intuitive. Animating alpha, by itself, makes logical sense in any color model. Unfortunately, this creates its own set of problems when it comes time to actually apply the result to a material, since at some point you have to combine the channels together and convert to something that can actually be drawn. Setting the color parameter to HSL requires that you have signals for all three components, H, S, and L, although I suppose some of these can be constants. |
That is a valid way of doing it, but it's effectively just splitting a single spline into 4 independent ones and then having to recombine them later. (The only benefit I see is the freedom in control point placement, but I'm not super sure how helpful is that, I'd imagine you want to line up your control points in channels.) |
I like this idea in principle but the cons are a bit too harsh here, I think. Having to call |
If your concern is about code within This would still be more complex than having |
FWIW it's how Blender and Godot do it, when you create an animation it creates 3/4 lanes for each component, and each can be controlled independently. |
Maybe math in color spaces isn't the way to go at all. If I wanted to produce a color spline with the current implementation, I would define the spline on This seems good enough. I'm fine with the idea that once you turn numbers into colors, you can't do math on them any more, so if you want to do color math you have to keep around the coordinates in This approach is not going to be obvious to users, but I think this a small explanation in the book it would be fine. It seems basically good enough for most cases. |
Color math is still useful in areas other than splines, and some color spaces are designed specifically with the idea of being able to do math in them (ie. Oklab being a perceptually-linear space designed for taking relative distances, comparing colors, and performing additive synthesis). Which is why my original stance on this issue was "math on some color types". Yes, you could for all use-cases extract the components as a vector and do your math on that, but that defeats the purpose of providing type safety in the first place (one could also push the argument ad absurdum and say that vector types are strictly unnecessary, they're just a bunch of numbers that we could pass as a tuple on all operations, anyway -- it's a type of primitive obsession, I think). |
It does seem like most of those are covered by
I am generally sympathetic to this, but our spaces are not pure After some consideration, I don't think we need math on |
Objective
Solution
LinearConvexSpace
trait as suggested by @alice-i-cecile in this comment. This trait has everything needed for splines including arithatic operations such as addition or subtraction.LinearConvexSpace
does not implementstd::ops::Add
and friends because those operations are not well defined for some types (such as colors) allthough those types may be used to define a linear convex space.LinearConvexSpace<Scalar>
is generic over someScalar
type so thatLinearConvexSpace
may also be used forf64
oru32
based types such asDVec2
. These types cannot be used with splines but sinceLinearConvexSpace
does not suggest only being used with splines, this might be useful in the future, allthough I am happy to remove this if wanted.impl_linear_convex_space!(Type, Scalar)
macro that will automatically implementLinearConvexSpace<Scalar> for Type
using thestd::ops::*
traits.LinearConvexSpace<f32>
using a macroimpl_color_linear_convex_space
local tobevy_color
. All operations on color types are componentwise with no special rules for the alpha channel.Migration Guide
Point
trait fromcubic_splines.rs
has been removed; as such any type that is supposed to be used with splines should implementLinearConvexSpace<f32>
now. This can be done automatically for any custom type previously used with splines using theimpl_linear_convex_space
macro.Additional information
Mathematical operations on colors are a controversial topic. This is not the first PR attempting to add them and there has been lots of feedback and discussions on some previous PRs. If you want more information for the reasoning behind this proposal, please check #12534 out. For more information on why we don't implement
std::ops::Add
etc. for Colors or why arithmetic operations on colors may still be useful, please check out #12460.