Ben
Ben

Reputation: 16689

How does rotation influence an orthographic camera in Three.js

In Three.js, I have a scene that contains a plane and an orthographic camera.


Orthographic camera at -90deg:

If the camera is rotated to -90 deg on the x-axis (i.e. if it is looking straight down at the floor from above), then I can see the whole plane - and only the plane.

Scene setup:

enter image description here

What the camera sees:

enter image description here


Orthographic camera at -40deg:

But if the camera is rotated to e.g. -40 deg on the x-axis, then I can see the whole plane, plus other parts of the scene. Meaning, due to its rotation, the camera "sees more".

Scene setup:

enter image description here

What the camera sees:

enter image description here


My question is:


PS: To reiterate, this is an orthographic camera. The camera's left, right, top, and bottom are currently hardcoded to take into account the height of the plane and the aspect ratio of the screen. The camera's position is (0,0,0). So is the plane's position.

camera = new THREE.OrthographicCamera(
    -planeZ * aspectRatio / 2;
    planeZ * aspectRatio / 2;
    planeZ / 2;
    -planeZ / 2;
    -100,
    100
);
camera.position.set(0,0,0);

Update

I have created a code example based on @Trentium's code, but with significant changes, e.g. removed OrbitControls and used the angle calculation from @Marquizzo. See https://plnkr.co/edit/UxO37ShEAiJNAK8H?preview

Upvotes: 1

Views: 1120

Answers (2)

Trentium
Trentium

Reputation: 3719

Here's an implementation of @Marquizzo's answer (so please give him credit if this answers the mail), with some minor adjustments:

  • The example code is primarily lifted from the three.js orthographic example with some minor tweaks to the location of the main 300x300 plane, to ensure it passes through the XYZ origin.
  • The primary code adjustments to implement @Marquizzo's answer can be found near the bottom of the Snippet in the animate() function.
  • To calculate the camera angle relative to the main 300x300 XZ plane centered on the XYZ origin, this involves calculating the distance of the camera to the XYZ origin and then finding the angle between the camera's Y coordinate and the XZ plane. (From a trig perspective, this is taking the arcsin of the camera's Y position divided by the radius, in this case the distance to the origin.)
  • Note that since sin is being used to calculate the final frustums, there is no need to perform the arcsin and then reapply sin, but instead the camera.position.y / r can be used straight up in the frustum calculations. For clarity's sake(?!), I left the trig functions in...
  • Additionally, the bounds of the XZ plane (ie, 150) are hard coded for clearer understanding, and a minor adjustment of 10% (ie, * 1.10 ) is added to each frustum to provide a bit of border on the 300x300 plane to better see the results of the frustum adjustments while orbiting the scene.

Best to enter "Full page" mode after starting the code snippet...

