THREE.js ArrowHelper not obeying intrinsic Euler angle rotation - javascript

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>

Related

How to calculate angle between two planes?

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

Rotate around local axis

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

Rotating a 3d vector in THREE.js on two axes

Sorry for posting possible duplicate.
I have two 3d vectors:
center ( 0, 0, 0 )
orb ( 0, 0, 100 )
I want to rotate the orb-vector around the center-vector on both X and the Y axes.
What I'm trying to achieve is the make and object always be in view of the camera in the direction it's facing.
I've tried this but with no luck.
var vector = new THREE.Vector3( 0, 0, 100 );
vector.applyAxisAngle( new THREE.Vector3( 0, 1, 0 ), Math.PI * 0.5 );
vector.applyAxisAngle( new THREE.Vector3( 1, 0, 0 ), Math.PI * 0.5 );
orbBall.position.copy(vector);
"What I'm trying to achieve is the make and object always be in view of the camera in the direction it's facing."
If you are simply trying to ensure that orbBall always faces the camera, and both orbBall and the camera are part of the THREE scene then you can just use lookAt like so:
orbBall.lookAt(camera.position);
if you need to align a particular side of orbBall you can use a null dummy Object3D to do it, by adding the dummy node to the scene and orbBall to the dummy, in something like this:
dummy = new THREE.Object3D();
scene.add(camera); // "camera" is defined however
scene.add(dummy);
dummy.add(orbBall); // "orbBall" is created as usual
// ... not to align dummy+orbBall
dummy.lookAt(camera.position);
// ...and you can just rotate orbBall around "inside" dummy, just once,
/// to face however you like
After digging around this issue I realise that it's quite advanced mathematics.
Check out this lecture about quaternions if you're interested:
https://www.youtube.com/watch?v=mHVwd8gYLnI
So I used #bjorke suggestins and used a dummy object, and it works well.
var container, scene, camera, renderer, controls, stats;
var SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight;
var VIEW_ANGLE = 45, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 20000;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR);
camera.position.set(100, 100, 400);
camera.lookAt(scene.position);
scene.add(camera);
renderer = new THREE.WebGLRenderer( {antialias:true} );
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
container = document.getElementById( 'ThreeJS' );
container.appendChild( renderer.domElement );
var light = new THREE.DirectionalLight(0xffffff); //new THREE.PointLight(0xffffff);
light.position.set( 30, 80, -15 );
scene.add(light);
var boxGeo = new THREE.BoxGeometry( 10, 10, 10);
var axes = new THREE.AxisHelper(1000);
scene.add( axes );
var cameraObj = new THREE.Mesh(boxGeo, new THREE.MeshLambertMaterial( {color: 0x888800}));
scene.add(cameraObj);
var orbSpace = new THREE.Object3D();
scene.add(orbSpace);
var orbBall = new THREE.Mesh(boxGeo, new THREE.MeshLambertMaterial( {color: 0x880088}));
orbBall.position.set(0, 0, cameraObj.position.z + 100);
orbSpace.add(orbBall);
animate();
var camX = 0.3;
var camY = 0;
function animate() {
requestAnimationFrame( animate );
camY += 0.02;
if (camY >= 2) camY = 0;
cameraObj.rotation.x = Math.PI * document.querySelector('#volume').value;
cameraObj.rotation.y = Math.PI * camY;
orbSpace.position.copy(cameraObj.position);
orbSpace.rotation.copy(cameraObj.rotation)
renderer.render( scene, camera );
}
Here's a codepen about how it works:
http://codepen.io/arpo/pen/RrpMJJ
You can update the X angle in the uper right corner

Setting the camera matrix manually in ThreeJS

I'm trying to manually set the matrix of a camera in a simple three.js scene. I've tried calling the matrix.set method in combination with matrixAutoUpdate = false, but whilst the scene renders initially it doesn't change over time as I was hoping. I've also tried setting the matrix with camera.matrix = with the same result. Makes me think I'm missing something about how to get the object to 'take on' the manually set values. I've also tried applyMatrix but that seems to do something else entirely.
Any advice much appreciated - thanks!
Here's a pen of the code in action:
http://codepen.io/heyscam/pen/phflL
And here's just the JS:
var WIDTH = 640;
var HEIGHT = 360;
var VIEW_ANGLE = 31.417;
var ASPECT = WIDTH / HEIGHT;
var NEAR = 0.1;
var FAR = 10000;
var $container = $('#container');
console.log($container);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(WIDTH, HEIGHT);
$container.append(renderer.domElement);
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
VIEW_ANGLE,
ASPECT,
NEAR,
FAR
);
scene.add(camera);
var cube = new THREE.Mesh(
new THREE.CubeGeometry(200, 200, 200)
);
scene.add(cube);
var frame = 0;
animate();
function animate() {
camera.matrixAutoUpdate = false;
camera.matrix.set(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 500 + (frame * 10), 0, 0, 0, 1);
render();
frame++;
requestAnimationFrame(animate);
}
function render() {
renderer.render(scene, camera);
}
After setting the camera matrix, you need to call
camera.updateMatrixWorld( true );
What you are doing is not advisable.
three.js was not designed to be used this way. It is best not to mess with an object matrix directly -- unless you really know what you are doing, and understand the inner-workings of the library.
Instead just set the camera's quaternion (or rotation), position, and scale, and let the library update the matrix.
three.js r.60

