Using Three.js to display a PerspectiveCamera with a visible bounding Sphere

My challenge is to create a Scene that loads a single .obj file where the object is fully visible in the PerspectiveCamera upon scene initialization.

  • The field of view (FOV) is set at 60
  • The objects vary in size
  • TrackballControls are utilized for camera control

Despite attempting this specified solution, I encountered issues.

The provided code snippet is as follows:

FOV = 60

scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera( FOV, 500 / 350, 0.1, 10000 )
scene.add( camera )

objLoader = new THREE.OBJLoader();
objLoader.load('/airboat.obj', function (object) {
  scene.add( object )

  var boundingBox = new THREE.Box3();
  boundingBox.setFromObject( object );
  var sphere = boundingBox.getBoundingSphere()

  // not functioning correctly
  var center = boundingBox.getCenter();
  var size = boundingBox.getSize();
  var maxDim = Math.max( size.x, size.y, size.z );
  var cameraZ = maxDim / 2 / Math.tan(Math.PI * FOV / 360);
  camera.lookAt(center)
  camera.position.set( center.x, center.y, cameraZ );
  camera.updateProjectionMatrix();
})

UPDATE

For further reference, I have prepared this fiddle (please scroll down the JS code) where @Brakebein's solution typically works well, except for instances where the bounding box edges are not visible

Answer №1

Shifting the camera target to the center of the sphere and adjusting the camera position in the opposite direction of the current line of sight by a calculated distance:

The line of sight is represented by the vector from camera.position to controls.target.

var current_los = new THREE.Vector3().subVectors(controls.target, camera.position);

To calculate the new target and position:

var new_pos     = new THREE.Vector3().addVectors(center, current_los.setLength(-cameraZ));
var new_target  = center; 

Ensure that the camera.position is set before calling look At, as the function relies on the position and controls need to be updated:

var boundingBox = new THREE.Box3();
boundingBox.setFromObject( group );

var center = boundingBox.getCenter();
var size   = boundingBox.getSize();

var distance = Math.max( size.x, size.y, size.z );

var cameraZ = distance / 2 / Math.sin(Math.PI * FOV / 360);

var current_los = new THREE.Vector3().subVectors(controls.target, camera.position);
var new_pos     = new THREE.Vector3().addVectors(center, current_los.setLength(-cameraZ));
var new_target  = center; 

// copy values
camera.position.copy(new_pos);
camera.lookAt(center);

controls.target.copy(center);
controls.update();

For a larger image section, using the bounding sphere's diameter instead of the box's maximum length is recommended:

var distance = boundingBox.getBoundingSphere().radius * 2;

However, keep in mind that this may not achieve the desired result. Refer to the images below:

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

For the desired outcome, consider using the sine function instead of tangent:

sin(FOV/2) == (maxDim/2) / cameraZ;

var cameraZ = maxDim / 2 / Math.sin(Math.PI * FOV / 360);

Answer №2

To ensure proper camera positioning, consider calling camera.lookAt(center) following the camera's initial position setup:

camera.position.set( center.x, center.y, cameraZ );
camera.lookAt(center);

Alternatively, you can explore utilizing the BoundingSphere method in your project to adjust objects within the view. This method is compatible with both OrbitControls and TrackballControls:

var sphere = boundingBox.getBoundingSphere();

// Calculate new distance between camera and the object's/sphere's center
var h = sphere.radius / Math.tan( camera.fov / 2 * THREE.Math.DEG2RAD );

// Determine the camera's direction
var dir = new THREE.Vector3().subVectors(camera.position, controls.target);

// Update the camera's position
var newPos = new THREE.Vector3().addVectors(sphere.center, dir.setLength(h));

// Update camera and controls positions
camera.position.copy(newPos);
controls.target.copy(sphere.center);

camera.lookAt(controls.target); 

Hint: Ensure camera and controls are not positioned identically to enable proper direction calculation.

EDIT: After reviewing @Rabbid76's diagrams, it seems my initial solution aligns closely with the first one. To address potential objects or bounding box parts outside the viewing frustum, consider adjusting tan to sin:

var h = sphere.radius / Math.sin( camera.fov / 2 * THREE.Math.DEG2RAD );

Answer №3

By loading the .obj file and then adjusting the camera to focus on the center of the object, you are instructing the camera to move to the precise center of the loaded object, instead of just loading it into view.

var center = boundingBox.getCenter(); //This retrieves the exact center of the box

What you should do is calculate the width and depth of the object you are loading and adjust the camera position accordingly.

For example:

camera.position.set( center.x - size.x, center.y - size.y, cameraZ - size.z );

The code above (not tested) will move the camera position back by the full size of the object, bringing it into view. You can also subtract an additional 'buffer' to have a better view of the object instead of being at the edge of its bounds.

I hope this explanation helps!

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

I must only assign the result to "value" if the condition of the map is true

