Initial camera/scene position at canvas load with OrbitControls

Hello,

new to Three.js, love it. :heart_eyes:
Playing around with a very simple scene that has all elements within xyz [0, 1].
I’ve added OrbitControls and set the target, so that the scene rotates around the center of 0.5, 0.5, 0.5 (middle of [0, 1] scene). This works just fine after initial hiccups, see next.

I’m facing two issues:
(1) the scene initially renders with a canvas of [-1, 1], so my objects are not centered as all of my objects are within [0, 1] - as they will be using external data that is normalized [0, 1]

(2) once I click drag the mouse (to rotate the canvas), the scene now jumps into a different position (because of attached OrbitControls) and then stays there

I ultimately want to start the scene in a certain position and have OrbitControls attached so that user can pan/zoom/tilt/move the scene (without jumping) from that starting position.
I’ve tried re-positioning the scene and camera, but no luck.
The screenshot below shows the starting position I want.

How can I achieve that?

and this right here is the full code to re-create:

import * as THREE from 'three';
import {OrbitControls} from 'three/addons/controls/OrbitControls.js';

// -------- (1) canvas attrs --------

const w = 800;
const h = 800;

// -------- (2) create scene --------

const scene = new THREE.Scene();

// -------- (3) create camera --------

const camera = new THREE.PerspectiveCamera(35, w/h, 0.01, 3000);
camera.position.z = 3.5;

// -------- (4) create renderer --------

const renderer = new THREE.WebGLRenderer();
renderer.setSize(w, h);
document.body.appendChild(renderer.domElement);

// -------- (5) create objects --------

const gh_1 = new THREE.GridHelper(1, 10);
gh_1.position.x = 0.5;
gh_1.position.z = 0.5;
scene.add(gh_1);

const gh_2 = new THREE.GridHelper(1, 10);
gh_2.position.x = 0.5;
gh_2.position.z = 0;
gh_2.position.y = 0.5;
gh_2.rotation.x = 1.57;   // <-- mystery number
scene.add(gh_2);

const gh_3 = new THREE.GridHelper(1, 10);
gh_3.position.x = 0;
gh_3.position.z = 0.5;
gh_3.position.y = 0.5;
gh_3.rotation.x = 1.57;
gh_3.rotation.z = 1.57;
scene.add(gh_3);

// -------- (6) add orbit controls --------

const controls = new OrbitControls(camera, renderer.domElement);
controls.addEventListener('change', draw);
controls.target = new THREE.Vector3(0.5, 0.5, 0.5);

// -------- (7) render --------

function draw()
{
    renderer.render(scene, camera);
}

draw();

as a bonus question:
why does the rotation.x|z of Gridhelper has to be 1.57 in order to position it orthogonal to the bottom Gridhelper plane? I ended there merely by playing with the numbers. :roll_eyes:

As a side note, I do not use AnimationLoop because the scene will only have static data and only the user will pan/zoom/tilt/move the elements.

Thanks!

  1. Multiply it by 2 :eyes:
  1. If you want the user to be able to do absolutely anything (ie. the scene not to be a static image), you need the animation loop - it updates the rendering based on the camera position and elements in the scene. Adding a simple update loop will likely fix most of the issues you’ve just mentioned:
const animate = () => {
  requestAnimationFrame(animate);

  renderer.render(scene, camera);
};
animate();
1 Like

@mjurczyk
the AnimationLoop is not needed to pan/zoom/tilt/move the static scene.
If you run the code I provided you see it all works just fine (after the initial issues) without AnimationLoop and adding it does not solve the problem.

I mean - ain’t gonna force you to add it, but as soon as you do this (which I think is the part you’re missing in the code):

camera.position.set(1.0, 1.0, 1.0);
controls.target.set(0.5, 0.5, 0.5);
controls.update();

You’ll notice why it is indeed needed. If you want to be able control whether the rendering is updated or not, consider having a conditional in the rendering loop - that way you won’t be binding yourself to calling draw() manually (which becomes unmanageable as soon as the code grows above 500 LoC or adds asynchronicity - you’ll be forced to predict whether or not something, or more than one thing, has or has not changed at a given time, leading to random “chunks” of scene state to be rendered):

let paused = false; // NOTE Control the loop by toggling this value instead of calling animate manually

const animate = () => {
  requestAnimationFrame(animate);

  if (paused) return;

  renderer.render(scene, camera);
};
animate();