<!DOCTYPE html>
<html>

    <body>
    

    <script src="https://rawgit.com/mrdoob/three.js/master/build/three.js"></script>
    <script src="https://rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
    <script src="https://rawgit.com/mrdoob/three.js/master/examples/js/renderers/CSS3DRenderer.js"></script>

        <script>

            let camera, scene, renderer, controls;

            let scene2, renderer2;

            const frustumSize = 500;

            init();
            animate();

            function init() {

                const aspect = window.innerWidth / window.innerHeight;
                camera = new THREE.OrthographicCamera( frustumSize * aspect / -2, frustumSize * aspect / 2, frustumSize / 2, frustumSize / -2, 1, 1000 );

                camera.position.set( - 200, 200, 200 );

                scene = new THREE.Scene();
                //scene.background = new THREE.Color( 0xf0f0f0 );
        scene.background = new THREE.Color( 0x000000 );

                scene2 = new THREE.Scene();

                //const material = new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true, wireframeLinewidth: 1, side: THREE.DoubleSide } );
        const material = new THREE.MeshBasicMaterial( { color: 0x000000, side: THREE.DoubleSide} );

                // left
                createPlane(
                    100, 100,
                    'chocolate',
                    new THREE.Vector3( - 50, 0, 0 ),
                    new THREE.Euler( 0, - 90 * THREE.MathUtils.DEG2RAD, 0 )
                );
                // right
                createPlane(
                    100, 100,
                    'saddlebrown',
                    new THREE.Vector3( 0, 0, 50 ),
                    new THREE.Euler( 0, 0, 0 )
                );
                // top
                createPlane(
                    100, 100,
                    'yellowgreen',
                    new THREE.Vector3( 0, 50, 0 ),
                    new THREE.Euler( - 90 * THREE.MathUtils.DEG2RAD, 0, 0 )
                );
                // bottom
                createPlane(
                    300, 300,
                    'seagreen',
                    new THREE.Vector3( 0, - 0, 0 ),
                    new THREE.Euler( - 90 * THREE.MathUtils.DEG2RAD, 0, 0 )
                );

                //

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

                renderer2 = new THREE.CSS3DRenderer();
                renderer2.setSize( window.innerWidth, window.innerHeight );
                renderer2.domElement.style.position = 'absolute';
                renderer2.domElement.style.top = 0;
                document.body.appendChild( renderer2.domElement );

                controls = new THREE.OrbitControls( camera, renderer2.domElement );
                controls.minZoom = 0.5;
                controls.maxZoom = 2;

                function createPlane( width, height, cssColor, pos, rot ) {

                    const element = document.createElement( 'div' );
                    element.style.width = width + 'px';
                    element.style.height = height + 'px';
                    element.style.opacity = 0.75;
                    element.style.background = cssColor;


                    const object = new THREE.CSS3DObject( element );
                    object.position.copy( pos );
                    object.rotation.copy( rot );
                    scene2.add( object );

                    const geometry = new THREE.PlaneGeometry( width, height );
                    const mesh = new THREE.Mesh( geometry, material );
                    mesh.position.copy( object.position );
                    mesh.rotation.copy( object.rotation );
          
                    scene.add( mesh );

                }

                window.addEventListener( 'resize', onWindowResize );

            }

            function onWindowResize() {

                const aspect = window.innerWidth / window.innerHeight;

                camera.left = - frustumSize * aspect / 2;
                camera.right = frustumSize * aspect / 2;
                camera.top = frustumSize / 2;
                camera.bottom = - frustumSize / 2;

                camera.updateProjectionMatrix();

                renderer.setSize( window.innerWidth, window.innerHeight );

                renderer2.setSize( window.innerWidth, window.innerHeight );

            }

            function animate() {

                requestAnimationFrame( animate );
        
        
        
        /**********************************************************/
        // 
        // Code added to adjust frustum...
        //
        
        function distanceToOrigin( p ) {
          return Math.sqrt( p.x * p.x + p.y * p.y + p.z * p.z );
        }

        camera.position.z = 0;
      
        let r = distanceToOrigin( camera.position );
        let angle = Math.asin( camera.position.y / r );
        
        camera.left = -150 * Math.sin( angle ) * 1.10;
        camera.right = 150 * Math.sin( angle ) * 1.10;
        camera.top = 150 * Math.sin( angle ) * 1.10;
        camera.bottom = -150 * Math.sin( angle ) * 1.10;
        camera.updateProjectionMatrix();

        controls.update();
        
        //
        /**********************************************************/



                renderer.render( scene, camera );
                renderer2.render( scene2, camera );

            }
      
      

      
      
        </script>
    </body>
</html>

Upvotes: 1

M -
M -

Reputation: 28497

How does rotation influence an orthographic camera in Three.js?

What you're describing is a basic rotation of a square, Three.js doesn't do anything out of the ordinary. When the square is at right (90°) angles, the dissecting plane is the width of the square. When the square is at 45° angles, the dissecting plane is the diagonal of the square, which is √2 times bigger than the width, that's why it gives the impression that it covers a bigger area.

Solution

I think you should be able to achieve the scaling of your camera's top and bottom attributes with a simple sine function. When the camera is at 90°, it'll be at max height, but as it approaches 0, it'll become infinitely thin.

const maxSide = planeZ / 2;

// Set camera rotation
const angle = THREE.MathUtils.degToRad(90);
camera.rotation.x = angle;

// Scale frustum
camera.top = maxSide * sin(angle);
camera.bottom = -maxSide * sin(angle);
camera.updateProjecionMatrix();

Upvotes: 2

Related Questions