Search code examples
javascripthtmlthree.jsorbitcontrols

OrbiterControls not working properly in three.js app


I'm trying to create a basic scene with an orbitercontrols that can rotate around the center, i.e. always looking at (0,0,0). I want it to behave like this https://threejs.org/examples/misc_controls_orbit.html exactly. But in my version if I click and drag to the left/right, the camera just spins in a circle always looking at (0,0,0). How can I fix this?

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>My first three.js app</title>
        <style>
            body { 
                margin: 0; 
            }
        </style>
    </head>
    <body>
        <script type="module">
            import * as THREE from 'https://threejs.org/build/three.module.js';
            import { OrbitControls } from 'https://threejs.org/examples/jsm/controls/OrbitControls.js';

            const scene = new THREE.Scene();
            const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

            const renderer = new THREE.WebGLRenderer();
            renderer.setSize( window.innerWidth, window.innerHeight );
            document.body.appendChild( renderer.domElement );

            // ADD LIGHT
            const light = new THREE.PointLight(0xffffff, 2)
            light.position.set(0, 5, 10)
            scene.add(light)

            // ADD ORBITER
            const controls = new OrbitControls( camera, renderer.domElement );
            controls.listenToKeyEvents( window );
            controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
            controls.dampingFactor = 0.05;
            //controls.screenSpacePanning = false;
            //controls.minDistance = 100;
            //controls.maxDistance = 500;
            //controls.maxPolarAngle = Math.PI / 2;
            //controls.minPolarAngle = Math.PI / 2;
            //controls.maxPolarAngle = Math.PI / 2;

            camera.position.set(0, -5, 5);
            camera.lookAt( 0, 0, 0 );
            camera.up.set( 0, 0, 1 );
            //controls.target = new THREE.Vector3(0, 0, 0);
            controls.update();


            // ADD CUBE
            const geometry = new THREE.BoxGeometry();
            const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
            const cube = new THREE.Mesh( geometry, material );
            scene.add( cube );


            // ADD MAP
            const loader = new THREE.TextureLoader();
            const height = loader.load('/data/images/height.png');
            const texture = loader.load('/data/images/height.png');

            const map_geometry = new THREE.PlaneBufferGeometry(10,10,64,64);
            const map_material = new THREE.MeshStandardMaterial({
                color:'orange',
                //side: THREE.DoubleSide,
                map:texture,
                displacementMap: height,
                displacementScale: 0.5,
                //alphaMap: alpha,
                //transparent:true
            });

            const map_plane = new THREE.Mesh(map_geometry, map_material);
            //map_plane.position.set(0, 0, 0);
            scene.add( map_plane );

            // RENDER
            function animate() {
                requestAnimationFrame( animate );
                //cube.rotation.x += 0.01;
                //cube.rotation.y += 0.01;
                renderer.render( scene, camera );
            }
            animate();
        </script>
    </body>
</html>

Solution

  • The code needed several modifications. Use the code below instead and compare it with yours to find out which parts did I change.

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <title>My first three.js app</title>
            <style>
                body {
                    padding: 0;
                    margin: 0;
                    overflow: hidden;
                }
            </style>
        </head>
        <body>
            <script type="module">
                import * as THREE from 'https://threejs.org/build/three.module.js';
                import {OrbitControls} from 'https://threejs.org/examples/jsm/controls/OrbitControls.js';
    
                // BUILDING THE SCENE
                const scene = new THREE.Scene();
    
                // ADDING CAMERA
                const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
                camera.position.set(0, -5, 5);
                scene.add(camera);
    
                // CREATING THE RENDERER
                const renderer = new THREE.WebGLRenderer();
                renderer.setSize(window.innerWidth, window.innerHeight);
                document.body.appendChild(renderer.domElement);
    
                // ADD LIGHT
                const light = new THREE.PointLight(0xffffff, 2);
                light.position.set(0, 5, 10);
                scene.add(light);
    
                // ADD ORBITER
                const controls = new OrbitControls(camera, renderer.domElement);
                controls.maxPolarAngle = Math.PI/2.2;
                controls.minDistance = 5;
                controls.maxDistance = 10;
                controls.target.set(0, 0, 0);
    
                // ADD CUBE
                const geometry = new THREE.BoxGeometry();
                const material = new THREE.MeshBasicMaterial({color: 0x00ff00});
                const cube = new THREE.Mesh(geometry, material);
                cube.position.set(0, .5, 0);
                scene.add(cube);
    
                // ADD MAP
                const loader = new THREE.TextureLoader();
                const height = loader.load('/data/images/height.png');
                const texture = loader.load('/data/images/height.png');
    
                const map_geometry = new THREE.PlaneBufferGeometry(10,10,64,64);
                const map_material = new THREE.MeshStandardMaterial({
                    color:'orange',
                    side: THREE.DoubleSide,
                    map:texture,
                    displacementMap: height,
                    displacementScale: 0.5,
                });
    
                const map_plane = new THREE.Mesh(map_geometry, map_material);
                map_plane.rotation.x = Math.PI/2;
                scene.add(map_plane);
    
                window.addEventListener('resize', onWindowResize, false);
    
                function onWindowResize() {
                    camera.aspect = window.innerWidth / window.innerHeight;
                    camera.updateProjectionMatrix();
                    renderer.setSize(window.innerWidth, window.innerHeight);
                }
    
                // ANIMATING THE SCENE
                function animate() {
                    controls.update();
                    requestAnimationFrame(animate);
                    renderer.render(scene, camera);
                }
                animate();
            </script>
        </body>
    </html>
    
    1. Write controls.update(); in the animate function.
    2. controls.maxPolarAngle = Math.PI/2.2; doesn't let the camera to go under the ground.
    3. controls.target.set(0, 0, 0); makes the control stair at the center.
    4. I added map_plane.rotation.x = Math.PI/2; so the plane will be placed horizontally as the floor.
    5. And side: THREE.DoubleSide, helps the floor to be visible from all angels.
    6. I also added an onWindowResize function to the script to make the applicaion responsive. Now, it'll response to the browser's width and height when its resized.
    7. In case of CSS, the page has default padding as well. So, adding a padding: 0; to the CSS code is not a bad idea as well.