ThreeJS: Getting world coordinates from camera view - three.js

I want to animate a Plane vertices to fill the screen. (Vertices as this is the effect I want, I'm hoping to animate each vertex with a short delay to then fill the screen)
As a proof of concept, I've got a vertex to animate off to a random point, using the function below -
tileClick() {
var geo = this.SELECTED.geometry;
var mat = this.SELECTED.material as THREE.MeshBasicMaterial;
TweenMax.TweenLite.to(geo.vertices[0], 0.3, {x: -5, y:5, onUpdate: () =>{
mat.needsUpdate = true;
geo.colorsNeedUpdate = true;
geo.elementsNeedUpdate = true;
}, ease: TweenMax.Elastic.easeOut.config(1, 0.5)});
}
However, now I need to work out the points of the current view of the camera. pseudo code: camera.view.getBoundingClientRect();
Plnkr of WIP - https://next.plnkr.co/edit/Jm4D2zgLtiKBGghC

I believe what you need is THREE.Vector3.unproject. With this method, you can set the vector to x, y, z in screen coordinates, and it'll return x, y, z in world coordinates:
var vector = new THREE.Vector3();
var zNearPlane = -1;
var zFarPlane = 1;
// Top left corner
vector.set( -1, 1, zNearPlane ).unproject( camera );
// Top right corner
vector.set( 1, 1, zNearPlane ).unproject( camera );
// Bottom left corner
vector.set( -1, -1, zNearPlane ).unproject( camera );
// Bottom right corner
vector.set( 1, -1, zNearPlane ).unproject( camera );
Notice that all inputs are in the [-1, 1] range:
x:-1 = left side of screen
x: 1 = right side of screen
y: 1 = top
y:-1 = bottom
z: 1 = far plane
z: -1 = near plane

Related

Three.js, unexpected position shift when scaling object

I'm trying to create a zoom box, so far I managed to translate the cursor positions from locale to world coordinates and create a box object around the cursor with the right uvs.
Here is the fiddle of my attempt : https://jsfiddle.net/2ynfedvk/2/
Without scaling the box is perfectly centered around the cursor, but if you toggle the scaling checkbox to set the scale zoomMesh.scale.set(1.5, 1.5, 1), the box position shift the further you move the cursor from the scene center.
Am I messing any CSS like "transform origin" for three.js to center the scale around the object, is this the right approach the get this kind of zoom effect ?
I'm new to three.js and 3d in general, so thanks for any help.
When you scale your mesh with 1.5, it means that apply transform matrix that scales values of vertices.
The issue comes from changing of vertices. Vertices are in local space of the mesh. And when you set the left-top vertex of the square, for example, to [10, 10, 0] and then apply .scale.set(1.5, 1.5, 1) to the mesh, then the coordinate of vertex became [15, 15, 0]. The same to all the other 3 vertices. And that's why the center of the square does not match at 1.5 times from the center of the picture to mouse pointer.
So, an option is not to scale a mesh, but change the size of the square.
I changed your fiddle a bit, so maybe it will be more explanatory:
const
[width, height] = [500, 300],
canvas = document.querySelector('canvas'),
scaleCheckBox = document.querySelector('input')
;
console.log(scaleCheckBox)
canvas.width = width;
canvas.height = height;
const
scene = new THREE.Scene(),
renderer = new THREE.WebGLRenderer({canvas}),
camDistance = 5,
camFov = (2 * Math.atan( height / ( 2 * camDistance ) ) * ( 180 / Math.PI )),
camera = new THREE.PerspectiveCamera(camFov, width/height, 0.1, 1000 )
;
camera.position.z = camDistance;
const
texture = new THREE.TextureLoader().load( "https://picsum.photos/500/300" ),
imageMaterial = new THREE.MeshBasicMaterial( { map: texture , side : 0 } )
;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.format = THREE.RGBFormat;
const
planeGeometry = new THREE.PlaneGeometry( width, height ),
planeMesh = new THREE.Mesh( planeGeometry, imageMaterial )
;
const
zoomGeometry = new THREE.BufferGeometry(),
zoomMaterial = new THREE.MeshBasicMaterial( { map: texture , side : 0 } ),
zoomMesh = new THREE.Mesh( zoomGeometry, zoomMaterial )
;
zoomMaterial.color.set(0xff0000);
zoomGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0
]), 3));
zoomGeometry.setIndex([
0, 1, 2,
2, 1, 3
]);
scene.add( planeMesh );
scene.add( zoomMesh );
var zoom = 1.;
function setZoomBox(e){
const
size = 50 * zoom,
x = e.clientX - (size/2),
y = -(e.clientY - height) - (size/2),
coords = [
x,
y,
x + size,
y + size
]
;
const [x1, y1, x2, y2] = [
coords[0] - (width/2),
coords[1] - (height/2),
coords[2] - (width/2),
coords[3] - (height/2)
];
zoomGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([
x1, y1, 0,
x2, y1, 0,
x1, y2, 0,
x2, y2, 0
]), 3));
const [u1, v1, u2, v2] = [
coords[0]/width,
coords[1]/height,
coords[2]/width,
coords[3]/height
]
zoomGeometry.setAttribute('uv',
new THREE.BufferAttribute(new Float32Array([
u1, v1,
u2, v1,
u1, v2,
u2, v2,
u1, v1,
u1, v2
]), 2));
}
function setScale(e){
//zoomMesh.scale.set(...(scaleCheckBox.checked ? [1.5, 1.5, 1] : [1, 1, 1]));
zoom = scaleCheckBox.checked ? 1.5 : 1 ;
}
function render(){
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
canvas.addEventListener('mousemove', setZoomBox);
scaleCheckBox.addEventListener('change', setScale);
html, body {
margin: 0;
height: 100%;
}
body{
background: #333;
color: #FFF;
font: bold 16px arial;
}
canvas{
}
<script src="https://threejs.org/build/three.min.js"></script>
<canvas></canvas>
<div>Toggle scale <input type="checkbox" /></div>
thanks for the answer, not quite what I was looking for (not only resize the square but also zoom in the image), but you pointed me in the right direction.
Like you said the positions coordinate are shifting with the scale, so I have to recalculate the new position relative to the scale.
Added these new lines, with new scale and offset variables :
if(scaleCheckBox.checked){
const offset = scale - 1;
zoomMesh.position.set(
-(x1 * offset) - (size*scale)/2) -(size/2),
-((y1 * offset) + (size*scale)/2) -(size/2)),
0
);
}
Here is the working fiddle : https://jsfiddle.net/dc9f5v0m/
It's a bit messy, with a lot of recalculation (Especially to center the cursor around the square), but it gets the job done and the zoom effect can be achieved with any shape not only a square.
Thanks again for your help.