Trying to aim the particles to camera while rotating and alert message on intersecting in three js?

I'm trying to create event handler on the particles, with alert message on a sphere, aiming always on the camera.
Something similar to this demo ( and making it to work on IE 9+ )
here is my code..
http://jsfiddle.net/praveenv29/cVnKV/11/
var renderer, projector;
var mouseX, mouseY, stats, container;
var objects = [];
var INTERSECTED;
var camera, scene, renderer, material, mesh, cont;
var w1 = 960;
var h1 = 700;
var halfWidth = w1 / 2;
var halfHeigth = h1 / 2;
function init() {
cont = document.createElement('div');
cont.id = "cont";
document.body.appendChild(cont);
camera = new THREE.PerspectiveCamera(75, w1 / h1, 1, 10000);
camera.position.set(90, 90, -200);
scene = new THREE.Scene();
scene.add(camera);
controls = new THREE.OrbitControls(camera);
controls = new THREE.TrackballControls(camera, cont);
controls.rotateSpeed = 0.8;
controls.zoomSpeed = 1.2;
controls.panSpeed = 2.5;
controls.noZoom = true;
controls.noPan = true;
controls.staticMoving = false;
controls.target.set(0, 0, 0);
controls.keys = [95, 90, 84];
renderer = new THREE.CanvasRenderer();
material = new THREE.MeshBasicMaterial({
color: 0x000000,
wireframe: true
});
renderer.setSize(w1, h1);
cont.appendChild(renderer.domElement);
generateGeometry();
var light = new THREE.PointLight(0xffffff);
light.position.set(10, 0, 0);
scene.add(light);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
controls.update();
renderer.render(scene, camera);
}
function generateGeometry() {
var axis = new THREE.AxisHelper();
scene.add(axis);
for (var i = 0; i < 20; i++) {
var gloom = new THREE.ImageUtils.loadTexture('map_pin.png');
materialr = new THREE.MeshBasicMaterial({
map: gloom,
overdraw: true,
side: THREE.DoubleSide
});
var geometry = new THREE.PlaneGeometry(15, 15, 2, 2);
var cube = new THREE.Mesh(geometry, materialr);
cube.position.x = Math.random() * 2 - 1;
cube.position.y = Math.random() * 2 - 1;
cube.position.z = Math.random() * 2 - 1;
cube.position.normalize();
cube.position.multiplyScalar(125);
cube.rotation.x = cube.position.x / Math.PI; //57.38
cube.rotation.y = 360 / Math.PI * 2;
objects.push(cube);
scene.add(cube);
}
//earth
var texture = THREE.ImageUtils.loadTexture('world.jpg');
var materials = new THREE.MeshBasicMaterial({
map: texture,
overdraw: true
});
var cone = new THREE.SphereGeometry(120, 35, 35);
var coneMesh = new THREE.Mesh(cone, material);
coneMesh.position.y = 0;
coneMesh.rotation.set(0, 0, 0);
scene.add(coneMesh);
}
init();
animate();
It is pretty unclear what you are looking for; your demo link seems unrelated...
Are you trying to make cubes appear camera normal (always facing the camera)? If so, you'll need logic to re-orient them to re-face the camera anytime the user moves the camera view, as I see you are also setting up the TrackballControls, which actually move the camera, not the scene. This means a user can change the camera view of your scene, and items you want facing the camera need to be re-orientated. That re-orientation logic needs to be placed inside your render() function.
BTW, to get an object to always face the camera:
Define it such that when not rotated, it is facing the direction you
want;
Place the object into your scene via any method you want,
including whatever hierarchical rotations or translations you want to use to get them positioned where you want; (Note, they may not be facing
where you want at this point, but that is okay at this step);
Request from three.js that it calculate the local to world space
transformations for your scene. After that, each object's world
transformation matrix contains the concatenated rotations, and
translations that transform each object from local space to world
space.
Go into each object's local-to-world transform matrix and
replace the rotation 3x3 matrix components with the identity
transformation { [1 0 0] [0 1 0] [0 0 1] }. This effectively wipes
out the rotations in world space, making all the objects you do this
to always face the camera.

Categories