Rotate around local axis - javascript

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

Related

THREE.js ArrowHelper not obeying intrinsic Euler angle rotation

I would like to set intrinsic rotations to a THREE.ArrowHelper. From my understanding, THREE.js natively uses intrinsic Tait-Bryan euler angles to represent 3D rotations.
In the code below, I create a unit vector representing the x-axis, THREE.Vector3(1, 0, 0).
I then rotate it about the Y and Z axis by an arbitrary amount.
Since there was some rotation about Y and Z, the X axis of the local coordinate system (which I assume points along the red vector) has also changed.
So, when I apply a rotation about X, I don't expect the arrow to move at all (except rotate in place...but that shouldn't be visible).
Instead, I see the arrow sweeping around, as if it's rotating about some arbitrary axis, and not its local x axis.
Thanks for any assistance!
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 origin = new THREE.Vector3(0, 0, 0);
var xDir = new THREE.Vector3(1, 0, 0);
var length = 1;
var arrow = new THREE.ArrowHelper(xDir, origin, length, 0xff0000);
arrow.rotation.order = 'XYZ';
arrow.rotation.y = 0.5;
arrow.rotation.z = 0.5;
scene.add(arrow);
camera.position.z = 5;
var animate = function () {
requestAnimationFrame( animate );
arrow.rotation.x += 0.01;
renderer.render( scene, camera );
};
animate();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
The problem is that ArrowHelper is doing its own special rotation math. It creates a unit arrow facing up (+Y). It the uses custom math to setup the orientation to make that line point in the given direction in local space.
You can see this if you just create the arrow and then print the rotation
var origin = new THREE.Vector3(0, 0, 0);
var xDir = new THREE.Vector3(1, 0, 0);
var length = 1;
var arrow = new THREE.ArrowHelper(xDir, origin, length, color);
console.log(arrow.rotation.x, arrow.rotation.y, arrow.rotation.z);
You'll see rotation.z is already set to rotate the +Y arrow to face +X so then you go and change those rotations and the arrow is no longer based facing +X.
That means manipulating the arrow via arrow.rotation won't work as expected.
If you parent the arrow to an Object3D and then rotate that object it will work as expected (or as I expect it 😅)
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 );
scene.add(new THREE.GridHelper(10, 10));
function addArrow(x, ry, rz, color) {
var origin = new THREE.Vector3(0, 0, 0);
var xDir = new THREE.Vector3(1, 0, 0);
var length = 1;
var arrow = new THREE.ArrowHelper(xDir, origin, length, color);
var ob = new THREE.Object3D();
ob.position.x = x;
ob.rotation.order = 'XYZ';
ob.rotation.y = ry;
ob.rotation.z = rz;
scene.add(ob);
ob.add(arrow);
return ob;
}
addArrow(-4, 0, 0, 0xFF0000);
addArrow(-2, 0, 0.5, 0x00FF00);
addArrow( 0, 0.5, 0.5, 0xFFFF00);
const arrow = addArrow( 2, 0.5, 0.5, 0x00FFFF);
camera.position.z = 6;
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.update();
var animate = function () {
requestAnimationFrame( animate );
arrow.rotation.x += 0.01;
renderer.render( scene, camera );
};
animate();
body { margin: 0; }
canvas { display: block; }
<script src="https://cdn.jsdelivr.net/npm/three#0.115.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.115.0/examples/js/controls/OrbitControls.js"></script>
What I expect.
Rotations are in the local coodinate system. Rotation order 'XYZ' means, assuming you only had the arrow the full matrix calculation would be
matrix = projection *
view *
obTranslation *
obXAxisRotation *
obYAxisRotation *
obZAxisRotation *
obScale *
arrowOrientation;
In any case
var origin = new THREE.Vector3(0, 0, 0);
var xDir = new THREE.Vector3(1, 0, 0);
var length = 1;
var arrow = new THREE.ArrowHelper(xDir, origin, length, 0xff0000);
var ob = new THREE.Object3D();
scene.add(ob);
ob.add(arrow);
ob.rotation.order = 'XYZ';
Viewed from 0, 0, 5 this gives us an arrow pointing right.
I like to look at matrices as applied from the right to the left so looking at the formula above first scale will be applied. It's 1,1,1 so no change
Next zAxisRotation is applied. 0.5 radians is about 30 degrees so the arrow is now either pointing 30 degrees up
Next yAxisRotation is applied. The 30 degree up arrow is now pointing 30 degrees back as into the distance.
Next xAxisRotation is applyed so this funkily pointed arrow will spin around x
run it and drag on the sample above to look from above. You'll see it matches the description.
So it's up to you then. You can make a +X facing ArrowHelper and parent it to an Object3D or you can just know that an ArrowHelper actually makes a +Y arrow and then set the rotations appropriately knowing that.
To be honest, I've never used THREE.js, but I'll try to visualize my point with this answer. The imaginary or world axis is represented by the gray arrow, notice how when you toggle Y and Z axes values, it affects the red arrow but not the other gray arrow, that's what I meant by the imaginary X axis is not moving.
Your red arrow is still rotating around the X axis but not its X axis, but the world's which is why when you change the arrow's Y and Z axes it looks like it's sweeping around when in actuality it just continues to rotate around the same fixed axis it was rotating around since the beginning.
Well, I really expect I didn't make more a mess than an explanation.
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 origin = new THREE.Vector3(0, 0, 0);
var xDir = new THREE.Vector3(1, 0, 0);
var length = 1;
var arrow = new THREE.ArrowHelper(xDir, origin, length, 0xff0000);
var imaginaryXAxis = new THREE.ArrowHelper(xDir, origin, length+100, 0xffffff);
arrow.rotation.order = 'XYZ';
/*arrow.rotation.y = 0.5;
arrow.rotation.z = 0.5;*/
scene.add(arrow);
scene.add(imaginaryXAxis);
camera.position.z = 2;
var animate = function () {
requestAnimationFrame( animate );
arrow.rotation.x += 0.01;
renderer.render( scene, camera );
};
animate();
const yValue = arrow.rotation.y, zValue = arrow.rotation.z;
document.querySelector('button').addEventListener('click', (e) => {
e.target.classList.toggle('affected')
if(e.target.classList.contains('affected')){
arrow.rotation.y=.5;
arrow.rotation.z=.5;
e.target.textContent = "Reset Y and Z to zero";
} else {
arrow.rotation.y=yValue;
arrow.rotation.z=zValue;
e.target.textContent = "Affect Y and Z";
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
<button>Affect Y and Z</button>

Three.js rotate cube to show selected side in front view?

I have a rotating box made with three.js and I want to stop (and restart) animation to show the object face frontally.
I would like to show a smooth animation until the cube is in the correct position. I 've seen several examples here on StackOverflow but I wasn't able to replicate it in my example.
Example: JSFiddle example
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.z = 500;
scene.add(camera);
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
Here the animation
function render() {
mesh.rotation.x += 0.01;
mesh.rotation.z += 0.01;
mesh.rotation.y += 0.02;
renderer.render(scene, camera);
}
How is the best approach to keep it simple and smooth?
One approach to solve this problem is the usage of Quaternion.rotateTowards() (a method similar to Unity's Quaternion.RotateTowards()).
First, you have to define a target rotation for your box. This can be done like so:
var targetQuaternion = new THREE.Quaternion();
targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI, 0 ) );
In the next step, you use Quaternion.rotateTowards() in your animation loop like so:
if ( ! mesh.quaternion.equals( targetQuaternion ) ) {
var step = speed * delta;
mesh.quaternion.rotateTowards( targetQuaternion, step );
}
step represents the angular displacement in radians per seconds. In the following live example, the value is set to Math.PI. Meaning the box can rotate 180 degrees per second.
So essentially, you can define a target rotation for all sides of the cube and then use Quaternion.rotateTowards() to rotate towards it.
https://jsfiddle.net/0eutrjqs/
three.js R113

Three.js Draw where mouse clicks, but entirely parallel to camera orientation

I"m not exactly sure how I can do this. I read a lot about raycasting and that seems to be good for finding points that intersect with something, but in this case I just want it to interpolate the 2d mouse coordinates to the 3d point exactly where the mouse clicks, regardless of scale, rotation, whether or not there's an object there, etc.
One method I've thought of but not approached would be making an invisible plane that is parallel to the camera, always oriented upright and always intersecting the y axis. Then use a raycaster to hit the plane, draw as needed, then delete the plane. Seems like a silly way to do this though.
At the moment I have a method that works pretty well but it has some issues when the line gets further away from the origin, or the camera gets zoomed
In this photo I drew two lines from two different perspectives. The vertical line what it looks like when the camera is level with the x and z axis, and I draw a straight line down the y axis, while the horizontal line is what happens when i scribble with the camera facing down.
https://i.imgur.com/f8qw5xV.png
As you can see, it seems to use the distance to the camera to make this calculation, so the further the distance from the camera, the more distortion is in the calculation. How can get rid of this distortion?
source: https://github.com/AskAlice/mandala-3d-threejs
live demo: https://askalice.me/mandala/
Here is the relevant code:
js/content.js#112
function get3dPointZAxis(event)
{
camPos = camera.position;
var mv = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY/window.innerHeight) * 2 + 1, 1).unproject(camera);
var m2 = new THREE.Vector3(0,0,0);
var pos = camPos.clone();
pos.add(mv.sub(camPos).normalize().multiplyScalar(m2.distanceTo(camPos)));
return pos;
}
I used information from two stackoverflow posts to come up with this, and it has the issues I've described.
firstly, this post shows how to draw and convert it to the z axis. It is flat. But I had a lot of trouble trying to make that work in three dimensions.
How to draw a line segment at run time using three.js
and then i used information in the below post to at least get it parallel to the camera on the x-z axis like such: https://i.imgur.com/E9AQNpH.png
Moving objects parallel to projection plane in three.js
That option with THREE.Plane() and THREE.Raycaster().ray.intersectPlane():
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var point = new THREE.Vector3();
function getPoint(event){
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
planeNormal.copy(camera.position).normalize();
plane.setFromNormalAndCoplanarPoint(planeNormal, scene.position);
raycaster.setFromCamera(mouse, camera);
raycaster.ray.intersectPlane(plane, point);
}
Run the code snippet, click the "draw" checkbox to set it as checked, move your mouse randomly (without mouse down), click the checkbox again, rotate the scene with mousedown. All points are on the same plane.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var point = new THREE.Vector3();
document.addEventListener("mousedown", onMouseDown, false);
document.addEventListener("mousemove", onMouseMove, false);
function getPoint(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
planeNormal.copy(camera.position).normalize();
plane.setFromNormalAndCoplanarPoint(planeNormal, scene.position);
raycaster.setFromCamera(mouse, camera);
raycaster.ray.intersectPlane(plane, point);
}
function setPoint() {
var sphere = new THREE.Mesh(new THREE.SphereBufferGeometry(.125, 4, 2), new THREE.MeshBasicMaterial({
color: "yellow",
wireframe: true
}));
sphere.position.copy(point);
scene.add(sphere);
}
function onMouseDown(event) {
getPoint(event);
if (draw.checked) setPoint();
}
function onMouseMove(event) {
getPoint(event);
if (draw.checked) setPoint();
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<div style="position:absolute;">
<input id="draw" type="checkbox">
<label for="draw" style="color: white;">draw</label>
</div>

Three.js Rotate sphere to arbitrary position

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?

Understanding Sprite distance using Raycast in three js

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

Categories