How to calculate fov for the Perspective camera in three js? - three.js

I want to set CubeGeometry touch to the canvas and I used this fovFormula but it didn't work out. This CubeGeometry is going out of canvas.
var height = 500;
var distance = 1000;
var fov = 2 * Math.atan((height) / (2 * distance)) * (180 / Math.PI);
itsLeftCamera = new THREE.PerspectiveCamera(fov , 400 / 500, 1.0, 1000);
If I am calculating wrong so, please guide me how to overcome this problem? and I want to set this in generalize way so at any position of Perspective camera, this geometry would perfectly touch to my canvas and this geometry should be in center of the canvas.

IMO you should calculate for the diagonal instead of the height in the fov calculator because when doing for height you focus on height thereby cutting off width portion greater than height.... When you do for diagonal your camera focus on the entire rectangle...so code imo should be
var height = 500; //Height of the viewport
var width = 400; //Width of the viewPort
var distance = 1000; //Distance of the viewer from the viewport
var diag = Math.sqrt((height*height)+(width*width))
var fov = 2 * Math.atan((diag) / (2 * distance)) * (180 / Math.PI);
itsLeftCamera = new THREE.PerspectiveCamera(fov , width / height, 0.1, distance);

Related

How do I autofit the camera position in ThreeJS based on the objects? [duplicate]