Is not having an animation loop possible? Yes.
But does it make your life significantly harder at little-to-no-gain? Also yes.

1 Like

Are you setting controls.target when you want to reposition the camera manually?

If you are manually re-orienting the camera, you either need to

controls.enabled = false //Disable orbitcontrols…

or… after you move the camera, make orbitcontrols setup valid by adjusting its control.target (orbit center point) to something valid for the current camera.

You can do something like…

... adjust camera.position, make it do something.. etc ...

then:

let dist = controls.target.distanceTo(camera.position);

controls.target.set(0,0,-dist).applyQuaternion(camera.quaternion).add(camera.position)

// Update the controls.target to a point in front of the camera to prevent it snapping on its next update. (You may have to flip the sign from - to + I cant remember...)
camera.position.set(1.0, 1.0, 1.0);

this worked and helped, thanks!
but on it’s own, it completely moves the scene off-screen.
had to add this in order for the scene to stay in view port.

camera.lookAt(0, 0, 0);

this cmd does not work and throws errors. you can test this yourself by adding your code to the OP - the docs also state that this cmd is only needed in two (2) very specific cases.

controls.update();

Is not having an animation loop possible? Yes.
But does it make your life significantly harder at little-to-no-gain? Also yes.

point is, as stated before, that AnimationLoop is not needed at all here in this use case scenario. This is static data that will only ever be animated when the user interacts with the scene via the OrbitControls. which do not require AnimationLoop.
As stated before, adding AnimationLoop does not solve the issue.
But most importantly, it’s adding a frame loop at X fps to the user’s system for zero benefit. Even if one exits the draw() fn early via paused flag, the loop still calls the fn X times per second for no reason.
this canvas will be displaying 10K-50K of data points - absolutely counter productive to run a useless frame loop.

thanks for the help, appreciated!

note: for other users facing issue (2) from the OP (jumping when orbit controls kick in for the 1st time):
when you position the camera and the individual x|y|z values do not match (hence: the coordinates have different values), then the x,y,z values for orbit controls.target have to have the same ratio as the x,y,z values for camera.position.

HTH.

camera.lookAt takes a Vector3 parameter, not 3 floats.

AnimationLoop{
controls.update() is needed if you plan to have any damping or smoothing on your controls.

animationLoop also gets throttled when the tab is hidden (doesn’t get called)
or when it’s not the focused tab (runs at a lower rate)

Most folks here are using threejs as a realtime 3d viewer, not a static shot renderer, so that may be some of the source of your skepticism.

10K-50K of data points - absolutely counter productive to run a useless frame loop.

10k-50k aren’t huge numbers.
We run scenes with ~1 to 3 million triangles @ 60/144fps on the reg.

If you’re topping out rendering 10k to 50k points, there may be some other areas in your code that could be optimized.

Glad you got things sorted out! :smiley:

1 Like

camera.lookAt takes a Vector3 parameter, not 3 floats.

copy that.
the code I posted, using the 3 floats as input, works just fine on latest 0.167.1.
That Vector3 is ultimately a Tristimulus, an array/list of three (3) numbers used in a 3D coordinate system - camera.lookAt() seems to be able to handle the float input args.

AnimationLoop{
controls.update() is needed if you plan to have any damping or smoothing on your controls.
animationLoop also gets throttled when the tab is hidden (doesn’t get called)
or when it’s not the focused tab (runs at a lower rate)

yup, all clear, all stated in the docs.
but not used in our case hence no need to tax the user’s system with a useless loop.

Most folks here are using threejs as a realtime 3d viewer, not a static shot renderer, so that may be some of the source of your skepticism.

zero skepticism. If the animation is conditional, hence the only time the animation is needed when the condition kicks in. which in the case above, is when the user decides to animate the data, and then orbit controls handle everything just fine.

We run scenes with ~1 to 3 million triangles @ 60/144fps on the reg.

on what sys spec (of your machines), what animation spec and what sys specs does your avg target demographic have? w/o this context that statement means almost nothing.
if we display 50K data points and the user decides to very quickly, rapidly rotate the data, zoom, tilt. pan etc, (–> hence: continuous, fast animation) then the GPU needs to be able to handle all of this.
If client device is a machine/mobile device from 2010 with very low end sys specs, then this may cause issues.
horses for courses, paired with the #1 programming rule to be very mindful of the user’s system resources : which is why one never taxes any system unless you have to. :slightly_smiling_face:

1 Like

You’re welcome. :slight_smile: