Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ThreeJS update Camera position so Plane world position matches DIV screen position

Goal

I want to update the Camera Position so that a Plane World Position matches a DIV Screen Position.

before update after update different perspective

First thoughts

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.

Next steps

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

Please help

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?

Update

I've come up with a solution with the help of Craig's post. This class builds on his methods to cover resize events.

like image 728
Dan Kanze Avatar asked May 03 '17 00:05

Dan Kanze


1 Answers

<!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.

enter image description here

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.

like image 163
Craig.Li Avatar answered Sep 20 '22 22:09

Craig.Li