I am looking to set the variable "value" to only contain the value that meets the condition in the map function const arr= [1,2,3,4,5]; value = arr.map(item => item > 4 && item) console.log(value); The resulting value is currently [false, false, fa ...

Creating a Class in REACT

Hello fellow coding enthusiasts, I am facing a minor issue. I am relatively new to REACT and Typescript, which is why I need some assistance with the following code implementation. I require the code to be transformed into a class for reusability purposes ...

Standardizing URLs with ExpressJS router

Seeking normalized/canonical URLs for a single page application running on an ExpressJS server. While the SPA is supported by a server-side router, templates can vary slightly for different app URLs. One particular difference is the presence of the <li ...

Is it possible to customize text or images using the window.location method?

Imagine I have a scenario with 3 unique servers as follows: https://server-1.com https://server-2.com https://server-3.com In my Vue application, I want to dynamically change an image based on the server being used. Can I achieve this by utilizing someth ...

Trouble parsing JSON in Classic ASP

After receiving a JSON Response from a remote server, everything looks good. I discovered an helpful script for parsing the JSON data and extracting the necessary values. When attempting to pass the variable into JSON.parse(), I encountered an error which ...

Steps for adding custom text/symbols from a button on the textAngular toolbar

My goal is to include a button on the toolbar that allows users to insert © into the textangular editor (). However, I am struggling to grasp how to add functionality to my registered button. The examples provided by textangular for custom functionali ...

What causes the difference in behavior of nodejs function arguments when explicitly called?

As I refactor my nodejs application to improve code readability, I encountered an issue when calling a function directly. The following code works perfectly: router.route('/').get(({ query }, res, next) => { ItemsLogic.getItems(query) .the ...

example of using relative jquery countdown.js

I've been attempting to grasp JavaScript and incorporate the countdown found at this link (specifically, the example with a 300-second countdown), but after spending a few hours on it, I haven't been able to get it functioning properly. I have c ...

An effective way to prevent right-clicking on iframes across all websites

I am facing an issue with disabling right click for the iframe. I've successfully disabled it for the default URL of the IFrame, but when displaying any other webpage, the right click remains usable. Below are the sample codes I have used: document.o ...

Anticipated a JavaScript module script, but the server returned a MIME type of text/html as well as text/css. No frameworks used, just pure JavaScript

I have been attempting to implement the color-calendar plugin by following their tutorial closely. I have replicated the code from both the DEMO and documentation, shown below: // js/calendar.js import Calendar from '../node_modules/color-calendar&ap ...

Is there a way to assign a dynamic value to an input just once, and then retain the updated value without it changing again

During a for loop, I have an input element (type number) that needs its value modified by decrease and increase buttons: <div class="s-featured-list__item s-featured-list__item--expandable" v-for="(item, itemIndex) in category.items" ...

Personalize the appearance of dynamically generated DIV elements

This script generates a random number of squares (ranging from 20 to 40) and adds text to each square. The script then calculates the width of each square so that they all fit in a single row. Here is the code snippet: var quantity = Math.floor(Math.ran ...

Rails - implementing ajax in partials causing an error: 'cannot find method render'

I am encountering an issue with my form partial located within a div that has the id "chapcomments". The form includes a submit button: <%= f.submit "Post", remote: true %> Within the correct view folder, I have a file named create.js.erb which con ...

Constantly showing false values in AngularJS Firebase array objects

Encountering an issue with retrieving data from Firebase. When viewing console.log($scope.statusbaca), it shows 2 queries, true and false. However, in the app it only displays false. Apologies for any language barriers as English is not my first language. ...

Tips for implementing a nested ng-repeat with nested JSON array data triggered by a button click

Assuming I have assigned the following JSON data to $scope.people: [ { "personId": 1, "name": "Thomas", "age": 39, "friends": [ { "friendId": 1, "nickName": "Lefty" ...

"Enhancing User Experience with JavaScript Double Click Feature in Three.js

Currently, I have implemented a double click function that allows the user to double click on a car model, displaying which objects have been intersected such as wipers, grille, and tires. These intersections are listed along with the number of items the d ...

Guide to handling multiple forms within a single template using Express

If I have an index.html file containing two tables - "Customers" and "Items", along with two forms labeled "Add customer" and "Add item", how can I ensure that submitting these forms results in a new entry being added to the respective table as well as t ...

Receiving Array Data from JSON and Listing Results

Once I retrieve the first row result from a JSON array, I want to display all the results using the jQuery each method. Here is the code snippet: $(document).ready(function () { $("#btnsearch").click(function() { valobj = $('#search_box' ...

Transforming various date formats into the en-US format of mm/dd/yyyy hh:mm:ss can be accomplished through JavaScript

When encountering a date format in en-GB or European style (mm.dd.yyyy), it should be converted to the en-US format (mm/dd/yyyy). If the date is not already in en-US format, then it needs to be converted accordingly. ...

Instead of receiving my custom JSON error message, Express is showing the server's default HTML error page when returning errors

I have set up a REST api on an Express server, with a React app for the front-end. The design includes sending JSON to the front-end in case of errors, which can be used to display error messages such as modals on the client side. Below is an example from ...