Apply new camera world direction

If I understand things correctly, then a camera’s world direction is the direction the camera is looking in world space. I can move a camera around using OrbitControls and observe the changes to the camera world direction as I do so, but instead of using OrbitControls I need to specify the exact world direction in order to attempt to replicate a render that was made with a different drawing program completely outside of Three.js. I can set up some sliders in lil-gui, or even text fields to type into, but how can I apply the values obtained as a new camera world direction. In other words, given x,y,z values, how can I make a camera use those as its world direction? And do I need to normalize them first? Thanks.

camera.lookAt(x, y, z);

This will make camera to look at the coordinates from its current position. So the world direction will be defined by the vector going from camera.position to (x, y, z).

Thank you for this reply, but I cannot seem to get the new camera world direction to actually change. Here is some contrived code I’ve tried:

        // Get current world direction.
        let wd = new Vector3 ();
        camera.getWorldDirection (wd);
        console.log ('current world direction is', wd2);

        // Change it some arbitrary amount and apply with lookAt() method.
        wd.x += .5;
        camera.lookAt (wd.x, wd.y, wd.z);

        // Print the new world direction.
        let wd2 = new Vector3 ();
        camera.getWorldDirection (wd2);
        console.log ('new world direction is', wd2);

My result is that the new camera world direction after the lookAt() is unchanged. What am I doing wrong? Thanks.

My be u using controls.update();

Your code works for me, you can see the new direction in the console:

maybe something else in your code interferes with the camera, like controls or lil-gui?

Thanks for testing. I have used both OrbitControls and TransformControls, but both are currently disabled for testing.

And I am also using lil-gui where I display the current world direction values, but in a read-only form. But even if I disable that, I still see the same behavior.

I have managed to learn a little more but grown only more confused. If I apply an absurdly large change, then I do see a change in the render. For example, starting with a world dir of (0,0,-1) and changing x so that I get (500, 0, -1) and then calling camera.lookAt(), then the world dir and the render do indeed change. However, asking for camera.getWorldDirection() again, I see

x: 0.06820647702598776
y: 0.40923886215592664
z: -0.9098744035266768

That’s is a definite change from the original (0, 0, -1), but somehow the 500 for x got changed to 0.0682…, and the y and z changed too. There must be some normalization of some kind going on here.

What I really need to do is set the world direction to my desired value. In one particular case, I want (0.26, -0.6, -0.10), but if I try that, I get (-0.15, 0.406, -0.9), where the y and z are curiously not all that different from the values shown above when I arbitrarily tried (500, 0, -1). Any ideas what else might be interacting to cause this behavior? Thanks again.

And if I normalize my desired world direction vector, I still don’t get what I want. For example, if I want (0.26, -0.6, -0.10), which normalizes to (0.393, -0.907, -0.151), and pass the normalized value to lookAt(), I end up with (-0.157, 0.405, -0.901).


It will be much easier to tell if you make a simple working example showing this issue and put your code online on jsfiddle or codepen.

I’ve never done that, not sure how, but can probably figure it out. First, though, I’ll have to extract a simple working example, and that’s going to be an effort. Thanks for your help so far.

1 Like

Okay, this should be a super-simple idiot-level question. I have shrunk my code down to a reasonably-sized example, but I’m having trouble getting it into jsfiddle or codepen. All the javascript is in a file named main.js, and my index.html contains

  <script type="module" src="./main.js"></script>

to load that main.js file, but I have not figured out how to get that into jsfiddle. If I just paste my index.html and my main.js files into the appropriated panes, jsfiddle doesn’t see main.js. Or perhaps more accurately, I don’t know how to change the index.html script tag in the jsfiddle html pane to point to the javascript code in the javascript pane.

Should be easy but has not become obvious to me yet.

Alternatively, I could just paste my entire main.js file here. It’s 119 lines with minimal comments.


Yes, you could do that.

Also, you could use codepen instead of jsfiddle, it allows you to upload separate files and folders, so you could have the same structure as you have now without flattening it into a single file.

