Skip to content
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

Object3D: Introduce .forward property #14703

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/api/core/Object3D.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ <h3>[property:Boolean castShadow]</h3>
<h3>[property:Object3D children]</h3>
<p>Array with object's children. See [page:Group] for info on manually grouping objects.</p>

<h3>[property:Vector3 forward]</h3>
<p>
Specifies the local forward direction of the object.<br />
Default is [page:Object3D.DefaultForward] - that is, ( 0, 0, 1 ).
</p>

<h3>[property:Boolean frustumCulled]</h3>
<p>
When this is set, it checks every frame if the object is in the frustum of the camera before rendering the object.
Expand Down Expand Up @@ -171,6 +177,12 @@ <h2>Static Properties</h2>
been made (already created Object3Ds will not be affected).
</p>

<h3>[property:Vector3 DefaultForward]</h3>
<p>
The default local [page:.forward forward] direction for objects.<br />
Set to ( 0, 0, 1 ) by default.
</p>

<h3>[property:Vector3 DefaultUp]</h3>
<p>
The default [page:.up up] direction for objects, also used as the default position for [page:DirectionalLight],
Expand Down
20 changes: 2 additions & 18 deletions src/cameras/Camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import { Matrix4 } from '../math/Matrix4.js';
import { Object3D } from '../core/Object3D.js';
import { Vector3 } from '../math/Vector3.js';