Getting Z value on mesh for respective XY

I am trying to get the Z value on the mesh when i pass the X & Y coordinate. Sorry, i am new to three js.
I am using raycaster for the same. My plan is to set origin exactly above the point and direction just below it. So that it will intersect on mesh and will return me the respective values.
Here is my code:
for(var i=0;(i)<points.length;i++){
var pts = points[i];
var top = new THREE.Vector3(pts.x , pts.y , 50 );
var bottom = new THREE.Vector3( pts.x , pts.y , -50 );
//start raycaster
var raycaster = new THREE.Raycaster();
raycaster.set( top, bottom );
// calculate objects intersecting the picking ray
var intersects = rayCaster.intersectObjects(scene.getObjectByName('MyObj_s').children, false);
if (intersects.length > 0){
console.log(intersects[0].point);
}
}
However the above code results shows totally different X & Y positions, and definitely inaccurate Z values.
top
Object { x: 58.26593421875712, y: 63.505675324244834, z: 50 }
bottom
Object { x: 58.26593421875712, y: 63.505675324244834, z: -50 }
Result
Object { x: -2.9414508017947445, y: -13.236528362050667, z:
-2.0969017881066634 }
raycaster.set( top, bottom );
It seems you are not using Raycaster.set() correctly. As you can see in the documentation, the method expects an origin and a direction vector. In your code, you just pass in two points.
The first parameter origin represents the origin vector where the ray casts from.
The second parameter direction is a normalized (!) vector representing the direction of the ray.
three.js R104