Okay, the path of least resistance is to just paste the code here. This code demonstrates the problems I’m having with lookAt(). There is a lil-gui “function” button that gets the current world direction (0, 0, -1), adds 5 to the x-component, producing (5, 0, -1), normalizes it to (0.98, 0, -.196), and then calls lookAt() with the new direction.

Immediately after the lookAt(), it calls getWorldDirection() again, showing that the actual world direction achieved (-.15, -.15, -.98) is NOT what was passed to lookAt().

Pressing the lil-gui function button again does the same thing, adds 5, this time creating (4.85, -.15, -.98), which normalizes to (.98, -.031, -.197), quite similar to the first normalization above. When passed to lookAt(), the result is again (-.15, -.15, -.98) with slight differences down in the 7th or 8th decimal place. Somehow something somewhere really likes that (-.15, -.15, -.98) world direction.

Code follows. Any help on what I am so gravely misunderstanding will be greatly appreciated. Thanks.

import * as THREE from ''
import GUI from '';

let self, fThreeCamera, fScene, fRenderer, fLilGui, fDebuggingProps;

function main () {
    new LookAtDemo ();
} // main

class LookAtDemo {
    constructor () {
        self = this;
        console.log ('Three.js revision ' + THREE.REVISION);

        const container = document.querySelector ('#mycontainer');
        fRenderer = new THREE.WebGLRenderer ({ antialias: true });
        fRenderer.physicallyCorrectLights = true;
        container.append (fRenderer.domElement);

        // Scene and lights.
        fScene = new THREE.Scene ();
        fScene.background = new THREE.Color ('skyblue');
        const mainlight = new THREE.DirectionalLight ('white', 4);
        mainlight.position.set (500, 300, 1500);
        const ambientlight = new THREE.HemisphereLight ('white', 'darkslategrey', 2); (0, 0, 600); ();
        fScene.add (mainlight);
        fScene.add (;
        fScene.add (ambientlight);

        // Add spheres.
        const mat1 = new THREE.MeshLambertMaterial ({ color: 0x0000ff });
        const sphere1 = new THREE.Mesh (new THREE.SphereGeometry (30, 16, 8), mat1);
        sphere1.position.set (100, 300, 600);
        fScene.add (sphere1);

        const mat2 = new THREE.MeshLambertMaterial ({ color: 0x6d3e99 });
        const sphere2 = new THREE.Mesh (new THREE.SphereGeometry (50, 16, 8), mat2);
        sphere2.position.set (40, 800, 650);
        fScene.add (sphere2);
        // Create a Camera.
        fThreeCamera = new THREE.PerspectiveCamera (35, 1, .1, 5000);
        fThreeCamera.position.set (500, 500, 3200);
        fLilGui = new GUI ({width:300});
            // Show camera position, quat, and world direction in lil-gui.
            fDebuggingProps = {
                CamPosition: '',
                CamQuat: '',
                CamWorldDir: ''
            fLilGui.add (fDebuggingProps, 'CamPosition').name ('Camera Position').listen ();
            fLilGui.add (fDebuggingProps, 'CamQuat').name ('Camera Quat').listen ();
            fLilGui.add (fDebuggingProps, 'CamWorldDir').name ('Camera World Dir').listen ();
            self.updateDebuggingProps ();

        // Set up a lil-gui button to force a simple world direction change.
        const change_lookat_callback = {
            lookatCallback: function() { self.changeLookAtCallback (); }
        fLilGui.add (change_lookat_callback, 'lookatCallback').name ('Change Look At');
        fScene.add (new THREE.GridHelper (1000, 10));
        fScene.add (new THREE.AxesHelper (1000));

        fRenderer.setSize (window.innerWidth, window.innerHeight);
        fRenderer.render (fScene, fThreeCamera);
    } // ctor

    //	METHOD:		changeLookAtCallback()				-
    changeLookAtCallback () {
        let wd = new THREE.Vector3 ();
        fThreeCamera.getWorldDirection (wd);
        console.log ('\n');
        console.log ('changeLookAtCallback: incoming world direction is', wd);
        wd.x += 5;
        console.log ('changeLookAtCallback: changed to', wd);
        wd.normalize ();
        console.log ('changeLookAtCallback: after normalization', wd);
        // Apply lookAt().
        fThreeCamera.lookAt (wd);

        // Fetch the world direction again, to see what we really got.
        let wd2 = new THREE.Vector3 ();
        fThreeCamera.getWorldDirection (wd2);
        console.log ('changeLookAtCallback: after calling lookAt(), the actual world direction is', wd2);
        self.updateDebuggingProps ();
        fRenderer.render (fScene, fThreeCamera);
    } // changeLookAtCallback

    //	METHOD:		updateDebuggingProps()				-
    updateDebuggingProps () {
        if (fDebuggingProps) {
            fDebuggingProps.CamPosition = fThreeCamera.position.x.toFixed (2) + ', ' + fThreeCamera.position.y.toFixed (2) + ', ' + fThreeCamera.position.z.toFixed (2);
            fDebuggingProps.CamQuat = fThreeCamera.quaternion.x.toFixed (3) + ', ' + fThreeCamera.quaternion.y.toFixed (3) + ', ' + fThreeCamera.quaternion.z.toFixed (3) + ', ' + fThreeCamera.quaternion.w.toFixed (3);

            let worlddir = new THREE.Vector3 ();
            fThreeCamera.getWorldDirection (worlddir);
            fDebuggingProps.CamWorldDir = worlddir.x.toFixed (4) + ', ' + worlddir.y.toFixed (4) + ', ' + worlddir.z.toFixed (4);
    } // updateDebuggingProps
} // class LookAtDemo
export { LookAtDemo };
main ();

World direction vector is a “forward” vector of an object, z-axis of its local coordinate system.
As a vector, it can be defined by two points, the start point and the end point (the end point minus the start point).

The current object (camera) position is the start point, lookAt function sets the end point.
The world direction is the difference between the end and the start point (normalized).

Originally, the camera z-axis is looking down the world z-axis, so the direction is (0, 0, -1). When you move the camera, the direction doesn’t change, the camera is still looking down (parallel to) the world z-axis, just from a different point (500, 500, 3200), it doesn’t rotate because you moved it.

If you call lookAt, the camera will rotate and look at the point provided (the end point) and the difference between that end point and the current position will form the world direction.

So in your code, you pass the world direction to lookAt function and that doesn’t make much sense (the camera will look at the normalized point (5, 0, -1)).

You can also look at quaternions, they hold the camera orientation in space, you can use the camera quaternion to check the current look at point (assuming this point is one unit away along the local z-axis) like so:

let start = fThreeCamera.position;
// original camera z (forward) vector is 0, 0 -1. Current rotation is stored in the camera quaternion
let forward = new THREE.Vector3(0, 0, -1).applyQuaternion(fThreeCamera.quaternion); 
let lookat = new THREE.Vector3().copy(start).add(forward);

Since the camera is looking along a line, there is infinite look at points along it, it might matter which one to choose, if your code rotates the camera around the look at point, for example.

Thank you tfoller for this explanation. I’m actually not working today, and your explanation hasn’t quite soaked into my brain yet. I will try to grok it all and make changes to my code to try to get it to do what I want when I’m back on Monday. Thanks.

Thanks again, tfoller. I have marked your message as the solution to my original query. I was under the mistaken idea that getting the world direction actually gave the look-at point in some funky normalized fashion. My bad. 3D geometries make my head hurt.

Anyway, your code snippet showed me how to get the actual look-at point. Applied in the context of my simplified example, I can now get the look-at point, increment its x-component, and pass that modified value to the camera’s lookAt() method to accomplish the change I was wanting.

Now, on to applying what you have helped me learn to my original problem in my original context…

Thanks again.

1 Like