-
-
Notifications
You must be signed in to change notification settings - Fork 35.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
[WIP] Add position/quaternion/scale cache into Object3D #15706
Conversation
As I posted #14138 (comment) I'd like to suggest we go this cache approach so far, and we think of the other options if we realize performance gain isn't good enough or performance penalty for applications including a lot of dynamic objects isn't small. So I removed "[WIP]" from the title of this PR. |
works for me, but cannot say what the performance win is |
@Mugen87 Can you please explain what your approval of this PR means? Do you approve the implementation -- meaning you tested it and you believe it works correctly? Do you approve the performance difference in memory and speed? Wasn't performance worse in some cases? |
This looks like a failure point with this PR... var a = new THREE.Object3D();
var b = new THREE.Object3D();
var v = new THREE.Vector3();
a.position.set( 1, 2, 3 );
a.updateMatrix();
v.setFromMatrixPosition( a.matrix ); // 1, 2, 3
b.copy( a );
b.position.set( 0, 0, 0 );
b.updateMatrix();
v.setFromMatrixPosition( b.matrix ); // 1, 2, 3 -- oops |
Good catch. To pass your test case, I needed to update But came up with a more serious problem. Users can directly touch var a = new THREE.Object3D();
var b = new THREE.Object3D();
var v = new THREE.Vector3();
a.position.set( 1, 2, 3 );
a.updateMatrix();
v.setFromMatrixPosition( a.matrix ); // 1, 2, 3
b.matrix.copy( a.matrix );
b.position.set( 0, 0, 0 );
b.updateMatrix();
v.setFromMatrixPosition( b.matrix ); // 1, 2, 3 -- oops Thinking if there's any solutions to this issue... |
@takahirox already made statistics about this approach in #14138. TBH, I trust his data. Besides, caching values like this is a typical approach in engine design. I was just not aware about your mentioned side effect in |
@takahirox Have you tried to init the values in the cache differently? For example with this._positionCache = new Vector3( Infinity, Infinity, Infinity ); Notice that e.g. |
@Mugen87 I don't think it solves the problem because this problem can happen any time, not only when More simply b.updateMatrix(); // local matrix is updated from the .position/quaternion/scale and
// they are copied to ._*Cache
b.matrix.copy( a.matrix ); // manually update local matrix
b.updateMatrix(); // local matrix should be calculated from the
// .position/quaternion/scale but the matrix remains the same
// because .position/quaternion/scale has the same values as
// ._*Cache values and matrix.compose() inside is skipped |
It seems it's necessary to detect changes to If the |
Added "[WIP]" to the title again so far. |
Realized that the mechanism we needed on the current API is the one detecting if updated local matrix hasn't changed or if So, some ideas I quickly came up with without
updateMatrix: function () {
if ( ! this._positionCache.equals( this.position ) ||
! this._quaternionCache.equals( this.quaternion ) ||
! this._scaleCache.equals( this.scale ) ||
! this._matrixCache.equals( this.matrix ) ) {
this.matrix.compose( this.position, this.quaternion, this.scale );
this.matrixWorldNeedsUpdate = true;
this._positionCache.copy( this.position );
this._quaternionCache.copy( this.quaternion );
this._scaleCache.copy( this.scale );
this._matrixCache.copy( this.matrix );
}
},
updateMatrix: function () {
this.matrix.decompose( tmpPosition, tmpQuaternion, tmpScale );
if ( ! this.position.equals( tmpPosition ) ||
! this.quaternion.equals( tmpQuaternion ) ||
! this.scale.equals( tmpScale ) {
this.matrix.compose( this.position, this.quaternion, this.scale );
this.matrixWorldNeedsUpdate = true;
}
},
updateMatrix: function () {
tmpMatrix.compose( this.position, this.quaternion, this.scale );
if ( ! this.matrix.equals( tmpMatrix ) ) {
this.matrix.copy( tmpMatrix );
this.matrixWorldNeedsUpdate = true;
}
}, But I don't think they are performant. Probably there is no simple solution on our APIs...? |
d535ef1
to
b470b43
Compare
I think this is one reason why certain engines don't grant direct access to matrices like the world matrix. Or they make these properties read only. This makes it easier to control how and when to execute an update. Instead you use methods like .getWorldMatrix() (from Babylon.js) or a read-only property like localToWorldMatrix (from Unity) to retrieve it. AFAIK, Unity does not even provide access to the local matrix. Users have to compose it themselves if they need it via Matrix4x4.TRS. I think this also true for Babylon.js. We might need an API change to handle caching properly. |
Yeah I think so too, we need to change API if we'd like to introduce cache. But I can't imagine how much this change requires to edit core code, example code, and user code... |
If we don't change the API for flexibility, maybe this type of optimization should be done in user side? For example users can define their function updateMatrix( object ) {
if ( object.userData.cache === undefined ) {
object.userData.cache = {
position: new THREE.Vector3( NaN, NaN, NaN ),
quaternion: new THREE.Quaternion( NaN, NaN, NaN, NaN ),
scale: new THREE.Vector3( NaN, NaN, NaN )
};
}
if ( ! object.userData.cache.position.equals( object.cache ) ||
! object.userData.cache.quaternion.equals( object.quaternion ) ||
! object.userData.cache.scale.equals( object.scale ) ) {
object.matrix.compose( object.position, object.quaternion, object.scale );
object.matrixWorldNeedsUpdate = true;
object.userData.cache.position.copy( object.position );
object.userData.cache.quaternion.copy( object.quaternion );
object.userData.cache.scale.copy( object.scale );
}
}
function updateMatrixWorld( object, force ) {
if ( object.matrixAutoUpdate ) updateMatrix( object );
if ( object.matrixWorldNeedsUpdate || force ) {
if ( object.parent === null ) {
object.matrixWorld.copy( object.matrix );
} else {
object.matrixWorld.multiplyMatrices( object.parent.matrixWorld, object.matrix );
}
object.matrixWorldNeedsUpdate = false;
force = true;
}
// update children
var children = object.children;
for ( var i = 0, l = children.length; i < l; i ++ ) {
updateMatrixWorld( children[ i ], force );
}
}
var scene = new THREE.Scene();
scene.autoUpdate = false;
scene.onBeforeRender = function () {
updateMatrixWorld( scene );
}; |
This of course an option 👍 But in general I have to admit that a caching approach is not suitable to the flexibel API or |
Reminds me at introducing |
@Mugen87 Could you remove approval from this PR for clear state? |
Done^^ |
@takahirox how can i use this #15706 (comment) , with aframe on user side? |
To handle the case the @WestLangley brought up would it make sense to add a If If it's important to handle the case where |
@takahirox Also this idea of drop-in replacements for the |
@gkjohnson A default |
see #14138 (comment) |
This PR adds
position/quaternion/scale
cache intoObject3D
. This is one of the ideas, discussed in #14138, to skip unnecessary matrix update in.updateMatrixWorld()
.This PR is still WIP because it could have performance penalty in some cases. But you can see how small the change is from this PR.