Three.js - how do up vectors work with lookAt()?
Asked Answered
C

4

17

I'm trying to understand how up vectors and lookAt() work together in three.js. I'm setting the up vector of this axisHelper, so that the Y axis always points at the target geo, which marks the position of the up vector. It works as expected for X and Y, rotating the axes around the Z axis; and when I try to adjust the Z value of the up vector I would expect the axes to rotate around the X axis, but nothing happens.

http://jsfiddle.net/68p5r/4/ [Edit: I've added geo to show the up target position.]

I have a dat.gui interface manipulating the up vector to demonstrate, but the problem exists when I set the vector manually as well.

I suspect the problem is around line 74:

zControl.onChange(function(value) {
  axes.up.set(this.object.x, this.object.y, value);
  axes.lookAt(new THREE.Vector3(0, 0, 1));
});

When I update the up vector, I instruct the axisHelper to update its orientation onscreen by redoing its lookAt() down its Z axis. Changing the X and Y works as expected, why not the Z?

(This is also the case if I use geo instead of an axisHelper: http://jsfiddle.net/68p5r/5/)

rotated axisHelper

Chrissa answered 10/12, 2013 at 22:12 Comment(2)
1. An axis in three.js should always have unit length; be sure to call axis.normalize(). 2. Why are you changing the up vector? That is normally not required. Please modify your post and explain. 3. What are you trying to achieve? Please explain in your post.Divert
I'm just trying to understand how up vectors and lookAt() work together. I'll clarify the question.Chrissa
D
16

When you call Object.lookAt( vector ), the object is rotated so that its internal z-axis points toward the target vector.

But that is not sufficient to specify the object's orientation, because the object itself can still be "spun" on its z-axis.

So the object is then "spun" so that its internal y-axis is in the plane of its internal z-axis and the up vector.

The target vector and the up vector are, together, sufficient to uniquely specify the object's orientation.

three.js r.63


Tip: An axis in three.js should always have unit length; be sure to call axis.normalize() in your code.

Divert answered 11/12, 2013 at 15:55 Comment(5)
Thanks, this is how I understood up vectors and lookAt() to work, which is why I'm confused about the problem I describe in my question, as illustrated in the linked fiddles.Chrissa
In your fiddle, the target vector never changes, so the z-axis always points in the same direction.Divert
I understand both halves of that sentence but not how the one follows from the other – I must be having trouble seeing the problem. I understood the "target" to be an object-space vector, and the "up" to be a world-space vector, and as you said: the two determine the object's orientation. If the up vector's Z value changes, wouldn't the object's orientation change to follow it?Chrissa
It took another cup of coffee, but thanks to your comment I figured out the flaw in my assumption - the target vector is in world space, and my mental model was incorrect.Chrissa
To be clear: are the up and lookat vectors both in world space? In this comment it looks like @mrdoob is saying the up vector is in object space, but that was 2 years ago… github.com/mrdoob/three.js/issues/1752#issuecomment-5206241Chrissa
T
2

I assume your title meant rotate on Z instead of X?

Anyways, the culprit seems to be axes.lookAt(new THREE.Vector3(0, 0, 1)); if you change that to axes.lookAt(new THREE.Vector3(0, 1, 0)); for all methods then Y doesn't rotate as expected. You are telling the axis helper to look down a specific axis (in your case Z). Hence why Z value isn't working.

Is there an example of what your trying to accomplish that might help us?

Maybe someone else can give a bit more in depth explanation of what's happening. Hopefully my answer will push you in the right direction.

Thorncombe answered 10/12, 2013 at 23:47 Comment(5)
In the linked example, you can see that adjusting the X and Y values rotates the axes on the Z axis, so the Y axis always points toward the target. Adjusting the Z value should rotate the axes on the X axis. I'll clarify the question a bit.Chrissa
@Chrissa ahh your right I mixed up wording of values and axis. This makes more sense now. Your example is always pointing towards Z and rotating on Z which is why adjusting Z doesn't rotate it. Are you trying to make the axis helper rotate to point at the sphere controlled by the UI?Thorncombe
I'm not sure I understand your reasoning – I'm looking down the Z axis because that's how you get the Y axis to be "up". If you check the example fiddle, you can see that adjusting the Z value of the target doesn't change the X rotation of the axes, though I believe it should.Chrissa
So you weren't incorrect, but I didn't understand until after a comment by @Divert - I'll give you an upvote at least.Chrissa
No worries. I figured @Divert would eventually respond anyways. I should have clarified a bit, but I'm glad you were able to figure it out!Thorncombe
C
1

Here's how I came to understand the problem:

The lookAt and up vectors determine the orientation of an object like so:

  1. The lookAt vector is applied FIRST, which sets the X and Y rotations, locking the direction the object's Z axis points.
  2. THEN the up vector determines how the object rotates around the Z axis, to set the direction the object's Y axis points -- it won't affect the X and Y rotations at all.

In my example, the axisHelper is looking down its blue Z axis in the direction of the lookAt vector, which is a point in space at (0, 0, -1) -- so the X and Y rotations have already been set. The only thing left to do is figure out how to rotate the axisHelper around its Z axis, which means setting the X and Y points of the up vector -- moving the up vector forward and backward along the Z axis won't change anything.

Here's a fiddle with a demo illustrating this relationship: the blue arrow is the lookAt axis, and the green arrow is the up axis.

https://jsfiddle.net/hrjfgo4b/3

lookat and up vector demo

 Links to jsfiddle.net must be accompanied by code
Chrissa answered 12/12, 2013 at 21:28 Comment(0)
D
0

The .up, .lookAt and .position are all points defined in the world coordinate system. The physics definition of a vector is direction with a magnitude, but .up, .lookAt and .position take points as inputs, not vectors. It is important to make the distinction because in Three JS speak you are defining vectors (new THREE.Vector3()), but you are actually working with points in world space.

This is my interpretation. Internally, Three JS places the +z-axis of the object's local coordinate system from .position to .lookAt. Three JS defines the +y-axis of the local coordinate system to be from .position to .up.

Here is some code to help follow. The code below rotates the object's local coordinate system +90-degrees about the world's +z-axis. For test cases 1 and 2, converting the world points to the local coordinate system yields the correct and same result. For test case 3, the result is different and is due to the order of .lookAt, then .up.

  1. Use .up, then .lookAt
  2. Make sure to use updateMatrixWorld() after defining .position, .up and .lookAt
  3. Note that while you might expect to get zero, sometimes you will get a very small value, e.g. -2.220446049250313e-16. Such small values are equivalently zero.

// let THREE = require('three');
// import * as THREE from 'THREE';

let worldPointA = new THREE.Vector3(0, 0, 0);
let worldPointB = new THREE.Vector3(0, 1, 0);
let worldPointC = new THREE.Vector3(-1, 0, 0);
let worldPointD = new THREE.Vector3(0, 0, 1);

// Test Case 1
let newObject = new THREE.Object3D();
newObject.position.set(worldPointA.x, worldPointA.y, worldPointA.z);
newObject.rotateZ(Math.PI / 2);
newObject.updateMatrixWorld();
console.log('Position of points A, B and C in the objects local coordinate system');
console.log(newObject.worldToLocal(worldPointA.clone()));
console.log(newObject.worldToLocal(worldPointB.clone()));
console.log(newObject.worldToLocal(worldPointC.clone()));
// Position of points A, B and C in the objects local coordinate system
// Vector3 { x: 0, y: 0, z: 0 }
// Vector3 { x: 1, y: 2.220446049250313e-16, z: 0 }
// Vector3 { x: -2.220446049250313e-16, y: 1, z: 0 }

// Test Case 2
let newObjectB = new THREE.Object3D();
newObjectB.position.set(worldPointA.x, worldPointA.y, worldPointA.z);
newObjectB.up = worldPointC.clone();
newObjectB.lookAt(worldPointD.clone()); // lookAt should always come after .up
newObjectB.updateMatrixWorld();
console.log('Position of points A, B and C in the objects local coordinate system');
console.log(newObjectB.worldToLocal(worldPointA.clone()));
console.log(newObjectB.worldToLocal(worldPointB.clone()));
console.log(newObjectB.worldToLocal(worldPointC.clone()));
// Position of points A, B and C in the objects local coordinate system
// Vector3 { x: 0, y: 0, z: 0 }
// Vector3 { x: 1, y: 2.220446049250313e-16, z: 0 }
// Vector3 { x: -2.220446049250313e-16, y: 1, z: 0 }

// Test Case 3
let newObjectC = new THREE.Object3D();
newObjectC.position.set(worldPointA.x, worldPointA.y, worldPointA.z);
newObjectC.lookAt(worldPointD.clone());
newObjectC.up = worldPointC.clone();
newObjectC.updateMatrixWorld();

console.log('Position of points A, B and C in the objects local coordinate system');
console.log(newObjectC.worldToLocal(worldPointA.clone()));
console.log(newObjectC.worldToLocal(worldPointB.clone()));
console.log(newObjectC.worldToLocal(worldPointC.clone()));
// Position of points A, B and C in the objects local coordinate system
// Vector3 { x: 0, y: 0, z: 0 }
// Vector3 { x: 0, y: 1, z: 0 }
// Vector3 { x: -1, y: 0, z: 0 }
// ^^^^^^ Not correct
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.145.0/three.min.js"></script>
Detail answered 20/5, 2023 at 15:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.