I've created some objects in my scene, and set up raycasting/tweening code so that whenever I click on an object, the object animates directly to the position and rotation of the camera.
This is my code for raycasting/tweening the object:
function onDocumentMouseDown( event ) {
event.preventDefault();
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 ) {
new TWEEN.Tween( intersects[ 0 ].object.position ).to( {
x: 0,
y: 0,
z: -100 }, 2000 )
.easing( TWEEN.Easing.Elastic.Out).start();
new TWEEN.Tween( intersects[ 0 ].object.rotation ).to( {
x: 0,
y: 0,
z: 0 }, 2000 )
.easing( TWEEN.Easing.Elastic.Out).start();
object.lookAt.camera;
}
}
However, I was wondering, how can I make the tween animate the camera to the object, rather than the object to the camera? I want to do this because I want the objects to be constantly rotating and moving around the scene, and would like the camera to be able to stay on and track individual objects.
This is all in perspective camera, by the way.
Let's assume you are using OrbitControls.js to control your camera.
What you need to do then is set the controls target by controls.target = new THREE.Vector3(0, 0, -100); to the center of you object(or tween it). You are setting your objects rotation to (0, 0 ,0), so let's say you want to look at your object from top, you set your camera position to (0, 10, -100) - same as you target, just offset in the direction you want to be looking form.
Assuming your objects rotation is not (0, 0 ,0), you need to calculate its forward vector (or any other vector you want to be looking at it from), and put the camera position somewhere along the axis of this vector.
I am trying to do something similar, here is what i have so far but struggling with getting the objects face direction vector
function toObj(obj) {
var lookAtVector = new THREE.Vector3(0, 0, 1);
lookAtVector.applyQuaternion(obj.quaternion);
console.log(lookAtVector);
var rotateTween = new TWEEN.Tween(controls.target)
.to({
x: obj.position.x,
y: obj.position.y,
z: obj.position.z
}, 4000)
.interpolation(TWEEN.Interpolation.CatmullRom)
.easing(TWEEN.Easing.Quintic.InOut)
.start();
var goTween = new TWEEN.Tween(camera.position)
.to({
x: obj.position.x,
y: obj.position.y,
z: obj.position.z + 10
}, 4000)
.interpolation(TWEEN.Interpolation.CatmullRom)
.easing(TWEEN.Easing.Quintic.InOut);
goTween.start();
goTween.onComplete(function() {
console.log('done!');
});
}
you will need to add
controls = new THREE.TrackballControls(camera);
Related
I can't figure out how to properly rotate an object in ThreeJS. The object is a simple box geometry that is rendered from above somewhere on the screen.
Codepen with the full code.
The object is supposed to rotate around it's own Y axis (the vertical axis) to always face the mouse cursor. I can get it to rotate as the cursor moves around the global axis in the middle of the screen, but not when the cursor moves around the object's own local axis.
UPDATE: I got it to work using ray casting. See code further down or in the codepen.
The orthographic camera is set up like this:
camera = new THREE.OrthographicCamera(
window.innerWidth / - 2, // left
window.innerWidth / 2, // right
window.innerHeight / 2, // top
window.innerHeight / - 2, // bottom
0, // near
1000 ); // far
camera.position.set(0, 0, 500)
camera.updateProjectionMatrix()
The object is set up like this:
const geometry = new THREE.BoxGeometry( 10, 10, 10 );
const material = new THREE.MeshBasicMaterial( { color: "grey" } );
const mesh = new THREE.Mesh( geometry, material );
Code for handling rotation at mousemove:
function onMouseMove(event) {
// Get mouse position
let mousePos = new THREE.Vector2();
mousePos.set(
(event.clientX / window.innerWidth) * 2 - 1, // x
-(event.clientY / window.innerHeight) * 2 + 1); // y
// Calculate angle
let angle = Math.atan2(mousePos.y, mousePos.x);
// Add rotation to object
mesh.rotation.set(
0, // x
angle, // y
0) // z
}
I have also tried
mesh.rotateY(angle)
but this only makes the object spinn like a helicopter.
It's obvious the rotation needs to be based on the relationship between the cursor and the local axis rather than the global axis. I just can't figure out how to achieve that.
UPDATE
I have added a codepen at the top of the question.
UPDATE
I got it to work using the following method with ray casting.
let plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
let pointOfIntersection = new THREE.Vector3();
let raycaster = new THREE.Raycaster();
let mousePos = new THREE.Vector2();
mousePos.set(
(event.clientX / window.innerWidth) * 2 - 1, // x
-(event.clientY / window.innerHeight) * 2 + 1))
raycaster.setFromCamera(mousePos, camera);
raycaster.ray.intersectPlane(plane, pointOfIntersection);
mesh.lookAt(pointOfIntersection)
I have a vector like this
{x: 0, y: 0, z: 1}
Then I have another vector that is a normal, a direction, like this
{x: 1, y: 0, z: 0}
How do i rotate the vector based on the direction in the normal so it looks like this?
{x: 1, y: 0, z: 0}
I'm using Three.js
After digging in to this answer I come up with a solution that seems to work
https://github.com/mrdoob/three.js/issues/1486
rotateVectorWithNormal(toRotate: Vector3, normal: Vector3) {
const newVector: Vector3 = new Vector3().copy(toRotate);
// set up direction
let up = new Vector3(0, 1, 0);
let axis: Vector3;
// we want the vector to point in the direction of the face normal
// determine an axis to rotate around
// cross will not work if vec == +up or -up, so there is a special case
if (normal.y == 1 || normal.y == -1) {
axis = new Vector3(1, 0, 0);
} else {
axis = new Vector3().cross(up, normal);
}
// determine the amount to rotate
let radians = Math.acos(normal.dot(up));
const quat = new Quaternion().setFromAxisAngle(axis, radians);
newVector.applyQuaternion(quat);
return newVector;
}
Code in Typescript
While the auto-answer is correct, here some general points about such a rotation:
If only two vectors are given, namely a and b, there are infinite rotations transforming a into b. The answer above takes the shortest rotation but requires to determine the axis of rotation via a cross product. A second solution is to take the bisector as rotation axis and rotate by Pi. Here you would normalize to a_n and b_n and rotate around (a_n + b_n).
The difference between the rotations would only affect non-rotational symmetric object.
If all vectors are normalized already it should be as simple as
var a = new THREE.Vector3( 0, 0, 1 );
var b = new THREE.Vector3( 1, 0, 0 );
var c = new THREE.Vector3( x, y, z );
var quaternion = new THREE.Quaternion();
quaternion.setFromAxisAngle( a + b, Math.PI );
c.applyQuaternion( quaternion );
If c==a then c is rotated onto b and if c==b then c is rotated onto a.
Is it possible to rotate an Object3D object about its end? I found my object's center with const boundingBox = new THREE.Box3().setFromObject(this.object) to determine the center. Would rotating at a point mean translating the object to one point, rotate, then translate back?
Is it possible to rotate an Object3D object about its end?
Yes. By default, Object3D rotates about its center, but it's possible to change rotation point by "wrapping" object in parent (Object3D) and setting position for parent (this will be also a rotation point):
function changeRotationPoint(obj, scene, position) {
// create parent and add it to scene
const parent = new THREE.Object3D();
parent.add(obj);
if (obj.parent) {
obj.parent.add(parent);
} else {
// add to THREE scene if obj doesn't have a
// parent
scene.add(parent);
}
// position for rotation,
// for example (put your data): { x: 0.1, y: 0.1, z: 0 }
parent.position.set(position.x, position.y, position.z);
// correct position of obj, so only rotation point
// will be changed
const x = obj.position.x - position.x,
const y = obj.position.y - position.y,
const z = obj.position.z - position.z
obj.position.set(x, y, z);
}
// call function
changeRotationPoint(this.object, scene, { x: 0.1, y: 0.1, z: 0 });
// rotate this.object
this.object.parent.rotation.set(0.1, 0.2, 0.3);
I'm currently trying to tween-rotate a cube in 3D and thanks to this post (How to rotate a object on axis world three.js?) the rotation without tweening works without any problems. So currently I'm trying to transfer the rotation done by setFromRotationMatrix to something I can use as end rotation for my tween.
EDIT:
Here is what I have at the moment:
// function for rotation dice
function moveCube() {
// reset parent object rotation
pivot.rotation.set( 0, 0, 0 );
pivot.updateMatrixWorld();
// attach dice to pivot object
THREE.SceneUtils.attach( dice, scene, pivot );
// set variables for rotation direction
var rotateZ = -1;
var rotateX = -1;
if (targetRotationX < 0) {
rotateZ = 1;
} else if (targetRotationY < 0) {
rotateX = 1;
}
// check what drag direction was higher
if (Math.abs(targetRotationX) > Math.abs(targetRotationY)) {
// rotation
var newPosRotate = {z: rotateZ * (Math.PI / 2)};
new TWEEN.Tween(pivot.rotation)
.to(newPosRotate, 2000)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.start();
//rotateAroundWorldAxis(dice, new THREE.Vector3(0, 0, rotateZ), Math.PI / 2);
} else {
// rotation
var newPosRotate = {x: -rotateX * (Math.PI / 2)};
new TWEEN.Tween(pivot.rotation)
.to(newPosRotate, 2000)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.start();
//rotateAroundWorldAxis(dice, new THREE.Vector3(-rotateX, 0, 0), Math.PI / 2);
}
// detach dice from parent object
THREE.SceneUtils.detach( dice, pivot, scene );
}
Thanks to WestLangley I think I'm finally close to a solution that is easy to do and will serve my purpose. When initializing the pivot object I set it to the exact same position as the dice, so the rotation will still be around the center of the dice.
var loader = new THREE.JSONLoader();
loader.load(
'models/dice.json',
function ( geometry, materials ) {
material = new THREE.MeshFaceMaterial( materials );
dice = new THREE.Mesh( geometry, material );
dice.scale.set(1.95, 1.95, 1.95);
dice.position.set(2.88, 0.98, 0.96);
scene.add( dice );
pivot = new THREE.Object3D();
pivot.rotation.set( 0, 0, 0 );
pivot.position.set(dice.position.x, dice.position.y, dice.position.z);
scene.add( pivot );
}
);
The solution I have atm (upper snippet) does not attach the dice to the pivot object as parent. I'm probably overlooking something very basic ...
EDIT END
As I thought it was a really simple thing I had to do, to get it working:
I only needed to move the detachment of the child object (the dice) to the beginning of the function, instead of having it at the end of it and it works the charm.
Here's the working code:
// function for rotating dice
function moveCube() {
// detach dice from parent object first or attaching child object won't work as expected
THREE.SceneUtils.detach( dice, pivot, scene );
// reset parent object rotation
pivot.rotation.set( 0, 0, 0 );
pivot.updateMatrixWorld();
// attach dice to pivot object
THREE.SceneUtils.attach( dice, scene, pivot );
// set variables for rotation direction
var rotateZ = -1;
var rotateX = -1;
if (targetRotationX < 0) {
rotateZ = 1;
} else if (targetRotationY < 0) {
rotateX = 1;
}
// check what drag direction was higher
if (Math.abs(targetRotationX) > Math.abs(targetRotationY)) {
// rotation
var newPosRotate = {z: rotateZ * (Math.PI / 2)};
new TWEEN.Tween(pivot.rotation)
.to(newPosRotate, 2000)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.start();
} else {
// rotation
var newPosRotate = {x: -rotateX * (Math.PI / 2)};
new TWEEN.Tween(pivot.rotation)
.to(newPosRotate, 2000)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.start();
}
}
Thanks a lot for helping!
I'm trying to place a cube relative to the camera, rather than relative to the scene. The thing is, to place it in the scene (which I have to do make it show), I have to know the scene coordinates that correspond to the cubes camera space coordinates. I found this function "projectionMatrixInverse" in THREE.Camera. It has a nice function called "multiplyVector3" which I hoped would enable me to transform a vector (1,1,1) back to scene space like this:
var camera, myvec, multvec; // (and others)
camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, - 2000, 1000 );
camera.position.x = 200;
camera.position.y = 100;
camera.position.z = 200;
myvec = new THREE.Vector3(1,1,1);
console.log("myvec: ", myvec);
multvec = camera.projectionMatrixInverse.multiplyVector3(THREE.Vector3(1,1,1));
console.log("multvec: ", multvec);
the thing is, on the console i get:
myvec: Object { x=1, y=1, z=1}
TypeError: v is undefined
var vx = v.x, vy = v.y, vz = v.z;
multiplyVector3 simply doesn't accept my myvec, or says it's undefined, even though the console says it's an object. I don't get it.
The camera is located at the origin of it's coordinate system, and looks down it's negative-Z axis. A point directly in front of the camera has camera coordinates of the form ( 0, 0, z ), where z is a negative number.
You convert a point p
p = new THREE.Vector3(); // create once and reuse if you can
p.set( x, y, z );
from camera coordinates to world coordinates like so:
p.applyMatrix4( camera.matrixWorld );
camera.matrixWorld is by default updated every frame, but if need be, you can update it yourself by calling camera.updateMatrixWorld();
three.js r.95
This may also be what you're after:
scene.add( camera );
brick.position.set( 0, 0, -1 );
camera.add( brick );