function Camera() {

Expand All @@ -19,6 +18,8 @@ function Camera() {
this.projectionMatrix = new Matrix4();
this.projectionMatrixInverse = new Matrix4();

this.forward.set( 0, 0, - 1 );

}

Camera.prototype = Object.assign( Object.create( Object3D.prototype ), {
Expand All @@ -40,23 +41,6 @@ Camera.prototype = Object.assign( Object.create( Object3D.prototype ), {

},

getWorldDirection: function ( target ) {

if ( target === undefined ) {

console.warn( 'THREE.Camera: .getWorldDirection() target is now required' );
target = new Vector3();

}

this.updateMatrixWorld( true );

var e = this.matrixWorld.elements;

return target.set( - e[ 8 ], - e[ 9 ], - e[ 10 ] ).normalize();

},

updateMatrixWorld: function ( force ) {

Object3D.prototype.updateMatrixWorld.call( this, force );
Expand Down
41 changes: 23 additions & 18 deletions src/core/Object3D.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function Object3D() {
this.parent = null;
this.children = [];

this.forward = Object3D.DefaultForward.clone();
this.up = Object3D.DefaultUp.clone();

var position = new Vector3();
Expand Down Expand Up @@ -95,6 +96,7 @@ function Object3D() {

}

Object3D.DefaultForward = new Vector3( 0, 0, 1 );
Object3D.DefaultUp = new Vector3( 0, 1, 0 );
Object3D.DefaultMatrixAutoUpdate = true;

Expand Down Expand Up @@ -305,7 +307,8 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),

// This method does not support objects with rotated and/or translated parent(s)

var m1 = new Matrix4();
var m1 = new Matrix3();
var targetDirection = new Vector3();
var vector = new Vector3();

return function lookAt( x, y, z ) {
Expand All @@ -320,15 +323,9 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),

}

if ( this.isCamera ) {

m1.lookAt( this.position, vector, this.up );

} else {

m1.lookAt( vector, this.position, this.up );
targetDirection.subVectors( vector, this.position ).normalize();

}
m1.lookAt( this.forward, targetDirection, this.up );

this.quaternion.setFromRotationMatrix( m1 );

Expand Down Expand Up @@ -506,22 +503,26 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),

}(),

getWorldDirection: function ( target ) {
getWorldDirection: function () {

if ( target === undefined ) {
var quaternion = new Quaternion();

console.warn( 'THREE.Object3D: .getWorldDirection() target is now required' );
target = new Vector3();
return function getWorldDirection( target ) {

}
if ( target === undefined ) {

this.updateMatrixWorld( true );
console.warn( 'THREE.Camera: .getWorldDirection() target is now required' );
target = new Vector3();

var e = this.matrixWorld.elements;
}

return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize();
this.getWorldQuaternion( quaternion );

},
return target.copy( this.forward ).applyQuaternion( quaternion );

};

}(),

raycast: function () {},

Expand Down Expand Up @@ -655,6 +656,9 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
if ( this.renderOrder !== 0 ) object.renderOrder = this.renderOrder;
if ( JSON.stringify( this.userData ) !== '{}' ) object.userData = this.userData;

if ( this.forward.equals( Object3D.DefaultForward ) === false ) object.forward = this.forward.toArray();
if ( this.up.equals( Object3D.DefaultUp ) === false ) object.up = this.up.toArray();

object.layers = this.layers.mask;
object.matrix = this.matrix.toArray();

Expand Down Expand Up @@ -791,6 +795,7 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),

this.name = source.name;

this.forward.copy( source.forward );
this.up.copy( source.up );

this.position.copy( source.position );
Expand Down
3 changes: 3 additions & 0 deletions src/loaders/ObjectLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,9 @@ Object.assign( ObjectLoader.prototype, {

}

if ( data.forward !== undefined ) object.forward.fromArray( data.forward );
if ( data.up !== undefined ) object.up.fromArray( data.up );

if ( data.castShadow !== undefined ) object.castShadow = data.castShadow;
if ( data.receiveShadow !== undefined ) object.receiveShadow = data.receiveShadow;

Expand Down
56 changes: 56 additions & 0 deletions src/math/Matrix3.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,62 @@ Object.assign( Matrix3.prototype, {

}(),

makeBasis: function ( xAxis, yAxis, zAxis ) {

this.set(
xAxis.x, yAxis.x, zAxis.x,
xAxis.y, yAxis.y, zAxis.y,
xAxis.z, yAxis.z, zAxis.z
);

return this;

},

lookAt: function () {

var localRight = new Vector3();
var worldRight = new Vector3();
var worldUp = new Vector3( 0, 1, 0 );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fixing world up to be +y ?
What impact would this have for using +z as up?

Copy link
Collaborator Author

@Mugen87 Mugen87 Aug 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fixing world up to be +y ?

worldUp could be actually configurable. I've hard-wired this to always assumes Y-Up as global up direction of the scene in world space.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some awkward people (ie me*) work with up = +z, the existing Matrix4.lookAt() has an
exception around for the z up case #11543 already.

  • geographic models fit better.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we find a way to make it configurable, it should be no problem. Do you have any ideas?

Copy link
Collaborator Author

@Mugen87 Mugen87 Aug 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn. I realize right now that this PR breaks all user code where camera.up.set( 0, 0, 1 ) is already set. https://threejs.org/examples/webgl_loader_pcd.html is such a problematic case. You would now have to change worldUp instead. Even if we apply a configuration option, the approach is not a backward-compatible. Too bad, it seems we can't go this way 😞 .

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean that these use cases will break unless the user also does camera.forward.set( 0, -1, 0 )?

Copy link
Collaborator Author

@Mugen87 Mugen87 Aug 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why yet but this does not solve the issue. It works if you remove camera.up.set( 0, 0, 1 ) and set worldUp to (0, 0, 1) (e.g. something like THREE.WorldUp.set( 0, 0, 1)). Even then, for some reasons TransformControls seems to have problems with this approach (it internally uses Matrix4.lookAt()).

Copy link
Collaborator Author

@Mugen87 Mugen87 Aug 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After a closer look, the main problem is caused since the new Matrix3.lookAt() interprets Object3D.up differently than Matrix4.lookAt(). That's the reason why both methods do not work together like in the example. From my point of view, Matrix3.lookAt() is the better approach since it's a more clear implementation of what's actually happening in lookAt(): A given Object3D.up vector is mapped like all other local basis vectors (forward, right) to the counterpart of a world coordinate frame (defined by worldUp, targetDirection, and worldRight). This is the way .lookAt() should work and Matrix3.lookAt() is exactly implemented like that.

Even if we introduce THREE.WorldUp to allow a configuration (e.g. to enable Z-UP) and then use this value in Matrix3.lookAt(), we would have to migrate all code that uses Matrix4.lookAt() to Matrix3.lookAt(). Besides, setting camera.up.set( 0, 0, 1 ) is not necessary anymore.

So introducing this new approach is a breaking change. If there will ever be a three.js 2, this should be the way to go^^.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for investigating!

var perpWorldUp = new Vector3();
var temp = new Vector3();

var m1 = new Matrix3();
var m2 = new Matrix3();

return function lookAt( localForward, targetDirection, localUp ) {

localRight.crossVectors( localUp, localForward ).normalize();

// orthonormal linear basis A { localRight, localUp, localForward } for the object local space

worldRight.crossVectors( worldUp, targetDirection ).normalize();

if ( worldRight.lengthSq() === 0 ) {

// handle case when it's not possible to build a basis from targetDirection and worldUp
// slightly shift targetDirection in order to avoid collinearity

temp.copy( targetDirection ).addScalar( Number.EPSILON );
worldRight.crossVectors( worldUp, temp ).normalize();

}

perpWorldUp.crossVectors( targetDirection, worldRight ).normalize();

// orthonormal linear basis B { worldRight, perpWorldUp, targetDirection } for the desired target orientation

m1.makeBasis( worldRight, perpWorldUp, targetDirection );
m2.makeBasis( localRight, localUp, localForward );

// construct a matrix that maps basis A to B

this.multiplyMatrices( m1, m2.transpose() );

};

}(),

multiply: function ( m ) {

return this.multiplyMatrices( this, m );
Expand Down
23 changes: 17 additions & 6 deletions src/math/Quaternion.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,14 +295,25 @@ Object.assign( Quaternion.prototype, {

// assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)

var te = m.elements,
var te = m.elements;

m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ],
var m11, m12, m13, m21, m22, m23, m31, m32, m33;

trace = m11 + m22 + m33,
s;
if ( m.isMatrix3 ) {

m11 = te[ 0 ]; m12 = te[ 3 ]; m13 = te[ 6 ];
m21 = te[ 1 ]; m22 = te[ 4 ]; m23 = te[ 7 ];
m31 = te[ 2 ]; m32 = te[ 5 ]; m33 = te[ 8 ];

} else {

m11 = te[ 0 ]; m12 = te[ 4 ]; m13 = te[ 8 ];
m21 = te[ 1 ]; m22 = te[ 5 ]; m23 = te[ 9 ];
m31 = te[ 2 ]; m32 = te[ 6 ]; m33 = te[ 10 ];

}

var trace = m11 + m22 + m33, s;

if ( trace > 0 ) {

Expand Down