Using three.js I have the following.
A scene containing several Object3D instances
Several predefined camera Vector3 positions
A dynamic width/height of the canvas if the screen resizes
A user can select an object (from above)
A user can select a camera position (from above)
Given an object being viewed and the camera position they have chosen how do I compute the final camera position to "best fit" the object on screen?
If the camera positions are used "as is" on some screens the objects bleed over the edge of my viewport whilst others they appear smaller. I believe it is possible to fit the object to the camera frustum but haven't been able to find anything suitable.
I am assuming you are using a perspective camera.
You can set the camera's position, field-of-view, or both.
The following calculation is exact for an object that is a cube, so think in terms of the object's bounding box, aligned to face the camera.
If the camera is centered and viewing the cube head-on, define
dist = distance from the camera to the _closest face_ of the cube
and
height = height of the cube.
If you set the camera field-of-view as follows
fov = 2 * Math.atan( height / ( 2 * dist ) ) * ( 180 / Math.PI ); // in degrees
then the cube height will match the visible height.
At this point, you can back the camera up a bit, or increase the field-of-view a bit.
If the field-of-view is fixed, then use the above equation to solve for the distance.
EDIT: If you want the cube width to match the visible width, let aspect be the aspect ratio of the canvas ( canvas width divided by canvas height ), and set the camera field-of-view like so
fov = 2 * Math.atan( ( width / aspect ) / ( 2 * dist ) ) * ( 180 / Math.PI ); // in degrees
three.js r.69
Based on WestLangleys answer here is how you calculate the distance with a fixed camera field-of-view:
dist = height / 2 / Math.tan(Math.PI * fov / 360);
To calculate how far away to place your camera to fit an object to the screen, you can use this formula (in Javascript):
// Convert camera fov degrees to radians
var fov = camera.fov * ( Math.PI / 180 );
// Calculate the camera distance
var distance = Math.abs( objectSize / Math.sin( fov / 2 ) );
Where objectSize is the height or width of the object. For cube/sphere objects you can use either the height or width. For a non-cube/non-sphere object, where length or width is greater, use var objectSize = Math.max( width, height ) to get the larger value.
Note that if your object position isn't at 0, 0, 0, you need to adjust your camera position to include the offset.
Here's a CodePen showing this in action. The relevant lines:
var fov = cameraFov * ( Math.PI / 180 );
var objectSize = 0.6 + ( 0.5 * Math.sin( Date.now() * 0.001 ) );
var cameraPosition = new THREE.Vector3(
0,
sphereMesh.position.y + Math.abs( objectSize / Math.sin( fov / 2 ) ),
0
);
You can see that if you grab the window handle and resize it, the sphere still takes up 100% of the screen height. Additionally, the object is scaling up and down in a sine wave fashion (0.6 + ( 0.5 * Math.sin( Date.now() * 0.001 ) )), to show the camera position takes into account scale of the object.
Assuming that object fits into screen if it's bounding sphere fits, we reduce the task to fitting sphere into camera view.
In given example we keep PerspectiveCamera.fov constant while changing camera rotation to achieve best point of view for the object. Zoom effect is achieved by moving camera along .lookAt direction vector.
On the picture you can see problem definition:
given bounding sphere and camera.fov, find L, so that bounding sphere touches camera's frustum planes.
Here's how you calculate desired distance from sphere to camera:
Complete solution: https://jsfiddle.net/mmalex/h7wzvbkt/
var renderer;
var camera;
var scene;
var orbit;
var object1;
function zoomExtents() {
let vFoV = camera.getEffectiveFOV();
let hFoV = camera.fov * camera.aspect;
let FoV = Math.min(vFoV, hFoV);
let FoV2 = FoV / 2;
let dir = new THREE.Vector3();
camera.getWorldDirection(dir);
let bb = object1.geometry.boundingBox;
let bs = object1.geometry.boundingSphere;
let bsWorld = bs.center.clone();
object1.localToWorld(bsWorld);
let th = FoV2 * Math.PI / 180.0;
let sina = Math.sin(th);
let R = bs.radius;
let FL = R / sina;
let cameraDir = new THREE.Vector3();
camera.getWorldDirection(cameraDir);
let cameraOffs = cameraDir.clone();
cameraOffs.multiplyScalar(-FL);
let newCameraPos = bsWorld.clone().add(cameraOffs);
camera.position.copy(newCameraPos);
camera.lookAt(bsWorld);
orbit.target.copy(bsWorld);
orbit.update();
}
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(54, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 15;
camera.position.y = 15;
camera.position.z = 15;
camera.lookAt(0, 0, 0);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color(0xfefefe));
document.body.appendChild(renderer.domElement);
orbit = new THREE.OrbitControls(camera, renderer.domElement);
// create light
{
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(0, 100, 50);
spotLight.castShadow = true;
spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;
spotLight.shadow.camera.near = 500;
spotLight.shadow.camera.far = 4000;
spotLight.shadow.camera.fov = 30;
scene.add(spotLight);
}
var root = new THREE.Object3D();
scene.add(root);
function CustomSinCurve(scale) {
THREE.Curve.call(this);
this.scale = (scale === undefined) ? 1 : scale;
}
CustomSinCurve.prototype = Object.create(THREE.Curve.prototype);
CustomSinCurve.prototype.constructor = CustomSinCurve;
CustomSinCurve.prototype.getPoint = function(t) {
var tx = t * 3 - 1.5;
var ty = Math.sin(2 * Math.PI * t);
var tz = 0;
return new THREE.Vector3(tx, ty, tz).multiplyScalar(this.scale);
};
var path = new CustomSinCurve(10);
var geometry = new THREE.TubeGeometry(path, 20, 2, 8, false);
var material = new THREE.MeshPhongMaterial({
color: 0x20f910,
transparent: true,
opacity: 0.75
});
object1 = new THREE.Mesh(geometry, material);
object1.geometry.computeBoundingBox();
object1.position.x = 22.3;
object1.position.y = 0.2;
object1.position.z = -1.1;
object1.rotation.x = Math.PI / 3;
object1.rotation.z = Math.PI / 4;
root.add(object1);
object1.geometry.computeBoundingSphere();
var geometry = new THREE.SphereGeometry(object1.geometry.boundingSphere.radius, 32, 32);
var material = new THREE.MeshBasicMaterial({
color: 0xffff00
});
material.transparent = true;
material.opacity = 0.35;
var sphere = new THREE.Mesh(geometry, material);
object1.add(sphere);
var size = 10;
var divisions = 10;
var gridHelper = new THREE.GridHelper(size, divisions);
scene.add(gridHelper);
var animate = function() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
animate();
try this for OrbitControls
let padding = 48;
let w = Math.max(objectLength, objectWidth) + padding;
let h = objectHeight + padding;
let fovX = camera.fov * (aspectX / aspectY);
let fovY = camera.fov;
let distanceX = (w / 2) / Math.tan(Math.PI * fovX / 360) + (w / 2);
let distanceY = (h / 2) / Math.tan(Math.PI * fovY / 360) + (w / 2);
let distance = Math.max(distanceX, distanceY);
From user151496's suggestion about using the aspect ratio, this seems to work, although I've only tested with a few different parameter sets.
var maxDim = Math.max(w, h);
var aspectRatio = w / h;
var distance = maxDim/ 2 / aspectRatio / Math.tan(Math.PI * fov / 360);
I had the same question but I expected the object(s) (represented by a Box3 as a whole) could rotate on my phone if the whole was wider than my screen so I could view it by zooming in as near as possible.
const objectSizes = bboxMap.getSize();
console.log('centerPoint', centerPoint, bboxMap, objectSizes, tileMap);
//setupIsometricOrthographicCamera(bboxMap);
//https://gamedev.stackexchange.com/questions/43588/how-to-rotate-camera-centered-around-the-cameras-position
//https://threejs.org/docs/#api/en/cameras/PerspectiveCamera
//https://stackoverflow.com/questions/14614252/how-to-fit-camera-to-object
// Top
// +--------+
// Left | Camera | Right
// +--------+
// Bottom
// canvas.height/2 / disance = tan(fov); canvas.width/2 / disance = tan(fovLR);
// => canvas.width / canvas.height = tan(fovLR)/tan(fov);
// => tan(fovLR) = tan(fov) * aspectRatio;
//If rotating the camera around z-axis in local space by 90 degrees.
// Left
// +---+
// Bottom | | Top
// | |
// +---+
// Right
// => tan(fovLR) = tan(fov) / aspectRatio;
const padding = 0, fov = 50;
let aspectRatio = canvas.width / canvas.height;
let tanFOV = Math.tan(Math.PI * fov / 360);
let viewWidth = padding + objectSizes.x, viewHeight = padding + objectSizes.y;
//The distances are proportional to the view's with or height
let distanceH = viewWidth / 2 / (tanFOV * aspectRatio);
let distanceV = viewHeight / 2 / tanFOV;
const camera = this.camera = new THREE.PerspectiveCamera(fov, aspectRatio, 0.1, 10000); //VIEW_ANGLE, ASPECT, NEAR, FAR
if (aspectRatio > 1 != viewWidth > viewHeight) {
console.log('screen is more narrow than the objects to be viewed');
// viewWidth / canvas.width => viewHeight / canvas.width
// viewHeight / canvas.height => viewWidth / canvas.height;
distanceH *= viewHeight / viewWidth;
distanceV *= viewWidth / viewHeight;
camera.rotateZ(Math.PI / 2);
}
camera.position.z = Math.max(distanceH, distanceV) + bboxMap.max.z;
//camera.lookAt(tileMap.position);
I had tested two different aspect of Box3 on tow different orientations (landscape and portrait) using my phone, it worked well.
References
Box3.getSize ( target : Vector3 ) : Vector3
target — the result will be copied into this Vector3.
Returns the width, height and depth of this box.
Object3D.rotateZ ( rad : Float ) : this (PerspectiveCamera)
rad - the angle to rotate in radians.
Rotates the object around z axis in local space.
Other answers

