I've been seeing a lot of questions boiling down to not knowing how to convert rotation to/from world coordinate space. There's a nice API to do this for position (localToWorld
/worldToLocal
) but no equivalent for quaternion and scale. We could consider adding those helpers, and perhaps deprecating the more limited getWorld*()
getters.
Proposed:
// Converts argument from local to world space
object.localToWorldPosition( position )
object.localToWorldQuaternion( quaternion )
object.localToWorldScale( scale )
// Converts argument from world to local space
object.worldToLocalPosition( position )
object.worldToLocalQuaternion( quaternion )
object.worldToLocalScale( scale )
Based on https://github.com/mrdoob/three.js/pull/5420 we might not want to add such helpers. I'll admit I dislike the verbose names. But getWorld*(target)
is strictly less useful than the proposal, and has the notable side effect of calling updateMatrixWorld()
where fooToBar
does not.
Alternatively, additions to the matrix transformation docs might do the job.
/cc @WestLangley @jbaicoianu
Very probably there is a better API for this that would not involve Object3D... 馃
As I said:
Object3D.localToWorld( vector ) and Object3D.worldToLocal( vector ) are two methods I would remove.
How does an object transform a vector? Of course, the answer is it doesn't.
I know they are widely-used, so they would have to be deprecated in favor of an alternative.
@donmccurdy Actually, I am not sure I understand what your concern is...
@WestLangley if I understand correctly, your concern is with the current localToWorld
API surface, not the functionality it provides? I agree with that, if so.
If I have a position in the local coordinate space of object1 and want to convert it the local coordinate space of object2, localToWorld
/ worldToLocal
does the job nicely. To do the same thing with rotation, you need to be comfortable working with matrices (unless there are shortcuts I've missed), and I think we could make that a bit easier.
How about:
// worldToLocal
object.matrixWorld.transformPosition( position );
object.matrixWorld.transformQuaternion( quaternion );
object.matrixWorld.transformScale( scale );
// localToWorld
matrixWorldInverse = new Matrix4().getInverse( object.matrixWorld );
matrixWorldInverse.transformPosition( position );
matrixWorldInverse.transformQuaternion( quaternion );
matrixWorldInverse.transformScale( scale );
I think this is nice:
// localToWorld
object.matrixWorld.clone() // returns a new matrix, if you want a new one.
.inverse() // modifies the new one in place
.transformPosition(object.position)
and if you're using a cache:
const m = new Matrix4
// ...
// localToWorld
m.copy(object.matrixWorld) // don't make a new one, use the cache
.inverse() // modifies m in place
.transformPosition(object.position)
This makes the existing clone
and copy
methods first class citizens in the process of choosing whether to have a new matrix or to have cache. In this ideality, .inverse()
would always modify in place. Other methods would also modify in place.
(Internally, this inverse()
method could use a cache (instead of accepting m
as a parameter), which helps with the implementation itself.)
I feel this makes the mental model of inverse()
nicer without an extra parameter, while clone
and copy
provide the mechanisms by which to choose allocation or re-use.
At the moment, we can choose re-use objects in more than one way: by using clone, or by passing an m
arg, but both forms are applicable in different cases. I think it's better to have a single defacto form applicable for all cases.
The same could apply to all the other math methods (in-place method, with clone/copy/new for making the other choices of "make a new one" or "use a cache"). It's just simpler.
EDIT: Sorry for sidetracking a little. Regardless of that underlying matrix or vector stuff, it would be nice to have the additional Object3D.worldToLocal/localToWorld methods.
I think even this proposal does not help yet. The current way rotation is implemented is not intuitive and very very very confusing for beginners. If you rotate something you might end up having the positions of elements changed that you did not intend to reposition. See https://stackoverflow.com/questions/57311356/rotating-an-object-properly-around-a-pivot-point-given-axis-and-angle for how many approaches and questions are already out there. Many of the approaches and examples are outdated and add to the confusion. It would be good to have more "proper/official" examples that show how a tree of objects with rotated children can be handled. Currently I am using the
static adjustRelativeTo(mesh, toMesh) {
//logSelected("adjusting toMesh",toMesh);
//logSelected("beforeAdjust",this.mesh);
toMesh.updateMatrixWorld(); // important !
mesh.applyMatrix(new THREE.Matrix4().getInverse(toMesh.matrixWorld));
//logSelected("afterAdjust",this.mesh);
}
pattern and I am not even sure whether this is the correct approach.
this ticket was mentioned in a way to get worldToLocal for quaternions
question, so here is one way to convert rotation to local frame, assuming uniform scale
@donmccurdy I can add something like this, if you want:
setWorldPosition: function ( vector ) {
this.position.copy( vector );
if ( this.parent ) {
this.parent.updateWorldMatrix( true, false );
this.position.applyMatrix4( _m1.getInverse( this.parent.matrixWorld ) ); // hopefully no _m1 nane-conflict...
}
return this;
},
setWorldQuaternion: function ( q ) {
this.quaternion.copy( q );
if ( this.parent ) {
this.quaternion.premultiply( this.parent.getWorldQuaternion( _q1 ).inverse() );
}
return this;
},
I'm not sure, however, a .setWorldScale( scale )
method makes sense.
I don't know if I can speak for the people who have upvoted this issue, but in my case I am interested in converting a quaternion in local space to a quaternion in world space (or vice versa). I don't necessarily want to modify the object whose local space I'm using, so setters aren't what I'd had in mind.
Maybe I should have suggested methods on SceneUtils
, rather than adding to the Object3D instance methods...
Oh, that should be easy. In fact I prefer it.
quaternion.localToWorld( object1 ).worldToLocal( object2 );
vector.localToWorld( object1 ).worldToLocal( object2 );
Is that the API you are looking for?
I like that! We probably can't import Object3D in the Vector.js or Quaternion.js files, without creating a circular dependency, but it doesn't look like an import would be necessary...
Good. I'll take care of it. :-)
I think vector.localToWorld( object )
is how object.localToWorld( vector )
should have been implemented in the first place.
For me, object.localToWorld( vector )
seems more appropriate with regards to encapsulation because it's the object that is maintaining its internal coordinate system and hence it should be the object's responsibility to transform coordinates. Putting the capability in the vector's domain means that it will require access to the object's internal coordinate mechanisms. Having said that, the required mechanisms are already public so I can see where having both vector.localToWorld( object )
and object.localToWorld( vector )
might be useful.
I would also like to see overloaded methods for Object3D's localToWorld
and worldToLocal that accept position, quaternion or scale and returns the appropriate transformation.
Most helpful comment
@WestLangley if I understand correctly, your concern is with the current
localToWorld
API surface, not the functionality it provides? I agree with that, if so.If I have a position in the local coordinate space of object1 and want to convert it the local coordinate space of object2,
localToWorld
/worldToLocal
does the job nicely. To do the same thing with rotation, you need to be comfortable working with matrices (unless there are shortcuts I've missed), and I think we could make that a bit easier.How about: