I want to update the Camera Position so that a Plane World Position matches a DIV Screen Position.
I need to calculate camera.position.z
- so that the planes face matches the size of the DIV
- even when resizing the canvas.
this.computeZ = function(meshHandle, cameraHandle, faceHeight, targetHeight){
var face = meshHandle.geometry.vertices[2]
var vFOV = cameraHandle.fov * Math.PI / 180;
var vHeightPartial = 2 * Math.tan( vFOV / 2 );
var p1 = faceHeight * window.innerHeight;
var p2 = face.z * vHeightPartial;
var p3 = targetHeight * vHeightPartial;
var p4 = targetHeight * p2;
var p5 = p1 + p4;
var z = p5/p3;
return z;
}
See computeZ
in action here.
The world face size now matches the DIV
screen pixel size.
Next we need to find a camera.position.x
and camera.position.y
- so that the face directly overlaps the DIV
.
I've studied...
How to Fit Camera to Object
Three.js - Width of view
THREE.JS: Get object size with respect to camera and object position on screen
Converting World coordinates to Screen coordinates in Three.js using Projection
...But have been struggling to build something that works for computeX
and computeY
Take a look at the computeX
and computeY
functions in the fiddle I've provided. These functions are my best attempt - but do not work.
How do I build these functions?
I've come up with a solution with the help of Craig's post. This class builds on his methods to cover resize events.
<!DOCTYPE html>
<html>
<head>
<title>SO code</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/three.js/85/three.min.js"></script>
<style>
html,body{
height:100%;
width:100%;
padding:0px;
margin:0px;
}
#content{
width:100%;
height:100%;
position:relative;
}
#box{
position:absolute;
background:orange;
height:100px;
width:100px;
bottom:100px;
right:100px;
}
</style>
</head>
<body>
<div id="content">
<div id="box"></div>
</div>
<script>
function Terrain(){
this.container = document.getElementById('content');
this.camera;
this.scene;
this.renderer;
this.light;
this.computeZ = function(meshHandle, cameraHandle, faceHeight, targetHeight){
var face = meshHandle.geometry.vertices[2]
var vFOV = cameraHandle.fov * Math.PI / 180;
var vHeightPartial = 2 * Math.tan( vFOV / 2 );
var p1 = faceHeight * window.innerHeight;
var p2 = face.z * vHeightPartial;
var p3 = targetHeight * vHeightPartial;
var p4 = targetHeight * p2;
var p5 = p1 + p4;
var z = p5/p3;
//calculate dom element center coordinate
var screenPositionX = 0;
var screenPositionY = 0;
var div = document.getElementById('box');
var divDim = div.getBoundingClientRect();
screenPositionX = (divDim.left + divDim.right) / 2;
screenPositionY = (divDim.bottom + divDim.top) / 2;
var vector = new THREE.Vector3((screenPositionX / window.innerWidth) * 2 -1, (screenPositionY / window.innerHeight) * 2 -1, 0.5);
//unproject camera
vector = vector.unproject(this.camera);
var distanceZ = this.camera.position.z - vector.z ;
var offsetX = vector.x * (z-10) / distanceZ;
var offsetY = vector.y * (z-10) / distanceZ;
var cameraPosition = new THREE.Vector3(offsetX,offsetY,z);
return cameraPosition;
}
this.computeX = function(meshHandle, cameraHandle, faceHeight, targetWidth){
var div = document.getElementById('box');
var divDim = div.getBoundingClientRect();
var y = ((divDim.left + (targetWidth/2)) / window.innerHeight ) * 2 + 1;
return y;
}
this.computeY = function(meshHandle, cameraHandle, faceHeight, targetHeight){
var div = document.getElementById('box');
var divDim = div.getBoundingClientRect();
var y = ((divDim.top + (targetHeight/2)) / window.innerHeight ) * 2 + 1;
return y;
}
this.onDocumentClick = function(event)
{
var vector = new THREE.Vector3(( event.clientX / (window.innerWidth) ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
vector = vector.unproject(this.camera);
console.log(vector);
}
this.init();
}
Terrain.prototype.init = function () {
this.camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
this.scene = new THREE.Scene();
this.geometry = new THREE.BoxGeometry(20, 20, 20);
this.material = new THREE.MeshPhongMaterial();
this.mesh = new THREE.Mesh( this.geometry, this.material );
this.scene.add( this.mesh );
var ambient = new THREE.AmbientLight( 0x00ff00, 0.5 );
this.scene.add( ambient );
this.renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
this.renderer.setPixelRatio( window.devicePixelRatio );
this.renderer.setSize( window.innerWidth, window.innerHeight );
this.container.appendChild(this.renderer.domElement );
window.animations['terrain'] = this.animate.bind(this);
window.addEventListener( 'resize', this.onWindowResize.bind(this), false );
document.addEventListener('click',this.onDocumentClick.bind(this), false);
}
Terrain.prototype.onWindowResize = function(){
this.renderer.setSize( window.innerWidth, window.innerHeight );
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
}
Terrain.prototype.animate = function(){
//this.camera.position.x = this.computeX(this.mesh, this.camera, 20, 100);
//this.camera.position.y = this.computeY(this.mesh, this.camera, 20, 100);
this.renderer.render( this.scene, this.camera );
}
function animate(){
for(var i in window.animations){
window.animations[i]();
};
window.requestAnimationFrame(animate);
}
window.animations = {};
var terrain = new Terrain();
window.requestAnimationFrame(animate);
var newPosition = terrain.computeZ(terrain.mesh,terrain.camera,20,100);
terrain.camera.position.x -= newPosition.x;
terrain.camera.position.y += newPosition.y;
terrain.camera.position.z += newPosition.z;
</script>
</body>
</html>
To solve the problem, I flow these two steps, 1: get the dom element coordinate in 3D space. 2: using similar triangles calculate offset of x and y. I will show you how to use similar triangles.
Cause the camera is a perspective camera. now the dom element, the camera and the mesh can make a triangle, if we ignore the Y dimension, the triangle would look like this picture.
Now, we know dom element coordinate in 3D space, and you got the right 'z', we also know the green cube's depth. and we need to calculate the offset x(purple line in the picture). It's obviously that these triangles in the picture are similar triangles. vector.x / offsetX = vector.z / z - mesh.geometry.parameters.depth
. and we can do the same thing to get the offsetY
.
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