Three.js cube face rotation vector in relation to camera

I have a rotating sphere on which I have a div attached the example can be viewed here: https://jsfiddle.net/ao5wdm04/
I calculate the x and y values and place the div using a translate3d transform and that works quite well.
My question is how to can get the values for the rotateX, rotateY and rotateZ or rotate3d transforms so the div "tangents" the sphere surface. I know the cube mesh faces the sphere center so I assume the rotation vector of the outward facing normal vector in relation to the camera would contain the values I need. But I'm not quite sure how to obtain these.
Update
By using Euler angles I'm almost achieving the desired effect, shown here: https://jsfiddle.net/ao5wdm04/1/ but the rotation is not large enough.
Disclaimer: I know nothing about three.js. I've just done a bit of OpenGL.
Your euler angles are coming from a model-view-projected origin (lines 74-80). I can't see the logic behind this.
If your div is on the sphere surface, then it should be oriented by the normal of the sphere at the location of the div. Fortunately, you already have these angles. They are named rotation.
If you replace the euler angles in lines 82-84 with the rotation angles used to position the div, then in my browser the div appears edge on when it is at the edge of the circle, and face on when it is at the centre. It kind of looks like it is moving in a circle, edge on to the screen. Is this the effect you want?
My modification to the linked code:
82 var rotX = (rotation.x * (180/ Math.PI));
83 var rotY = (rotation.y * (180/ Math.PI));
84 var rotZ = 0;
EDIT
Ah, ok. The rotation variable is just that of the camera. It governs the tangent at the equator. You also need to modify the orientation to account for latitude.
Make rotY equal to negative your latitude. Then make sure that this rotation happens before the equatorial rotation. Rotations are not commutative.
In summary, changes from the code at https://jsfiddle.net/ao5wdm04/1/ are as follows:
27 var lat = 45 * Math.PI / 180;
...
82 var rotX = (rotation.x * (180/ Math.PI));
83 var rotY = - 45;
...
88 document.getElementById('face').style.webkitTransform = 'translate3d(' + x + 'px,' + y + 'px,0px) rotateY('+rotX+'deg) rotateX('+rotY+'deg)';
I don't know how the latitude should propagate between the init and render functions. As I said, I'm not familiar with the language.
For details about transformation and rotation in openGL or any other graphics please go through here.
Basic -
There is basically 2 kind of transformations in 3D world-
Translation
Rotation
A small example on this things are given here.
If u go through all of them, I think u have a clear concept on the 3D transformation system.
If u can understand those, u can easily simulate that :) because u need to do this 2 things for each move at the same time.
Try this code-
var camera, scene, renderer, raycaster, geometry, material, mesh, box;
var rotation = {
x: 0,
y: 0
};
var distance = 500;
init();
animate();
function init() {
raycaster = new THREE.Raycaster(); ;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = distance;
camera.position.y = 100;
scene.add(camera);
geometry = new THREE.SphereGeometry(100, 50, 50, 50);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
var transform = new THREE.Matrix4().getInverse(scene.matrix);
var lat = 0 * Math.PI / 180;
var lon = 90 * Math.PI / 180;
var r = 100;
var p = new THREE.Vector3(-r * Math.cos(lat) * Math.cos(lon),
r * Math.sin(lat),
r * Math.cos(lat) * Math.sin(lon)
);
p.applyMatrix4(transform);
var geometry = new THREE.CubeGeometry(10, 10, 10);
box = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
color: 0xff0000,
}));
box.position.set(p.x, p.y, p.z);
box.lookAt(mesh.position);
//scene.add(box);
box.updateMatrix();
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
rotation.x += 0.01;
camera.position.x = distance * Math.sin(rotation.x) * Math.cos(rotation.y);
camera.position.y = distance * Math.sin(rotation.y);
camera.position.z = distance * Math.cos(rotation.x) * Math.cos(rotation.y);
camera.lookAt(mesh.position);
var w = window.innerWidth;
var h = window.innerHeight;
var mat = new THREE.Matrix4();
var v = new THREE.Vector3();
mat.copy(scene.matrix);
mat.multiply(box.matrix);
v.set(0, 0, 0);
v.applyMatrix4(mat);
v.project(camera);
var euler = new THREE.Euler().setFromVector3(v);
var rotX = (rotation.x * (180/ Math.PI));
var rotY = (rotation.y * (180/ Math.PI));
var rotZ = 0;
var x = (w * (v.x + 1) / 2) - 12.5; //compensate the box size
var y = (h - h * (v.y + 1) / 2) - 12.5;
document.getElementById('face').style.webkitTransform = 'translate3d(' + x + 'px,' + y + 'px,0px) rotateX('+rotY+'deg) rotateY('+rotX+'deg) rotateZ('+rotZ+'deg)';
renderer.render(scene, camera);
}
#face {
position: absolute;
width: 25px;
height: 25px;
border-radius: 50%;
background-color: red;
}
<script src="https://rawgit.com/mrdoob/three.js/master/build/three.min.js"></script>
<div id="face"></div>