Is there ANY way to have the three.js camera lookat being rendered off-center?

Is there a way to setup the Three.js renderer in such a way that the lookat point of the camera is not in the center of the rendered image?
To clarify: image a scene with just one 1x1x1m cube at ( 0, 0, 0 ). The camera is located at ( 0, 0, 10 ) and the lookat point is at the origin, coinciding with the center of the cube. If I render this scene as is, I might end up with something like this:
normal render
However I'd like to be able to render this scene in such a way that the lookat point is in the upper left corner, giving me something like this:
desired render
If the normal image is 800x600, then the result I envision would be as if I rendered a 1600x1200 image with the lookat in the center and then cropped that normal image so that only the lower right part remains.
Of course, I can change the lookat to make the cube go to the upper left corner, but then I view the cube under an angle, giving me an undesired result like this:
test.moobels.com/temp/cube_angle.jpg
I could also actually render the full 1600x1200 image and hide 3/4 of the image, but one would hope there is a more elegant solution. Does anybody know it?
If you want your perspective camera to have an off-center view, the pattern you need to use is:
camera = new THREE.PerspectiveCamera( for, aspect, near, far );
camera.setViewOffset( fullWidth, fullHeight, viewX, viewY, viewWidth, viewHeight );
See the docs: https://threejs.org/docs/#api/cameras/PerspectiveCamera
You can find examples of this usage in this example and this example.
three.js r.73
Here's a simple solution:
Assuming your cube is 4 x 4 x 4, at position 0, 0, 0:
var geometry = new THREE.BoxGeometry( 4, 4, 4 );
var material = new THREE.MeshBasicMaterial( { color: 0x777777 } );
var cube = new THREE.Mesh( geometry, material );
cube.position.set( 0, 0, 0 );
Get cube's position:
var Vx = cube.position.x,
Vy = cube.position.y,
Vz = cube.position.z;
Then deduct by 2 from x position, then add 2 to y and z position, and use the values to create a new Vector3:
var newVx = Vx - 2,
newVy = Vy + 2;
newVz = Vz + 2;
var xyz = new THREE.Vector3(newVx, newVy, newVz)
Then camera lookAt:
camera.lookAt(xyz);
Using console log, it would show that the camera is now looking at -2, 2, 2, which is the upper-left of your cube.
console.log(xyz);

Rotation with negative scale

