I have a box (collada file) that loads within a three.js canvas. I can interact with it as expected. However, the box size varies as users can change the size.
When I load it into a 500px by 500px canvas, if the box is large, users have to zoom in before seeing it, and if it is small, it is tiny and users need to zoom in. The size changes depending on the variables that are passed.
How would I have the object (collada file) fit in the canvas on load, and then let users zoom? Here is the code that loads on click to show the 3D object in a three.js canvas:
$scope.generate3D = function () {
// 3D OBJECT - Variables
var texture0 = baseBlobURL + 'Texture_0.png';
var boxDAE = baseBlobURL + 'Box.dae';
var scene;
var camera;
var renderer;
var box;
var controls;
var newtexture;
// Update texture
newtexture = THREE.ImageUtils.loadTexture(texture0);
// Initial call to render scene, from this point, Orbit Controls render the scene per the event listener
THREE.DefaultLoadingManager.onProgress = function (item, loaded, total) {
// console.log( item, loaded, total ); // debug
if (loaded === total) render();
};
//Instantiate a Collada loader
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load(boxDAE, function (collada) {
box = collada.scene;
box.traverse(function (child) {
if (child instanceof THREE.SkinnedMesh) {
var animation = new THREE.Animation(child, child.geometry.animation);
animation.play();
}
});
box.scale.x = box.scale.y = box.scale.z = .2;
box.updateMatrix();
init();
});
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(100, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xdddddd);
//renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setSize(500, 500);
// Load the box file
scene.add(box);
// Lighting
var light = new THREE.AmbientLight();
scene.add(light);
// Camera
camera.position.x = 40;
camera.position.y = 40;
camera.position.z = 40;
camera.lookAt(scene.position);
// Rotation Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.addEventListener('change', render);
controls.rotateSpeed = 5.0;
controls.zoomSpeed = 5;
controls.noZoom = false;
controls.noPan = false;
// Add 3D rendering to HTML5 DOM element
var myEl = angular.element(document.querySelector('#webGL-container'));
myEl.append(renderer.domElement);
}
// Render scene
function render() {
renderer.render(scene, camera);
console.log('loaded');
}
}
// Initial 3D Preview Load
$scope.generate3D();
Update: I have evaluated the solution presented here: How to Fit Camera to Object but am unsure how to define the distance for my collada file as it can be different depending on what dimensions the user enters. The collada file is generated by users sending variables to a third party vendor that returns a collada file that is subsequently loaded into three.js.
Update 2: Thanks to @Blindman67 I am closer to understanding how this interplays. When I manually up the camera.position x,y,z values, the object is in the screen. The challenge I have is how to determine what the correct x,y,z values will be as each box is dynamically changed and I literally have over 280 Million variations. I know that @Blindman67 already gave me the answer logically, but I just need a final push to discover how to get the right position for objects that vary each time so I can set the correct x,y,z.
Fit a 3D object to a view
There are several ways to fit a 3d object to the camera view.
You can discount the last option as it is impractical in most cases. (though I do notice you are doing that in the code given there is enough information in this answer to work out how to scale the object to fit. But I do not recommend you do that)
So you are left with either moving the camera in or out or keeping it stationary and zooming it. It is the same as with a real camera, you zoom or get closer.
Both methods have pros and cons.
Translation
Moving the camera (dolly) is the best for most situations as it keeps the perspective the same (how rapidly lines converge to the vanishing point) and thus does not distort the object in view. In 3D there are 3 problems with this method.
Zoom
Zooming involves changing the focal length of the camera. In the library you use this is done by adjusting the FOV (Field of view) this is the angle between the left and right side of the view given in degrees. Reducing the FOV effectively increases the focal length and zooms in (3D graphics don't have a focal length like a camera). Increasing FOV zooms out. There are problems with this method.
What method to use is ip to you, you can use one or the other or combine both.
How it is done
To the Question at hand. We need to know how big the object will appear in the scene and use this information to change the camera setting to the desired effect (namely fit the object to the display).
Figure 1. The camera, object, and view.
So there are some values that are needed. See fig1 for visual explanation.
You will need to calculate the bounding sphere of the object. Or use another value that approximates the bounding sphere. I will leave that up to you.
The code
var oL,cL; // for the math to make it readable
var FOV = 45 * (Math.PI / 180); // convert to radians
var objectLocation = oL = {x : 0, y : 0, z : 400};
var objectRadius = 50;
var cameraLocation = cL = {x : 0, y : 0, z : 0};
var farPlane = 1000;
var nearPlane = 200;
var displayWidth = 1600;
var displayHeight = 1000;
To work out how large the bounding sphere will appear on the view is simple trig.
// Get the distance from camera to object
var distToObject = Math.sqrt(Math.pow(oL.x - cL.x, 2) + Math.pow(oL.y - cL.y, 2) + Math.pow(oL.z - cL.z, 2));Figure 1
As we are using the right triangle (see fig1) we multiply the result by 2 to give the total angular size
// trig inverse tan of opposite over adjacent.
var objectAngularSize = Math.atan( (objectRadius) / distToObject ) * 2;
Get the fraction of the FOV that the object occupies.
var objectView = objectAngularSize / FOV;
And finally you get the pixel size of the object.
var objectPixelSize = objectView * displayWidth;
That is all you need to know to do what you ask. It would help you understand if you use the above code and math to try and rearrange the calculation so that you get the object to occupy ta desired pixel size by either moving the camera or FOV. Copying code does not teach you much, using the above information by applying it will set it in your mind and will make many other processes you need for 3D easier in future.
That said copying code is the quick solution and is what frameworks and libraries are all about. No need to know how you have better things to learn.
Zoom to fit.
This is the easiest and gets the object's angular size and adjust the FOV to fit. (NOTE I am using radians. the Three.js uses degrees for FOV you will need to convert)
var requieredObjectPixelSize = 900;
var distToObject = Math.sqrt(Math.pow(oL.x - cL.x, 2) + Math.pow(oL.y - cL.y, 2) + Math.pow(oL.z - cL.z, 2));
var objectAngularSize = Math.atan( (objectRadius) / distToObject ) * 2;
// get the amount the FOV must be expanded by
var scaling = displayWidth / requieredObjectPixelSize;
// change the FOV to set the objects size
FOV = objectAngularSize * scaling;
Convert FOV to degrees and use it to create the camera.
Translate to fit
Move the camera to fit the object. This is a little more involved but is the better method.
// Approx size in pixels you want the object to occupy
var requieredObjectPixelSize = 900;
// camera distance to object
var distToObject = Math.sqrt(Math.pow(oL.x - cL.x, 2) + Math.pow(oL.y - cL.y, 2) + Math.pow(oL.z - cL.z, 2));
// get the object's angular size.
var objectAngularSize = Math.atan( (objectRadius) / distToObject ) * 2;
// get the fraction of the FOV the object must occupy to be 900 pixels
var scaling = requieredObjectPixelSize / displayWidth;
// get the angular size the object has to be
var objectAngularSize = FOV * scaling;
// use half the angular size to get the distance the camera must be from the object
distToObject = objectRadius / Math.tan(objectAngularSize / 2);
Now to move the camera. It must be moved along the vector between the object and the camera.
// Get the vector from the object to the camera
var toCam = {
x : cL.x - oL.x,
y : cL.y - oL.y,
z : cL.z - oL.z,
}
Normalise the vector. This means make the length of the vector equal to 1 and is done by dividing each component (x,y,z) by the vector's length.
// First length
var len = Math.sqrt(Math.pow(toCam.x, 2) + Math.pow(toCam.y, 2) + Math.pow(toCam.z, 2));
// Then divide to normalise (you may want to test for divide by zero)
toCam.x /= len;
toCam.y /= len;
toCam.z /= len;
Now you can scale the vector to make it equal to the distance the camera must be from the object.
toCam.x *= distToObject;
toCam.y *= distToObject;
toCam.z *= distToObject;
Then its just a matter of adding the vector to the object's location and putting it in the camera location
cL.x = oL.x + toCam.x;
cL.y = oL.y + toCam.y;
cL.z = oL.z + toCam.z;
cl
now holds the camera location.
One last thing. You need to check if the object is inside the view.
if (distToObject - objectRadius < nearPlane) {
nearPlane = (distToObject - objectRadius) * 0.8; // move the near plane towards the camera
// by 20% of the distance between the front of the object and the camera
}
if (distToObject + objectRadius > farPlane) {
farPlane = distToObject + objectRadius * 1.2; // move the far plane away from the camera
// by 1.2 time the object radius
}
There is still one issue. The object if very small may be so close that the front of the object is behind the camera. If this happens you will need to use the Zoom method and move the camera back. This will be only for very select cases and can be ignored on the most part.
I have not given information on how to integrate this with Three.js but this is intended to be a generic answer that applies to all 3D packages. You will have to consult the three.js documentation on how to change the various camera settings. Its is straightforward and applies to the perspective camera.
Ok a big answer and I need to forget it for a bit as I don't see the typos and mistakes without a break. I will return and fix it up later in the day.
Hope it helps
This code will 'auto-center' the camera around the given object.
For user 'zooming', also take a look at three.js examples/js/controls/TrackballControls
.js which dollies the camera position (and changes lookat and up). If you choose to use that, I remember that you need to initialize the controls before moving the camera position with centerCam
. In that case centerCam
gets the whole object in view to prep for the user interaction, and TrackballControls
listeners take over after that.
<body onload="initPage()">
<canvas id="cadCanvas" width="400" height="300"></canvas>
<button onclick="loadFile()">Load Collada File</button>
</body>
function initPage(){
var domCanvas = document.getElementById('cadCanvas');
scene = new THREE.Scene();
var fovy = 75;
camera = new THREE.PerspectiveCamera( fovy, domCanvas.width/domCanvas.height, 0.1, 1000 );
renderer = new THREE.WebGLRenderer({canvas:domCanvas});
renderer.setSize( domCanvas.width, domCanvas.height );
}
function loadFile(){
new THREE.ColladaLoader().load( url, function(obj3D) {
scene.add(obj3D);
centerCam(obj3D);
renderer.render(scene, camera);
});
}
function centerCam(aroundObject3D){
//calc cam pos from Bounding Box
var BB = new THREE.Box3().setFromObject(aroundObject3D);
var centerpoint = BB.center();
var size = BB.size();
var backup = (size.y / 2) / Math.sin( (camera.fov/2)*(Math.PI/180) );
var camZpos = BB.max.z + backup + camera.near ;
//move cam
camera.position.set(centerpoint.x, centerpoint.y, camZpos);
camera.far = camera.near + 10*size.z;
camera.updateProjectionMatrix();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With