-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Revert transform back to using Mat4 #1755
Comments
Converting a Mat4 to TRS my result in a double cover Quaternion which may mess with some interpolation code that don't account for this, which isn't very common. Not a really big problem |
👋 A related issue came up on discord earlier today. I'll try to give some context and my opinion: As part of a PR (#2049) I double checked if child-entities inherit the scale of their parents, which could cause some issues with my algorithm through shearing. At first I thought it didn't because This can lead to unexpected behaviour if parent-child hierarchies, rotation and non-uniform scaling are mixed together at the same time. More specifically, scaling the parent along its x-axis would scale the children each along their own (local) x-axis. The change @lassade proposed here would potentially fix that (which is how all that ties into this specific issue). In general I see different ways to approach this, each with its own quirks and limitations, and I feel like all of them have their own values. Note that this is just my personal view, opinions of more experienced users are welcome:
Apparently there were discussions similar to this before, so sorry if I brought up a problem that has already been solved / dismissed. I guess all in all I'd like to bring some awareness to this issue, weighing the possible options - even if it's only confirming that the current approach is preferred - and documenting the limitations of that at some point. |
Adding the |
I don't think the issue is really with propagating transforms from parents through the hierarchy, it's with incremental updates between frames. If we want to use only the Mat4 representation for transformations, that means that every time the rotation of the local transformation is changed (via a There's a problem with our current TRS representation, which I don't think has been discussed outside of #2055, which is the order of application for different components. Our current implementation applies rotation, then scale, then translation (it also generates matrices by incorrectly applying SRT instead of RST, but that's a solvable problem). The RST ordering is pretty uninituitive, and is uncommon in graphics and game development. Unfortunately, this is actually the only representation we can use if we want to support nonuniform scale. The reason is that two affine transforms without a shear component can compose to generate a transform with a shear component. Shear cannot be represented with SRT ordering, but it can be represented with RST (because 'S' here is really more a combination of scale and shear than it is pure scale). Note that with uniform scale, shear is not an issue, and SRT and RST are equivalent. There is no real clean solution to this problem, and I think the right choice is to either keep the RST ordering and just fix all the bugs, or to split into separate components for uniform and nonuniform scaling with different semantics. Either way I think it makes sense to have a |
Yup this is one of main reasons we opted for "similarities" over mat4 as the source of truth.
My personal preference is to keep the current Transform representation(*) and to fix the transform op bugs. Separate components introduces extra complexity to entity construction and makes it way harder to do operations that require the values of multiple "source of truth" components (ex: transforming a point using Translation, Rotation, and Scale components requires querying for all three, then using some abstraction to group them and do the transform op). I don't yet have an opinion on SRT vs RST. I don't fully grok the implications of each yet. (*) including non-uniform scale ... it creates number of problems (physics, more expensive to calculate, etc) but in practice people want to squash and stretch stuff. I want that to work by default. Later we can internally optimize for uniform scales if it becomes a major concern (ex: consider making scale an
My main problem with making these separate components is that they can't have "up to date" values during arbitrary points in the UPDATE stage, so they won't be useful for game logic unless the user manually inserts hard TransformMatrix sync points between GAME_LOGIC_A and GAME_LOGIC_B (which currently means adding multiple stages). They might save one or two conversions by "internal systems" that run after the UPDATE stage, but generally the only thing that needs the final Mat4 is shader uniforms. And we generally only generate those uniforms once per entity, so caching the Mat4 wouldn't save us much. Most game-related requirements for Transforms (editing translation/rotation/scale, transforming a point or other transform, etc) can be done without recalculating the Mat4. Things that do require Mat4s could create their own caches if perf becomes a problem. We could consider embedding Mat4s into the Transform component and incrementally keeping them up to date, but that has a number of negative implications:
Can you link to sources / benchmarks for these numbers? |
Note that this benchmark is with the current (broken) composition implementation, which is going to be faster than the correct implementation. |
yeah, this is a good point. I don't think using the raw matrices is going to be a particularly common requirement anyway, especially if we flesh out the For SRT vs RST, if we do want to support nonuniform scale by default, we effectively have the choice between the relatively unintuitive RST representation, which is closed under composition, and the intuitive SRT, which is not. I think it makes sense here to go with RST to avoid introducing complexity to users that only care about uniform scaling, since it is much more common. No matter what, we need to document this carefully. |
Local transform should be kept the same, I only want to change the GlobalTransform to Mat4 witch shouldn't be changed directly |
Oh, I see. Using different representations for the local vs global representations does make me a bit uncomfortable. For example, wanting to convert the global transformation of one entity into the local frame of reference of a different entity is a useful operation. As long as we have a robust way to convert back from the matrix representation to the TRS representation, I think this is okay. |
about that, there was a lengthy discussion here: #1460 |
Would The benchmarks above only compare mat4 * mat4 and trs * trs, which isn't what we'd be comparing in this case. Does anyone have good links to resources covering non-uniform Similarity operations? Finding that material is harder than I expected. We should also try to find examples of other game engines / math libs that are operating under similar constraints / design choices as us. |
This isn't super important, but will probably be useful when researching this: similarities always have uniform scale and preserve angles. I'm not sure there's a word for an affine transformation without shear, and I'm not sure it's a particularly common or useful object because it isn't closed under composition. For the performance of matrix vs trs composition, we're comparing trs * mat4 to trs * trs. I don't have any benchmarks for this on hand, but my hunch is that the former will be faster because the trs * trs operation is rather complex. |
Ops sorry, Edit: |
So after banging my head against a wall trying to figure out why my implementation for
Now the scale is off-axis for both SRT and RST representations. This is kinda nice because it opens back up the opportunity to switch back to SRT representation for |
I think we need to start deferring to the wider gamedev ecosystem at this point. SRT with non-uniform scale appears to dominate:
In short: I don't think we are in crisis here (other than some superficial AxB vs BxA ordering problems). It looks like everyone is doing exactly what we are currently doing, with the exception of godot. |
The question for all of these implementations is how (and whether) they handle composition with nonuniform scale. Defold is the only one using SRT with public source code and the answer is that they handle it incorrectly by just multiplying the scales together: https://github.com/defold/defold/blob/1ae302ec33d4514408c04ad3ae5d3c1efe2057bd/engine/dlib/src/dlib/transform.h#L341. For unity and unreal we could theoretically check what they are doing by passing in values and observing the result. It's possible that this is the solution we have to go with, but it definitely bothers me that the results of |
I agree. |
Another realization: SRT (or RST) representation can't even be inverted. We don't currently have a |
In the past I've had the need to update only translations, or only querying rotations using change detection. Is it out of the question that we split out |
As an end user, I would love this solution. It would be quite a bit clearer, and, as discussed, I'm frequently updating only one property at a time in my own code. |
I would also prefer having separate components. It would also nicely resolve our questions about whether to include (impossible) functionality for I don't know how common wanting to switch spaces between the local transform and the parent is. Personally, I almost never want to do this, but frequentlywant to switch between local and global, which would still be fine if |
@Benjamin-L, @alice-i-cecile, @aevyrie I've already given my current opinion on this above (and in other threads). It works fine for the simple stuff. But it creates problems for anything "interesting". How do you propose enabling operations like "transforming a point using the current rotation / translation/ scale of the entity", "rotating an entity around a point", or "scaling an entity using a non-zero origin"? A centralized Transform makes it easy to perform these operations. |
We could still split out TRS from We would still need to settle on SRT/RST/whatever representation for transform propagation. Either the user needs to provide a system that turns stage start ---> user modifies TRS. If they access |
I'm pretty sure this is a tradeoff each engine has accepted. Clearly Unity has with its description of the lossy global scale property. If it works for Unity, Defold, and (presumably) Unreal, its can work for us. Seems like a pragmatic tradeoff to me.
This is the problem. Users wanting to do the more complicated operations mentioned above in the update stage can't rely on a Transform that lags behind by a frame. Therefore they need to query for them individually, then use some abstraction (construct a Transform in the system, use a Transform WorldQuery, etc) to do the op using "live data". |
I think we can get around this using bundle queries, enabled by #1843. Regardless, I think this split is RFC-worthy on its own, and out-of-scope for now. |
Hmm, if we go forward with having a single I think we need to be careful to avoid exposing an interface that behaves unexpectedly in a very subtle way. One component of this is documentation, but another is that the names for the functions/fields involved have to signify the actual behavior clearly. |
Isn't this exactly the problem we have now with If anything, having the Before I veer too far off the topic of this issue, I should reframe to highlight what this proposed change solves:
|
Agreed. Moving back to a "split component" model will require an RFC and it should take into account the large amount of history we have on the topic of transforms. I personally think using a WorldQuery derive (or a theoretical bundle query) should be avoided when possible. They add complexity and feel "too special" for core things like transforms. We'd need to start explaining to users why they need to do
Local scale is not lossy, only global scale (and the results of Transform * Transform ops). We could consider representing that with new types.
GlobalTransform cannot be trusted and (unlike local transforms) is O(HIERACHY_SIZE) to update on the fly. Due to that time complexity, it should always be out of sync by default (imo). This doesn't need to be true for local transforms. Making Transform out of sync with individual SRT components just creates a new class of error and increases the complexity of the system as a whole / makes it harder for newbies to learn "the rules". |
I'd like non-uniform scaling of arbitrary axes in my app, and this cannot be supported in general with RST or SRT, so I'm currently restricted to scaling X/Y/Z in object space using Bevy's I'm about half-way there, because I'm already using my own custom transform component for all 3D objects drawn, so I may have my own workaround soon. Still, it'd be cleaner if there was direct support for it. |
#2713 (or even a manual impl) would enable us to easily |
It was never about "ease of impl". Its about keeping abstractions to a minimum, especially for core / common / high traffic components. When a new bevy user asks "what is a Transform?", I don't want to say "oh thats a derived WorldQuery that combines Position, Rotation, and Scale components into a single Query". It immediately throws people into the deep end when they see
They couldn't, because |
This is reasonable. My position is that new Bevy users should not be working with In my experience helping Bevy users, I have never seen beginner gameplay code that needed to work with translation, rotation and scale in a compound fashion other than to convert screen space into world space and vice versa (which should not have to be done manually by beginners in the first place).
Right. No specialization, so we can't even magic it to convert WorldQuery types into their mutable forms for non-components. Pulling in mutable components seems quite reasonable: it's a good default and in the 1% of cases where it matters there's a trivial escape-hatch. |
Having Optimizing around the "hello world" use case would add extra complexity for any non-trivial operation in 3d space (i.e. when you need to recompose the transform). |
…4379) # Objective - Add capability to use `Affine3A`s for some `GlobalTransform`s. This allows affine transformations that are not possible using a single `Transform` such as shear and non-uniform scaling along an arbitrary axis. - Related to #1755 and #2026 ## Solution - `GlobalTransform` becomes an enum wrapping either a `Transform` or an `Affine3A`. - The API of `GlobalTransform` is minimized to avoid inefficiency, and to make it clear that operations should be performed using the underlying data types. - using `GlobalTransform::Affine3A` disables transform propagation, because the main use is for cases that `Transform`s cannot support. --- ## Changelog - `GlobalTransform`s can optionally support any affine transformation using an `Affine3A`. Co-authored-by: Carter Anderson <[email protected]>
…evyengine#4379) # Objective - Add capability to use `Affine3A`s for some `GlobalTransform`s. This allows affine transformations that are not possible using a single `Transform` such as shear and non-uniform scaling along an arbitrary axis. - Related to bevyengine#1755 and bevyengine#2026 ## Solution - `GlobalTransform` becomes an enum wrapping either a `Transform` or an `Affine3A`. - The API of `GlobalTransform` is minimized to avoid inefficiency, and to make it clear that operations should be performed using the underlying data types. - using `GlobalTransform::Affine3A` disables transform propagation, because the main use is for cases that `Transform`s cannot support. --- ## Changelog - `GlobalTransform`s can optionally support any affine transformation using an `Affine3A`. Co-authored-by: Carter Anderson <[email protected]>
…evyengine#4379) # Objective - Add capability to use `Affine3A`s for some `GlobalTransform`s. This allows affine transformations that are not possible using a single `Transform` such as shear and non-uniform scaling along an arbitrary axis. - Related to bevyengine#1755 and bevyengine#2026 ## Solution - `GlobalTransform` becomes an enum wrapping either a `Transform` or an `Affine3A`. - The API of `GlobalTransform` is minimized to avoid inefficiency, and to make it clear that operations should be performed using the underlying data types. - using `GlobalTransform::Affine3A` disables transform propagation, because the main use is for cases that `Transform`s cannot support. --- ## Changelog - `GlobalTransform`s can optionally support any affine transformation using an `Affine3A`. Co-authored-by: Carter Anderson <[email protected]>
…evyengine#4379) # Objective - Add capability to use `Affine3A`s for some `GlobalTransform`s. This allows affine transformations that are not possible using a single `Transform` such as shear and non-uniform scaling along an arbitrary axis. - Related to bevyengine#1755 and bevyengine#2026 ## Solution - `GlobalTransform` becomes an enum wrapping either a `Transform` or an `Affine3A`. - The API of `GlobalTransform` is minimized to avoid inefficiency, and to make it clear that operations should be performed using the underlying data types. - using `GlobalTransform::Affine3A` disables transform propagation, because the main use is for cases that `Transform`s cannot support. --- ## Changelog - `GlobalTransform`s can optionally support any affine transformation using an `Affine3A`. Co-authored-by: Carter Anderson <[email protected]>
The article on #596 miss interpreted and there is no compelling reason to use TRS for global transforms, here is what I found:
transform_propagation_system
GlobalTransfrom
confuses people into thinking they should modify it, the ecs approach doesnt allow fortransform.position = ...
like in traditional engines, because the transformations WONT propagate; Later on we want to add a WorldToLocal it can be useful in some situations.A few disadvantages
Sorry for wanting to break everyone else code
The text was updated successfully, but these errors were encountered: