What impact does rotation have on an orthographic camera within the Three.js framework?

I am working in Three.js with a scene that includes a plane and an orthographic camera.


Orthographic camera at -90deg:

When the camera is rotated to -90 deg on the x-axis (looking straight down from above), only the plane is visible in the view.

Scene setup:

https://i.sstatic.net/fsVQM.png

What the camera captures:

https://i.sstatic.net/bg8y9.png


Orthographic camera at -40deg:

However, when the camera is rotated to around -40 deg on the x-axis, more of the scene becomes visible along with the plane. The rotated angle expands the camera's field of view.

Scene setup:

https://i.sstatic.net/EW674.png

What the camera captures:

https://i.sstatic.net/dBAPC.png


My queries are:

  • How can I determine the degree of additional visibility achieved by the camera due to its rotation on the x axis?
  • How can I adjust the properties of left, right, top, bottom, and/or the y position of the camera to restrict its view to only the plane, excluding other elements? Vertically, I want the plane to fill the viewport entirely, even if parts extend beyond horizontally.

PS: It should be noted that this concerns an orthographic camera. The camera's parameters for left, right, top, and bottom are hardcoded considering the plane's height and screen aspect ratio. Both the camera and plane are positioned at (0,0,0).

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

Update

I've developed a code example inspired by @Trentium but with significant modifications including the removal of OrbitControls and utilizing angle calculations from @Marquizzo. Refer to https://plnkr.co/edit/UxO37ShEAiJNAK8H?preview

Answer №1

Exploring the Impact of Rotation on an Orthographic Camera in Three.js

When considering how rotation affects an orthographic camera in Three.js, it's important to note that the basic principles remain consistent. The rotation of a square, for example, follows familiar patterns within the framework of Three.js. As the square rotates from right angles (90°) to 45° angles, the dissecting plane transitions from being the width of the square to the diagonal, which is √2 times larger than the width. This change in perspective may create the illusion of covering a larger area.

Possible Solution Approach

To address scaling concerns related to the camera's top and bottom attributes, implementing a simple sine function could be effective. By utilizing this function, the camera's height can vary based on its rotation angle. For instance, at 90° rotation, the camera reaches maximum height, but as it approaches 0°, it becomes increasingly thin.

const maxSide = planeZ / 2;

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

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

Answer №2

Here is a modified version of @Marquizzo's response (please acknowledge him if this solves the issue), with a few tweaks:

  • The code example has been adapted from the three.js orthographic example, with slight adjustments to ensure the 300x300 plane intersects the XYZ origin.
  • The main changes required to incorporate @Marquizzo's solution are located towards the end of the Snippet in the animate() function.
  • To determine the camera angle concerning the primary 300x300 XZ plane centered on the XYZ origin, it entails calculating the distance between the camera and the XYZ origin, followed by determining the angle between the camera's Y coordinate and the XZ plane. (Mathematically, this involves taking the arcsine of the camera's Y position divided by the radius, which in this case is the distance to the origin.)
  • It should be noted that as sin is used to calculate the final frustums, there is no need to perform the arcsin and then reapply sin; rather, simply use camera.position.y / r directly in the frustum calculations. For transparency purposes, I retained the trigonometric functions...
  • Furthermore, the constraints of the XZ plane (e.g., 150) have been hardcoded for better comprehension, and a minor adjustment of 10% (i.e., * 1.10) has been applied to each frustum to add a border to the 300x300 plane, enhancing visibility of the frustum modifications while rotating the scene.

For optimal viewing experience, switch to "Full page" mode after running 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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

AngularFire Google OAuth failing to retrieve the user's email address