I'm creating a tool to rotate images in ThreeJs, but it doesn't work when dealing with negative scales.
The image is displayed in a Mesh created using a THREE.PlaneGeometry element and a material which maps to to correspongin image.
The tool is an object that has an element called gizmo (it's a small mesh) which is selected and dragged by the user to rotate the object.
To do the rotation I define an angle and an axis. The angle is defined by two vectors created using the the position of the gizmo (original and current) and the position of the Mesh.
var gizmoOriginalPosition = this.gizmoOriginalPosition.clone().applyMatrix4( this.matrixWorld );
var imagePosition = this.imageToTransformOriginalPosition.clone().applyMatrix4( this.imageToTransformParentOriginalMatrix );
var vector1 = gizmoOriginalPosition.sub( imagePosition ).normalize();
var vector2 = point.sub( imagePosition ).normalize();
var angle = Math.acos( vector1.dot( vector2 ) );
var axis = new THREE.Vector3( 0, 0, 1 );
var ortho = vector2.clone().cross( vector1 );
var _m = this.imageToTransformOriginalMatrix.clone();
this.tempMatrix.extractRotation( _m );
var q = new THREE.Quaternion().setFromRotationMatrix( this.tempMatrix );
var _axis = axis.clone().applyQuaternion( q );
var f = ortho.dot( _axis );
f = f > 0 ? 1 : -1;
angle *= -f;
var q = new THREE.Quaternion().setFromAxisAngle( axis, angle );
var Q = new THREE.Quaternion().multiplyQuaternions( this.imageToTransformOriginalQuaternion, q );
imageToTransform.quaternion.copy( Q );
The axis of rotation is always ( 0, 0, 1) because the Mesh is a plane in XY.
point is the new position of the gizmo using a plane of intersection.
The vectors to define the angle are in world coordinates. ortho is a vector to define the direction of the angle, so the Mesh rotates in the direction of the mouse pointer. I define the direction of the angle with the f value obtained using ortho and axis. The axis ( 0, 0, 1 ) is rotated so its direction is in world coordinates ( ortho is in world coordinates ).
This works as expected in almost every case, except when the Mesh has a negative scale in X and Y. Here the image rotates in the opposite direction to the mouse pointer.
Thanks.

Molecule angles building

I try to build molecule CH4 with threejs
But when I try to build 109.5 angle
methanum = function(x, y, z) {
molecule = new THREE.Object3D();
var startPosition = new THREE.Vector3( 0, 0, 0 );
molecule.add(atom(startPosition, "o"));
var secondPosition = new THREE.Vector3( -20, 10, 00 );
molecule.add(atom(secondPosition, "h"));
var angle = 109.5;
var matrix = new THREE.Matrix4().makeRotationAxis( new THREE.Vector3( 0, 1, 0 ), angle * ( Math.PI / 180 ));
var thirdPosition = secondPosition.applyMatrix4( matrix );
molecule.add(atom(thirdPosition, "h"));
var fourthPosition = thirdPosition.applyMatrix4( matrix );
molecule.add(atom(thirdPosition, "h"));
molecule.position.set(x, y, z);
molecule.rotation.set(x, y, z);
scene.add( molecule );
}
Demo: https://dl.dropboxusercontent.com/u/6204711/3d/ch4.html
But my atoms are not uniformly distributed as in the drawing
Some ideas?
Well there are 3 errors in your molecule code.
You place an oxygen as the center of the CH4 instead of a carbon
When you apply your fourth hydrogen, you specify the third position whereas you have created a fourthposition.
You are rotating around the wrong axis when you place your third hydrogen. My hints are the following: First of all , place your carbon, then move along the Z-axis, place your first hydrogen, rotate around the X-axis of 109.5°, place your second hydrogen, rotate around the Z-axis of 120° the position of your second hydrogen, place your third hydrogen and finally rotate once again around the Z-axis of 120° the position of your third hydrogen and place your last hydrogen.
Here is the CH4 I tried:
methanum3 = function(x, y, z) {
molecule = new THREE.Object3D();
var startPosition = new THREE.Vector3( 0, 0, 0 );
molecule.add(atom(startPosition, "c"));
var axis = new THREE.AxisHelper( 50 );
axis.position.set( 0, 0, 0 );
molecule.add( axis );
var secondPosition = new THREE.Vector3( 0, 0, -40 );
molecule.add(atom(secondPosition, "h"));
var angle = 109.5;
var matrixX = new THREE.Matrix4().makeRotationAxis( new THREE.Vector3( 1, 0, 0 ), angle * ( Math.PI / 180 ));
var thirdPosition = secondPosition.applyMatrix4( matrixX );
molecule.add(atom(thirdPosition, "h"));
var matrixZ = new THREE.Matrix4().makeRotationAxis( new THREE.Vector3( 0, 0, 1 ), 120 * ( Math.PI / 180 ));
var fourthPosition = thirdPosition.applyMatrix4( matrixZ );
molecule.add(atom(fourthPosition, "h"));
var fifthPosition = fourthPosition.applyMatrix4( matrixZ );
molecule.add(atom(fifthPosition, "h"));
molecule.position.set(x, y, z);
//molecule.rotation.set(x, y, z);
scene.add( molecule );
}
//water(0,0,0);
//water(30,60,0);
methanum3(-30,60,0);
Explanation:
Let's call H1 an hydrogen and H2 another one. The given angle of 109.5° is defined in the :
---> --->
(CH1,CH2) plane. Therefore when you look in the direction of the normal of that plane, you can see the 109.5° (Cf. the right part of the image below) BUT When you look in the direction of the normal of another plane you'll get the projection of that angle on that plane. In your case when you look in the direction of the Z-axis you can see an angle of 120°.(Cf. left part of the image below).
The two angles are different according to the direction of the camera.
Hope this helps.

Resources