Using Three.js I have a sphere (globe) and several sprites attached to volcano points. I can rotate (spin) the globe and the sprites stay in their positions because they're added as a group to the sphere.
Now I want to be able to spin the globe to an arbitrary position using a button. How can I do this? For example if the point I want to spin to is at the back of the globe, how can I rotate the globe so it's in the front?
This code is essentially what I have right now. A main mesh which I add sprite to.
<html>
<head></head>
<body>
<script src="three.min.js"></script>
<script>
var scene, camera, renderer;
var geometry, material, mesh;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 1000;
material = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: false } );
geometry = new THREE.SphereGeometry( 159, 32, 32 );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
var map = THREE.ImageUtils.loadTexture( "sprite1.png" );
var material2 = new THREE.SpriteMaterial( { map:map, color:0x00ff00 } );
var sprite1 = new THREE.Sprite( material2 );
sprite1.position.set(100,100,100);
sprite1.scale.set(40,40,40);
mesh.add(sprite1);
var sprite2 = new THREE.Sprite( material2);
sprite2.position.set(-100,-100,-100);
sprite2.scale.set(30,30,30);
mesh.add(sprite2);
var sprite3 = new THREE.Sprite(material2);
sprite3.position.set(100,-100,100);
sprite3.scale.set(20,20,20);
mesh.add(sprite3);
renderer = new THREE.WebGLRenderer({alpha:true});
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
mesh.rotation.y += 0.01;
renderer.render( scene, camera );
}
</script>
</body>
</html>
Diagram example
This would be my approach:
// as sprite is a child of mesh get world position
var spritePos = new THREE.Vector3().setFromMatrixPosition(sprite.matrixWorld);
// get the vectors for calculating angle
var cv3 = new THREE.Vector3().subVectors(camera.position, mesh.position);
var sv3 = new THREE.Vector3().subVectors(spritePos, mesh.position);
// we only want to rotate around y-axis, so only the angle in x-z-plane is relevant
var cv2 = new THREE.Vector2(cv3.x, cv3.z);
var sv2 = new THREE.Vector2(sv3.x, sv3.z);
// normalize Vectors
cv2.normalize();
sv2.normalize();
// dot product
var dot = cv2.dot(sv2);
// angle to between sprite and camera in radians
// cosinus is from 1 to -1, so we need to normalize and invert it and multiply it with PI to get proper angle
var angle = (1 - (dot + 1) / 2) * Math.PI;
// is sprite left or right from camera?
if(spritePos.x < 0)
mesh.rotation += angle;
else
mesh.rotation -= angle;
Now, I made a Plunker.
It seems a bit inaccurate as it always rotates a bit left or right to the very front position. Maybe it's due to the cosinus near to some specific angles.
Also keep in mind that the determination whether the sprite is left or right from the camera is a bit more difficult if camera or mesh is somewhere else in the scene.
Explanation after dot product:
The dot product gives the angle of two vectors as cosinus. So we get a value between -1 and 1. e.g. cos(0) = 1 cos(PI/2) = 0 cos(PI) = -1 So at the moment is 0° = 1 and 180° = -1.
We want to get the angle in radians to rotate the mesh in position. So first we normalize it (dot + 1) / 2, so 0° = 1 and 180° = 0.
Then invert it (0° = 0, 180° = 1) and multiply with PI (0° = 0, 180° = PI).
Now, we have the angle to rotate, but we don't know if need to rotate to the left or to the right, that's why I check if the sprite is left or right from camera.
I don't know if it's explanation enough or if it's comprehensable at all?
Related
I'm using Three.js to develop a player for 360° pictures.
My scene contains a sphere (BasicMeshMaterial) that has a 360° picture as texture. The camera is in the middle of that sphere, so that the user sees the picture, that is applied to the sphere's "walls".
I recently added the possibility to click on some spots of the sphere, to rotate the camera towards that spot. The camera smoothly moves to this spot, untill it has this spot in the center of the screen.
My problem is that sometimes the camera easing (tween animation) that is supposed to focus the spot clicked uses a very strange path and it's all very ugly.
This seems to happen when the initial position of the camera and the target position cross a certain point of the sphere. I have almost no clue what happens. Do you ? Could it have a link with the sphere, or the camera is only playing with quaternions and so it's not affected by others objects ? Then what could make it chose wrong path, and how come it happens only with a certain angle of the sphere ? anything would be help !
The player is testable at http://isiko.io/, after scrolling a bit.
I can show you the code but don't really know what to pick. Tell me if you need the camera's rotation,or the sphere's initiation..
Thank you for your feedbacks.
There is another approach with tweening an angle around an axis:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 0);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var sphere = new THREE.Mesh(new THREE.SphereGeometry(10, 32, 24), new THREE.MeshBasicMaterial({
color: "yellow",
wireframe: true
}));
scene.add(sphere);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var center = new THREE.Vector3();
var vectorStart = new THREE.Vector3(0, 0, -10); // in front of the camera
var vectorEnd = new THREE.Vector3();
var angle = {
value: 0
};
var angleEnd = {
value: 0
};
var normal = new THREE.Vector3();
var lookAt = new THREE.Vector3();
var isMoving = false;
window.addEventListener("mousedown", onMouseDown, false);
function onMouseDown(event) {
if (isMoving) return;
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
let newPosition = raycaster.ray.at(10);
setPoint(newPosition);
// prepare the values we need for tweening
vectorEnd.copy(newPosition);
angle.value = 0; // we will tween this value
angleEnd.value = vectorStart.angleTo(vectorEnd); // to this one
normal.copy(vectorStart).cross(vectorEnd).normalize(); // around this axis
// tween the angle
var tween = new TWEEN.Tween(angle).to(angleEnd, 1000).delay(250).easing(TWEEN.Easing.Cubic.InOut).onStart(function(){
isMoving = true;
}).onUpdate(
function() {
camera.lookAt(lookAt.copy(vectorStart).applyAxisAngle(normal, angle.value));
}
).onComplete(function() {
isMoving = false;
vectorStart.copy(vectorEnd);
}).start();
}
function setPoint(position) {
let point = new THREE.Mesh(new THREE.SphereGeometry(0.125, 4, 2), new THREE.MeshBasicMaterial({
color: "red",
wireframe: true
}));
point.position.copy(position);
scene.add(point);
}
render()
function render() {
requestAnimationFrame(render);
TWEEN.update(); // don't forget to put this line into the animation loop, when you use Tween.js
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.min.js"></script>
I have two planes, how can I calculate angle between them? Is it also possible to calculate angle between two Object3D points like in case of planes?
Heres an example fiddle: https://jsfiddle.net/rsu842v8/1/
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(25, 25, 12);
var material = new THREE.MeshBasicMaterial({
color: 0x00fff0,
side: THREE.DoubleSide
});
window.plane1 = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), material);
scene.add(plane1);
plane1.position.set(0.3, 1, -2);
plane1.rotation.set(Math.PI / 3, Math.PI / 2, 1);
window.plane2 = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshBasicMaterial({
color: 0x0fff00,
side: THREE.DoubleSide
}));
scene.add(plane2);
// setup rest
var pointLight = new THREE.PointLight(0xFFFFFF);
pointLight.position.x = 10;
pointLight.position.y = 50;
pointLight.position.z = 130;
scene.add(pointLight)
const renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x20252f);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
animate();
// TODO: What is the angle between plane1 and plane2?
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r82/three.js"></script>
<script src="https://yume.human-interactive.org/examples/buffer-geometry/OrbitControls.js"></script>
You want to find the angle between two three.js plane meshes.
Unrotated, a THREE.PlaneGeometry is oriented to face the positive z-axis. So the plane's normal points in the direction of the positive z-axis.
So, create a ( 0, 0, 1 ) vector, and apply the same rotation to it as is applied to the plane mesh.
Note that plane.quaternion is automatically updated when you set plane.rotation, so you can use the quaternion in the calculation -- like so:
var vec1 = new THREE.Vector3( 0, 0, 1 ); // create once and reuse
var vec2 = new THREE.Vector3( 0, 0, 1 );
vec1.applyQuaternion( plane1.quaternion );
vec2.applyQuaternion( plane2.quaternion );
var angle = vec1.angleTo( vec2 ); // radians
The problem is a bit more complicated if the planes are children of other rotated objects.
Of course, you can use angleTo() to find the angle between any two vectors.
three.js r.86
I would suggest somehow calculating the normal vectors for each plane you are rendering. Once you have these two vectors - let's say n1 and n2 - it is easy to calculate the angle between the planes with the dot product.
If you aren't familiar with the dot product, dot(n1,n2) where n1 = (x1,y1,z1) and n2 = (x2,y2,z2) would be equal to x1*x2 + y1*y2 + z1*z2. There is another simple identity that says dot(n1,n2) = |v1||v2|cos(a) where || indicates the magnitude of a vector - i.e. |v| = sqrt(x*x + y*y + z*z) if v = (x,y,z) - and a is the angle between the normals which is the angle between the planes. Here is a link to a Mathematics Stack Exchange answer.
In short a = arccos(dot(n1,n2) / |n1||n2|).
If you are interested in learning more about how planes are defined and what the normal vector represents try looking at this.
If you know n1 and n2 are unit vectors then the equation simplifies further to a = arccos(dot(n1,n2)).
I'm trying to implement an object, flying around a position (POI - Point Of Interest) and facing it. when you press WASD, you can change the POI's rotation. A and D -> change y-axis, W and S for the x-axis.
As you can see in the demo (http://jsbin.com/yodusufupi) the y-axis rotation is done based on local rotation of the helper object, but the x-axis is calculated in global space. Setting the rotation is done via: helper.rotation.set(rotX, rotY, 0);.
What I'm doing wrong? I want to have both rotations beeing done in local space.
Thx!
PS: minimal working example (the rotation around Y seems correct while x-axis is calculated globally)
var scene = new THREE.Scene();
var DEG_2_RAD = Math.PI / 180;
var rotX = -45 * DEG_2_RAD;
var rotY = 45 * DEG_2_RAD;
var helper = new THREE.AxisHelper(2);
var cam = new THREE.AxisHelper(1);
helper.add(cam);
scene.add(helper);
cam.translateZ(4);
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 10;
camera.position.y = 10;
camera.lookAt(new THREE.Vector3(0, 0, 0));
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild( renderer.domElement );
helper.rotation.set(rotX, rotY, 0);
renderer.render( scene, camera );
You need to understand how Euler angles work in three.js. For that, see this answer.
For your use case you can use these methods:
object.rotateX( angle ); // angle is in radians
object.rotateY( angle );
three.js r.77
I'm adding sprites in a 3d scene using three.js and I want to know distance between the camera and sprite when I click on screen. So I use a Raycater.
But if I click on the sprite, the distance property of intersection object is always "wrong" (someting like 0.3), or maybe I don't know how to read and understand the result. I thought "distance" value of intersection is the distance from camera to sprite (so, in my case something like 5).
Here is a shortened version of my code :
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var sprite = new THREE.Sprite(new THREE.SpriteMaterial({color: 0x00ff00}));
scene.add(sprite);
camera.position.z = 5;
var render = function () {
requestAnimationFrame(render);
renderer.render(scene, camera);
};
render();
window.addEventListener('mousedown', function (e) {
if (e.target == renderer.domElement) {
var vector = new THREE.Vector3((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1, 0.5);
var projector = new THREE.Projector();
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects([sprite]);
console.log(intersects[0]);
}
}, false);
You can see it in action here : http://jsfiddle.net/pWr57/
So how can I have the distance form camera to a sprite ?
three.js r66
Do this, instead
console.log( raycaster.ray.origin.distanceTo( intersects[0].point ) );
Tip: Read the source code, Raycaster.js, so you know what it is doing. It is currently returning the perpendicular distance from the sprite center to the ray.
In this case, I agree that it would be better to return the distance from the camera.
three.js r.66
Scenario:
In my scene I implemented a vertex shader that positions a plane mesh on the xz-axis at the position of the camera.
So if the camera moves, the plane mesh moves with it. This leads to the visual effect that, while moving the camera, the plane mesh seems to stay fixed in place. This seems to work correctly.
Problem:
If I move the camera (and therefore the plane mesh) to a certain extend, the mesh suddenly disappears.
I realized that there seems to be a relationship between the disappearance and the size of the plane, i.e. the larger the plane, the more I can move the camera before the plane mesh disappears.
Also, in my test scene the plane mesh only disappears when moving on the negative x-axis, positive x-axis or negative z-axis. It does NOT disappear when moving on the positive z-axis.
I assume it has something to do with some kind of clipping, but may be wrong. Recomputing the bounding box of the plane mesh had no effect.
Any ideas?
Cheers
Fiddle:
I created a fiddle that shows the problem: http://jsfiddle.net/p8wZ6/10/
In the fiddle I added an additional box mesh to better visualize that the camera actually moves.
- To change the axis the camera moves on (negative z-axis by default) (un-)comment the appropriate code line in the tick method.
- To change the size of the plane change the size value in the createPlane method.
Sourcecode Shader:
<script id="vertexShader" type="x-shader/x-vertex">
void main() {
vec4 pos = vec4( position, 1.0 );
vec4 wPos = modelMatrix * pos;
wPos.x += cameraPosition.x;
wPos.z += cameraPosition.z;
// standard
// vec4 pPos = projectionMatrix * modelViewMatrix * pos;
// keep fixed
vec4 pPos = projectionMatrix * viewMatrix * wPos;
gl_Position = pPos;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
void main() {
gl_FragColor.rgb = vec3(0.7, 0.7, 0.7);
gl_FragColor.a = 1.0;
}
</script>
Sourcecode JS:
var scene;
var camera;
var light;
var renderer;
var controls;
var onTick;
var planeMesh;
var boxMesh;
var heightmap;
var clock;
function createPlane(){
// disappearance seems related to size of geometry.
// the larger the longer it takes until disappearance.
var size = 20;
var geom = new THREE.PlaneGeometry(size, size, 20, 20);
return geom;
}
function createBox(){
var geom = new THREE.CubeGeometry(2, 2, 4);
return geom;
}
function createMesh(){
// plane
var geom = createPlane();
var shaderMaterial = new THREE.ShaderMaterial({
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
side: THREE.DoubleSide,
wireframe: true
});
planeMesh = new THREE.Mesh(geom, shaderMaterial);
var axis = new THREE.AxisHelper(4);
planeMesh.rotation.x = -90 * (Math.PI / 180);
planeMesh.add(axis);
scene.add(planeMesh);
// box
geom = createBox();
var material = new THREE.MeshBasicMaterial( {
color: 0xff00ff,
});
boxMesh = new THREE.Mesh(geom, material);
boxMesh.position.x = 5;
boxMesh.position.z = -15;
axis = new THREE.AxisHelper(4);
boxMesh.add(axis);
scene.add(boxMesh);
}
function startRendering(){
onTick();
};
function onTick(){
// move camera
// causes disappearance
// neg. z
camera.position.z -= .1;
// pos. x
// camera.position.x += .1;
// neg. x
// camera.position.x -= .1;
// causes no disappearance
// pos. z
// camera.position.z += .1;
requestAnimationFrame(onTick);
//controls.update(clock.getDelta());
renderer.render(scene, camera);
}
function init(){
renderer = new THREE.WebGLRenderer();
renderer.setClearColor( 0xffffff, 1 );
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
scene.add(new THREE.AxisHelper(4));
camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1, 0);
light = new THREE.DirectionalLight(0xffffff, 1);
light.shadowCameraVisible = true;
light.position.set(0, 0, 100);
scene.add(light);
//clock = new THREE.Clock();
//controls = new THREE.FirstPersonControls(camera);
//controls.movementSpeed = 20;
//controls.lookSpeed = .1;
}
init();
createMesh();
startRendering();
You have a fundamental misunderstanding.
You are moving the camera in the CPU. You are moving the vertices of the plane in the GPU.
The camera's frustum calculation knows nothing about the vertex displacements in the vertex shader.
As a work-around, you can set
planeMesh.frustumCulled = false;
A better solution is to just add the plane as a child of the camera, and omit vertex displacements.
planeMesh.position.set( 0, -1, 0 );
camera.add( planeMesh );
scene.add( camera );
You must add the camera to the scene graph it you use the second approach.
three.js r.65
When you define your camera in r73 the last two parameters allow you to specify your camera's near and far z clipping distance.
Taken from this link: http://threejs.org/docs/#Manual/Introduction/Creating_a_scene
var camera =
new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
The third parameter of Three.PerspectiveCamera defines the camera's near clipping distance and the fourth parameter defines the camera's far clipping distance.