How to get UV and texture coordinate from a face or 3d point?

I have a sphere geometry with a basic material which mapped by a texture:
var geometry = new THREE.SphereGeometry(500, 60, 40);
var material = new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture('textures/equirectangular.jpg'),
overdraw: 0.5
});
var mesh = new THREE.Mesh(geometry, material);
And on mouse click:
mouse.x = (event.clientX / renderer.domElement.width) * 2 - 1;
mouse.y = -(event.clientY / renderer.domElement.height) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObject(mesh);
Which gives me an array of intersected objects.
Now how can i find the UV coordinate out of the intersected point and then get the texture pixel(pixels range) of the sphere texture?
With more googling and seeing this question, i've got this code which gives me the right texture coordinate:
var p = intersects[0].point;
var x = (p.x - sphere.position.x) / (-1 * radius);
var y = (p.y - sphere.position.y) / radius;
var z = (p.z - sphere.position.z) / radius;
var u = 1 - (Math.atan2(z, x) / (2 * Math.PI) + 0.5);
var v = 1 - ((Math.asin(y) / Math.PI) + 0.5);
console.log("u,v:", u, v);
var x = u * textureWidth;
var y = v * textureHeight;
console.log(x, y);
Note: camera is inside the sphere at (0, 0, 0) and i apply a scale matrix to the sphere geometry like this:
geometry.applyMatrix(new THREE.Matrix4().makeScale(-1, 1, 1));
But i'm looking for more complete answer which applies for geometry without negative scale too or other basic geometries like plane or cube.
Also this code returns inaccurate texture X coordinates for a loaded sphere from blender. generally getting the right x coordinates is more problematic for me. i guess the geometry/mesh rotation and scale should take into account in order to get right x,y texture coordinates, but i'm not good at math!

