4

I am using three.js to load a gltf scene which has a camera. The camera and model shows well but when I try to apply OrbitControls.js to that loaded camera it won't work. It only works with cameras created directly in the three.js with the new THREE.PerspectiveCamera(). I leave the code below, I have commented the play() function just to try this first.

        var container, stats, clock, controls;
        var camera, scene, renderer, mixer;

        init();
        animate();              


        controls.update();


        function init() {
            container = document.getElementById( 'container' );

            scene = new THREE.Scene();
            clock = new THREE.Clock();
            var loader = new THREE.GLTFLoader();

            var ruta = 'file.gltf';

            loader.load( ruta, function ( data ) {


                handleSceneLoaded(data);

                var animations = data.animations;
                var avatar = data.scene;
                mixer = new THREE.AnimationMixer( avatar );
                NumberOfAnimations = animations.length;
                for ( var i=0; i<NumberOfAnimations; i++){
                    //mixer.clipAction( animations[ i ] ).play();
                }

                scene.add( avatar );
            } );
            //
            scene.background = new THREE.Color( 0xefe3a7 );

            renderer = new THREE.WebGLRenderer( { antialias: true } );
            renderer.setPixelRatio( window.devicePixelRatio );
            renderer.setSize( window.innerWidth, window.innerHeight );
            container.appendChild( renderer.domElement );
        }

        function handleSceneLoaded(data){
            scene.add(data.scene);
            var result={};
            data.scene.traverse(function(n){traverseScene(n,result);});

            if (result.cameras && result.cameras.length){

                camera = result.cameras[0];

                controls = new THREE.OrbitControls(camera);
                controls.screenSpacePanning = true;
            }else{
                camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
                camera.position.set( 15, 10, - 15 );
            }

            if(result.lights && result.lights.length){
                for (i=0;i<result.lights.length;i++){
                    var lamp = result.lights[i];
                    scene.add(lamp);
                }
            }else {
                var ambientLight = new THREE.AmbientLight( 0xffffff, 0.2 );
                scene.add( ambientLight );
                var directionalLight = new THREE.DirectionalLight( 0xffffff, .8 );
                directionalLight.position.set( 1, 1, - 1 );
                scene.add( directionalLight );
                var directionalLight2 = new THREE.DirectionalLight( 0xffffff, .8 );
                directionalLight2.position.set( -5, -5,  10 );
                scene.add( directionalLight2 );
            }

        }

        function traverseScene(n, result){
            if (n instanceof THREE.Camera){
                if(!result.cameras)
                    result.cameras = [];

                result.cameras.push(n);
            }
        }

        function animate() {
            requestAnimationFrame( animate );
            render();

        }
        function render() {
            var delta = clock.getDelta();
            if ( mixer !== undefined ) {
                mixer.update( delta );
            }
            renderer.render( scene, camera );
        }

Edit1: I finally solved it by assinging the loaded camera to a variable blenderCamera and then creating the real camera and copying the values from the loaded camera and its parent:

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

            blenderCamera = result.cameras[0];
            camera.position.x = blenderCamera.parent.position.x;
            camera.position.y = blenderCamera.parent.position.y;
            camera.position.z = blenderCamera.parent.position.z;

            camera.aspect = blenderCamera.aspect;
            camera.fov = blenderCamera.fov;
            camera.far = blenderCamera.far;
            camera.near = blenderCamera.near;
AlvaroMerino
  • 125
  • 7

1 Answers1

0

It seems that the GLTF exporter for Blender likes to create a parent object for exported cameras, as you've noticed in your workaround.

Defining behaviour for an orbital camera that works consistently and makes sense while that camera is potentially simultaneously being moved around the scene by the transformation of its parent is understandably difficult— as well as an edge case— so it looks like OrbitControls just fails when applied to a camera with a parent object.


Try detaching the camera from its parent object:

scene.attach(blenderCamera)

This will remove the parent attribute of the imported camera, while also applying the inverse transformation matrices of its parents so its position and orientation in the scene stay the same. You should be able to check blenderCamera.parent == null afterwards as well, which is the same as the hard-coded cameras that were working before.


Note: It looks scene.attach is currently the canonical way to de-parent an object. It apparently used to be SceneUtils.detach, which is now marked as deprecated in the source code. You could also try directly setting .parent = null, but you'd then probably lose the transformation from the parent in the best case scenario. With only one parent and a zero parent-space transform, you could circumvent that by manually copying in coordinates, but you might have to start applying the transformation matrices yourself for anything more complex or robust.

Will Chen
  • 482
  • 4
  • 12
  • Unfortunately, this means that any animations on the `blenderCamera` will be invalidated. Any possibility of applying OrbitControls to `blenderCamera.parent` or something like that? – Alec Jacobson May 08 '22 at 15:40
  • It seems that this is a by-product of how Blender does the +Y conversion for gltf export. Rather than changing the camera transformation directly, it creates this dummy child object. – Alec Jacobson May 08 '22 at 18:29