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

meetar
  • 7,443
  • 8
  • 42
  • 73
  • 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. – WestLangley Dec 11 '13 at 03:04
  • I'm just trying to understand how up vectors and lookAt() work together. I'll clarify the question. – meetar Dec 11 '13 at 15:36

4 Answers4

15

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.

WestLangley
  • 102,557
  • 10
  • 276
  • 276
  • 1
    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. – meetar Dec 11 '13 at 16:01
  • In your fiddle, the target vector never changes, so the z-axis always points in the same direction. – WestLangley Dec 11 '13 at 16:24
  • 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? – meetar Dec 11 '13 at 17:46
  • 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. – meetar Dec 11 '13 at 18:26
  • 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… https://github.com/mrdoob/three.js/issues/1752#issuecomment-5206241 – meetar Dec 12 '13 at 20:35
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.

Grant
  • 532
  • 2
  • 7
  • 21
  • 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. – meetar Dec 11 '13 at 00:17
  • @meetar 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? – Grant Dec 11 '13 at 01:12
  • 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. – meetar Dec 11 '13 at 01:32
  • So you weren't incorrect, but I didn't understand until after a comment by @WestLangley - I'll give you an upvote at least. – meetar Dec 11 '13 at 18:48
  • No worries. I figured @WestLangley would eventually respond anyways. I should have clarified a bit, but I'm glad you were able to figure it out! – Grant Dec 11 '13 at 19:37
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
meetar
  • 7,443
  • 8
  • 42
  • 73
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>
Chris A.
  • 91
  • 5