View Width when camera.setlens is used

I know the formula to calculate view width and height.
var vFOV = camera.fov * Math.PI / 180; // convert vertical fov to radians
var height = 2 * Math.tan( vFOV / 2 ) * dist; // visible height
var aspect = window.width / window.height;
var width = height * aspect;
But when camera.setlens(60) is involved calculations are going wrong. Will the formula changes when we use Camera.setlens .
Look at the docs or the source
Camera.setLens does not take a field of view. It takes focal length and a frame size

THREE.JS: Get object size with respect to camera and object position on screen

I am newbie to 3D programming, I did started to explore the 3D world from WebGL with Three.JS.
I want to predetermine object size while I change the camera.position.z and object's "Z" position.
For example:
i have a cube mesh at size of 100x100x100.
cube = new THREE.Mesh(
new THREE.CubeGeometry(100, 100, 100, 1,1,1, materials),
new THREE.MeshFaceMaterial()
);
and cam with aspect ratio of 1.8311874
camera = new THREE.PerspectiveCamera( 45, aspect_ratio, 1, 30000 );
I want to know size (2D width & height) of that cube object on screen when,
camera.position.z = 750;
cube.position.z = 500;
Is there is any way to find it/predetermine it?
You can compute the visible height for a given distance from the camera using the formulas explained in Three.js - Width of view.
var vFOV = camera.fov * Math.PI / 180; // convert vertical fov to radians
var height = 2 * Math.tan( vFOV / 2 ) * dist; // visible height
In your case the camera FOV is 45 degrees, so
vFOV = PI/4.
(Note: in three.js the camera field-of-view FOV is the vertical one, not the horizontal one.)
The distance from the camera to the front face (important!) of the cube is 750 - 500 - 50 = 200. Therefore, the visible height in your case is
height = 2 * tan( PI/8 ) * 200 = 165.69.
Since the front face of the cube is 100 x 100, the fraction of the visible height represented by the cube is
fraction = 100 / 165.69 = 0.60.
So if you know the canvas height in pixels, then the height of the cube in pixels is 0.60 times that value.
The link I provided shows how to compute the visible width, so you can do that calculation in a similar fashion if you need it.

Resources