I am trying to retrieve the email address from Google OAuth using AngularFire, but the popup is not requesting permission for the email. This code snippet is from the Firebase Google authentication documentation var ref = new Firebase("https://<your-f ...

What is the best way to retrieve a value from an asynchronous function in Node.js?

var fs = require('fs'); var ytdl = require('ytdl-core'); var favicon = require('serve-favicon'); var express = require('express'); var app = express(); app.use(favicon(__dirname + '/public/favicon.png')); ...

How can I activate a function or pass a selected value to a different scope variable using AngularUI Bootstrap Datepicker?

Check out this AngularUI Datepicker demo on Plunker: http://plnkr.co/edit/DWqgfTvM5QaO5Hs5dHco?p=preview I'm curious about how to store the selected value in a variable or trigger another function when a date is chosen in the field. I couldn't ...

Leverage angular-translate to establish placeholder text upon blurring

Just starting out with Angular and facing the challenge of implementing localization in my project. I have a lot of input fields that need their placeholders translated. In my HTML, I'm trying to achieve this: <input type="email" placeholder="{{ & ...

Selecting HTML5 data attributes using jQuery

There is a button on my page with multiple data attributes, and I am trying to hide it by selecting it based on its class and two specific data attributes. <a href='#' class='wishlist-icon' data-wish-name='product' data-wi ...

Need assistance with jQuery AJAX?

Hey there, I'm a new member and I've gone through the different Ajax Help topics but still can't figure out why my code isn't working. Here's what I have: $(document).ready(function(){ $.ajax({ type: "GET", ur ...

Unable to receive URL parameters using JavaScript on a mobile device

I have been developing a program that displays all users' buttons on a screen, except for the current user's buttons. I came across this code snippet to extract URL parameters: function getParameterByName(name, url) { if (!url) url = window.lo ...

Infinite loop using jQuery function endlessly

I'm trying to create a jQuery Animation that loops indefinitely, but my current code doesn't seem to be working. Here's what I have: $(document).ready(function() { var i = 0; document.write(i); function runAnimatio ...

Displaying PDF content in a new browser tab by utilizing JavaScript

Currently, I am utilizing the struts2 framework alongside dojo for the UI. My goal is to display a PDF in a new browser window. The PDF inputstream is obtained from the server through a standard AJAX call using the GET method (not utilizing a Dojo AJAX cal ...

Understanding the fundamental concepts of callbacks with Backbone.js

Currently, I am working with Backbone to send a login form to my server. Once the form is submitted and the database query is successful, a boolean value is flipped to enable GET responses from the server. However, the issue arises when it attempts to fetc ...

Multiple Button Triggered jQuery Ajax Function

I'm currently working on a project where I retrieve data from MySQL to create 4 buttons. I am using jQuery/ajax to trigger an event when any of the buttons are clicked. However, only the first button seems to be functioning properly, while the other t ...

Using StencilJs/Jsx: Displaying nested components with rendered HTMLElements

Welcome to my custom component: @Component({ tag: "my-alert-list", styleUrl: "alert-list.scss", shadow: true, }) export class AlertList { @State() alertList: object[] = []; @Method() async appendAlert( type: string, message: string, ...

The UseEffect function ceases to function properly upon refreshing the website

I'm currently using ReactJS for a project. I have a form that is intended to serve as the configuration for another form. The structure of this specific form is as follows: const [startingDate, setStartingDate] = useState(); const [endingDate, set ...

Combining the power of Angular with a Vanilla JS web application

Seeking your expertise. Situation We are transitioning from a legacy Vanilla JS webapp to Angular. Our plan is to gradually replace isolated components while adding new functionality as separate Angular apps, all within a timeframe of 6-12 months. Challe ...

The callback for closing the $uibModal with a value will consistently result in undefined

Using ng-click="$widget.addItem()" in my template triggers a $uibModal. Everything is functioning properly - opening, closing, etc. The issue arises when trying to get the callback value from $modal.close($value). The function itself works correctly, the ...

Transitioning JavaScript plugins to Vue framework

Recently, I've been transitioning my PHP app to Vue 3 in order to enhance the interactivity of the site. In the past, we utilized JS plugins that were triggered by selectors to carry out various tasks like generating forms and loading videos. However ...

Angular is reporting that the check-in component is nonexistent

I encountered an error in my Angular 8 application while working on a component. The error seems to be related to nested components within the main component. It appears that if the component is empty, the error will be shown, but if it's not null, th ...

How can I close a dialog within an iframe in jQuery?

Suppose I initiate a dialog in this way: $('<iframe id="externalSite" class="externalSite" src="http://www.example.com" />').dialog({ autoOpen: true, width: 800, height: 500, modal: true, resizable: ...

Postman seems to be functioning correctly while AXIOS is encountering issues

I've encountered a strange issue with my MERN app. When I use Postman to send a PUT request to my API, it successfully updates both the API and MongoDB. However, when performing the same action on the front-end, the API does not update even though the ...

Acknowledging client after client to server POST request has been successfully processed in Node.JS

I've been working on responding to client-side requests with Node.JS. While searching, I came across a helpful article on calling functions on the server from client-side JavaScript in Node JS. However, I'm having trouble implementing it in my pr ...