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
Remove force flag and make Object3D.matrixWorldAutoUpdate
work properly again
#27261
base: dev
Are you sure you want to change the base?
Conversation
📦 Bundle sizeFull ESM build, minified and gzipped.
🌳 Bundle size after tree-shakingMinimal build including a renderer, camera, empty scene, and dependencies.
|
Omw to fix examples again |
Do you know if these issues were introduced in #24028? |
@mrdoob Doesnt seem like it. Cody introduced The bug has miraculously avoided detection in tests, because tests are run inline one after the other and default properties ( like parent.matrixAutoUpdate = true;
child.matrixAutoUpdate = true; to Cody's Edit: @makc helped track this |
@Mugen87 I fixed the examples. Please have a look again when you have a moment. It would be great to have it in the upcoming release^^ |
I'm so glad this is getting attention. I thought I was going insane! |
@@ -583,8 +583,6 @@ class Object3D extends EventDispatcher { | |||
|
|||
this.matrixWorldNeedsUpdate = false; | |||
|
|||
force = true; | |||
|
|||
} | |||
|
|||
// update children |
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 had some time now to study the PR more closely (and matrixWorldAutoUpdate
in general) and I think this PR goes in the right direction!
However, there is one thing that confuses me: The comment update children
in this method is actually wrong. This should actually be called update descendants
since when a world matrix changes, you want to update not just the world matrices of children but of all subsequent nodes in the hierarchy.
Hence, I think it was wrong to add child.matrixWorldAutoUpdate === true
to this code block because it prevents updates that might be wanted further down the hierarchy. This PR puts the matrixWorldAutoUpdate
check at the right place (further up in the method where the world matrix of a node is actually computed).
So how about writing the code like so now?
// make sure descendants are updated if required
const children = this.children;
for ( let i = 0, l = children.length; i < l; i ++ ) {
const child = children[ i ];
child.updateMatrixWorld( force );
}
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.
Hi @Mugen87 , thanks for having a look!
Have you tested your code? Your argument about descendants makes sense conceptually, however in the current configuration I believe it would cause world matrix updates regardless of .matrixWorldAutoUpdate
, because:
- Scene calls
updateMatrixWorld()
on its descendants - Descendant checks
descendant.matrixAutoUpdate
and performsdescendant.updateMatrix()
updateMatrix()
setsdescendant.matrixWorldNeedsUpdate = true
- The next statement
if ( this.matrixWorldNeedsUpdate || this.matrixWorldAutoUpdate === true || force ) {
passes, once again regardless ofdescendant.matrixWorldAutoUpdate
In other words, as long as object has object.matrixAutoUpdate
set to true
, it will override object.matrixWorldAutoUpdate
. If we wanted to go the way you propose and keep object.matrixWorldNeedsUpdate
relevant, we would have to either:
a) eliminate descendant.matrixWorldNeedsUpdate = true
from updateMatrix()
or...
b) in the second if
statement if ( this.matrixWorldNeedsUpdate || this.matrixWorldAutoUpdate === true || force ) {
have this.matrixWorldAutoUpdate
somehow override the this.matrixWorldNeedsUpdate
flag if necessary.
My use-case (which has led to discovery of this bug) was setting skinned mesh's rootBone.matrixWorldAutoUpdate = false
(rootBone.matrixAutoUpdate
is left to default true
) to prevent skeleton matrices updates and perform it manually when needed. The current way is convenient because setting .matrixWorldAutoUpdate
on the root object automatically prevents world updates of its descendants. You might say it makes .matrixAutoUpdate
obsolete because the local matrix wont be updated either, but I think there is no reason to update local matrix if we dont update world matrix, since it is the latter that is sent to the shader anyway.
Thus I believe it is the object.matrixWorldAutoUpdate
that should take precedence over object.matrixAutoUpdate
.
It would be less convenient (although maybe more logical? 🤔) if traversal of descendants happens regardless (in the case of your change), as then in order to prevent all world matrix updates, you'd have to first traverse the entire object and set .matrixWorldAutoUpdate
to false
on each and every descendant instead of just the root object.
I am not sure if it even makes sense for descendant to update world matrix
child.matrixWorld.multiplyMatrices( parent.matrixWorld, child.matrix );
if the parent.matrixWorld
stayed the same. But maybe it makes sense for descendants deeper in the graph?
What would be some use-cases for this?🤔
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 assumed matrixAutoUpdate
and matrixWorldAutoUpdate
are set to false
at the same time if an app wants to control the update matrix process.
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.
Side note: This entire thread is another example of why the flag based approach is cumbersome and too complicated. At some point, we should revisit the entire world matrix update computation and implement a solution based on dirty flags and caches and remove the update flags altogether. Even if this means some breaking changes like making Object3D.matrix
private.
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.
This entire thread is another example of why the flag based approach is cumbersome and too complicated.
It is cumbersome, but I've found this force = true
flag removal to be a good workaround for now. How would that dirty flags and caches solution work?
I assumed matrixAutoUpdate and matrixWorldAutoUpdate are set to false at the same time if an app wants to control the update matrix process.
Then maybe it makes sense to merge these flags into one? Or maybe we could implement an enum-like option for matrices update to reduce the number of impossible states? Its generally better than having booleans for every option. Something like
const MATRIX_UPDATE_TEST_NAME_ENUM = {
LOCAL_AND_WORLD: 0,
ONLY_LOCAL: 1,
NONE: 2,
};
object.matrixAutoUpdate = MATRIX_UPDATE_TEST_NAME_ENUM.NONE;
Also I would avoid making Object3D.matrix
private. I know in more advanced performance-focused projects people omit position/rotation/scale
properties and just deal with matrices directly.
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.
How would that dirty flags and caches solution work?
The first step would be to introduce dirty flags in Vector3
and Quaternion
so you know when position
, quaternion
or scale
has been changed. Only then you recompute the local matrix which means matrixAutoUpdate
could be removed.
Also I would avoid making Object3D.matrix private.
You could still set a matrix with a new method called Object3D.setMatrix()
. It's just important that the matrix
property itself isn't writable anymore otherwise we can't properly detect a state change.
We have tried to implement something like this in #14138 but stopped because of Object3D.matrix
is writable. Besides, at that time I wanted to explore caching first since it requires no changes to the math classes. Turned out that caching is slower than the dirty flag approach.
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.
Remove force flag set from
Object3D.updateMatrixWorld()
, which makesObject3D.matrixWorldAutoUpdate
useless.This is a continuation of #27245
Motivation
I am setting
object.matrixWorldAutoUpdate = false;
in my app and updating the world matrices manually. However due to the issue,Object3D.matrixWorldAutoUpdate
simply doesn't work.Description
At the moment
Object3D.matrixWorldAutoUpdate
flag doesn't do anything. Itstrue
by default (which is why it hasnt been caught yet I assume), but if you set it tofalse
, the object will still have its world matrix recalculated every frame. Why? Because offorce = true
insideObject3D.updateMatrixWorld()
.The Problem
So what happens?
WebGLRenderer.render()
callsif ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
scene.updateMatrixWorld()
callsif ( this.matrixAutoUpdate ) this.updateMatrix();
scene.updateMatrix()
composes the local matrix and setsthis.matrixWorldNeedsUpdate = true;
scene.updateMatrixWorld
the condition is called:if ( this.matrixWorldNeedsUpdate || force ) {
, which will run because of point 3.force = true;
is called (for what reason, I do not know)if ( child.matrixWorldAutoUpdate === true || force === true ) {
- HERE WE SEEchild.matrixWorldAutoUpdate
DOES NOT MATTER! becauseforce
istrue
.child.updateMatrixWorld( force );
updating the entire scene graph, now withforce
flag set totrue
SO FROM NOW ON IT WILL ALWAYS BE TRUE FOR EVERY OBJECT IN THE SCENE GRAPH! And since it will always be passed down as true, the check from point 6.if ( child.matrixWorldAutoUpdate === true || force === true ) {
will always run, makingchild.matrixWorldAutoUpdate
not matter at all!Here is a rundown in the
Object3D
matrix updates code, with steps above pointed out in the comments:We see that in the current configuration as long as the root
Scene
object updates its local matrix, ALL of its descendants will have their world matrices updated, whether they like it or not (regardless ofchild.matrixWorldAutoUpdate
. Here is JSfiddle proof: https://jsfiddle.net/fpb09am8/Its very easy to test. Just do:
Here we see that even though child doesnt want to have its world matrix updated automatically (
child.matrixWorldAutoUpdate = false
) it still happens because thescene
will force it on all its descendants as shown in the 7 points above.Solution
Remove
force = true;
from insideObject3D.updateMatrixWorld
. It is setting itself to true by itself, making auto update checks ineffective. It is confusing if you setforce
tofalse
(or leaveundefined
) because it will turn totrue
in just 1 generation by itself anyway. It is not clear at all that its basically alwaystrue
.If I understand correctly from docs,
force
should be an external flag set by the users if they want it.Also important:
force
forced all world matrix updates. Without it, objects that havematrixAutoUpdate = false
will no longer update their world matrices, sincematrixWorldNeedsUpdate
wont be set totrue
(point 3.). This would for example break manyHelper
s since they havematrixAutoUpdate
set tofalse
. Necessity of objects' world matrix computation shouldnt rely solely on local matrix update, thus I also added an extrathis.matrixWorldAutoUpdate === true
check to the secondif
:This way world matrix recomputation also depends on
matrixWorldAutoUpdate
.Now since
force
forced all world matrix updates previously andmatrixWorldAutoUpdate
istrue
by default, this should avoid breaking 99.99% of existing code.