Tween camera target in three.js - three.js

I have this code that works well:
function onMouseMove( event ) {
window.onmousedown = function() {
var canvasPosition = renderer.domElement.getBoundingClientRect();
var mouseX = event.clientX - canvasPosition.left;
var mouseY = event.clientY - canvasPosition.top;
var mouseVector = new THREE.Vector3 (
2 * (mouseX / window.innerWidth) - 1,
1 - 2 * (mouseY / window.innerHeight), 1);
mouseVector.unproject( camera );
var dir = mouseVector.sub( camera.position ).normalize();
var distance = - camera.position.z / dir.z;
var pos = camera.position.clone().add( dir.multiplyScalar( distance ) );
camera.getWorldDirection();
camera.lookAt( pos );
// camera.updateMatrixWorld(true);
console.log(mouseVector);
console.log(mouseX);
console.log(mouseY);
// render();
}
}
But I would like to smooth the movement. So I found the following code from the tween example, but not sure how to use it. In the above code, I get current camera lookat from one place, one format, and put the new camera look at in camera.lookat in a different format - neither of which seem to be standard x,y,z.
In the below code, the tween would have me change an properties (x,y,z) on a single item. which the unprojecting and normalizing of the camera do not accommodate:
new TWEEN.Tween( intersects[ 0 ].object.position )
.to( {
x: Math.random() * 800 - 400,
y: Math.random() * 800 - 400,
z: Math.random() * 800 - 400
}, 2000 )
.easing( TWEEN.Easing.Elastic.Out)
.start();
If there is a breakdown or something I can read, or actually work out problems to understand, I'd be grateful. I've read camera tutorials and matrix tutorials over and over for years, but my brain just can't comprehend it.
I've been digging around here quite a bit, but nothing addresses a camera tween - at least for a valid version of threejs
Thank you!

I recommend you get acquainted with linear interpolation, or more commonly known as "lerp". The THREE.Vector3 class has a lerp function that you could use to interpolate between a starting point and an ending point:
var camPos = new THREE.Vector3(0, 0, 0); // Holds current camera position
var targetPos = new THREE.Vector3(10, 10, -10);// Target position
var origin = new THREE.Vector3(0, 0, 0); // Optional origin
function animate(){
// Interpolate camPos toward targetPos
camPos.lerp(targetPos, 0.05);
// Apply new camPos to your camera
camera.position.copy(camPos);
// (Optional) have camera look at the origin after it's been moved
camera.lookAt(origin);
// render();
}
In the above example, your animate() function is called once per frame, and the camera will travel 5% towards targetPos per frame.
If you change targetPos, the camera will animate towards its new target value.
I recommend you first get acquainted with lerping before you start bringing in third-party libraries like TWEEN.js or others.

just for smoothing the movement, this might already help you:
// keep this outside of the event-handler
var lookAtPosition = new THREE.Vector3();
var lookAtTween = new TWEEN.Tween(lookAtPosition);
// as lookAt is not a property we can assign to we need to
// call it every time the tween was updated:
lookAtTween.onUpdate(function() {
camera.lookAt(lookAtPosition);
});
window.onmousedown = function() {
// do your thing to compute pos
// instead of `camera.lookAt(pos)`, do this:
lookAtTween
.stop() // just in case it's still animating
.to(pos, 500) // set destination and duration
.start(); // start the tween
};

Related

Three.js: Rotate object with lookAt() while located at the current lookAt() position

I'm trying to implement a simple turn-around-and-move feature with Three.js. On mouse click, the object is supposed to first turn around and then move to the clicked location.
Codepen
The rotation is achieved with raycasting and lookAt(). It works by itself and it always works on the first click. If you remove the translation, it works continuously. The issue occurs when rotation and translation are implemented together. If you click a second time, after the object has moved to the previous clicked location, it doesn't rotate as expected. Depending on the mouse location it can flip to the other side without rotating at all.
Clarification: When you click the first time, notice how the object slowly and steadily turns around to face that direction? But the second time, after the object has moved, the rotation is quicker and/or flimsier or it simply flips over and there is no rotation at all. It depends on where you click in relation to the object.
I believe the issue stems from trying to implement lookAt while being located at the current lookAt location? If I stop the translation half way, the next rotation will work better. But of course I need it to go all the way.
I'm somewhat lost on how to proceed with this issue. Any help would be appreciated.
/*** Setup scene ***/
let width = 800
let height = 600
let scene
let renderer
let worldAxis
let box
let angle
let boxAxes
scene = new THREE.Scene()
worldAxis = new THREE.AxesHelper(200);
scene.add(worldAxis);
// Setup renderer
renderer = new THREE.WebGLRenderer({alpha: true, antialias: true})
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
document.body.appendChild(renderer.domElement)
// Setup camera
const camera = new THREE.OrthographicCamera(
width / - 2, // left
width / 2, // right
height / 2, // top
height / - 2, // bottom
0, // near
1000 ); // far
camera.position.set(0, 0, 500)
camera.updateProjectionMatrix()
// Setup box
let geometry = new THREE.BoxGeometry( 15, 15, 15 );
let material = new THREE.MeshBasicMaterial( { color: "grey" } );
box = new THREE.Mesh( geometry, material );
box.position.set(100, 150, 0)
box.lookAt(getPointOfIntersection(new THREE.Vector2(0, 0)))
addAngle()
boxAxes = new THREE.AxesHelper(50);
box.add(boxAxes)
scene.add(box)
renderer.render(scene, camera);
/*** Setup animation ***/
let animate = false
let currentlyObservedPoint = new THREE.Vector2();
let rotationIncrement = {}
let translationIncrement = {}
let frameCount = 0
document.addEventListener('click', (event) => {
let mousePosForRotate = getMousePos(event.clientX, event.clientY)
rotationIncrement.x = (mousePosForRotate.x - currentlyObservedPoint.x)/100
rotationIncrement.y = (mousePosForRotate.y - currentlyObservedPoint.y)/100
let mousePosForTranslate = getMousePosForTranslate(event)
translationIncrement.x = (mousePosForTranslate.x - box.position.x)/100
translationIncrement.y = (mousePosForTranslate.y - box.position.y)/100
animate = true
})
function animationLoop() {
if (animate === true) {
if (frameCount < 100) {
rotate()
} else if (frameCount < 200) {
translate()
} else {
animate = false
frameCount = 0
}
frameCount++
renderer.render(scene, camera)
}
requestAnimationFrame(animationLoop)
}
function rotate() {
currentlyObservedPoint.x += rotationIncrement.x
currentlyObservedPoint.y += rotationIncrement.y
let pointOfIntersection = getPointOfIntersection(currentlyObservedPoint)
box.lookAt(pointOfIntersection)
addAngle()
}
function translate() {
box.position.x += translationIncrement.x
box.position.y += translationIncrement.y
}
function getMousePos(x, y) {
let mousePos = new THREE.Vector3(
(x / width) * 2 - 1,
- (y / height) * 2 + 1,
0)
return mousePos
}
function getMousePosForTranslate(event) {
let rect = event.target.getBoundingClientRect();
let mousePos = { x: event.clientX - rect.top, y: event.clientY - rect.left }
let vec = getMousePos(mousePos.x, mousePos.y)
vec.unproject(camera);
vec.sub(camera.position).normalize();
let distance = - camera.position.z / vec.z;
let pos = new THREE.Vector3(0, 0, 0);
pos.copy(camera.position).add(vec.multiplyScalar(distance));
return pos
}
function getPointOfIntersection(mousePos) {
let plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
let pointOfIntersection = new THREE.Vector3()
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mousePos, camera)
raycaster.ray.intersectPlane(plane, pointOfIntersection)
return pointOfIntersection
}
function addAngle() {
let angle = box.rotation.x - 32
box.rotation.x = angle
}
animationLoop()
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/105/three.min.js'></script>

three js drag and drop Uncaught TypeError

I have been trying to implement the drag and drop functionality found here...
http://www.smartjava.org/tjscb/07-animations-physics/07.08-drag-n-drop-object-around-scene.html
Whenever I customise it slightly and use it in my project I get the following..
"Uncaught TypeError: Cannot read property 'point' of undefined"
whenever I try to drag a cube. The rotation isn't occurring so it must be recognising that I'm trying to drag an object and it relates to this line of code..
"selectedObject.position.copy(intersects[0].point.sub(offset))"
I assumed since I am new to all of this that I had messed up, so I copied all of the code from the link above into a new page (so should be identical) and ran it and I get the same thing (everything else works good)
Im probably missing something really stupid, I have searched for this and looked at other examples on how to achieve this, but since I was working my way through a book which explained everything I thought I would stick with this, and also it would be a good learning experience to figure out why its not working. If anyone could point me in the right direction I would appreciate it
<!DOCTYPE html>
<html>
<head>
<title>07.08 - Drag and drop object around scene</title>
<script type="text/javascript" src="js/threejs/three.min.js"></script>
<script type="text/javascript" src ="js/threejs/OrbitControls.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
<script>
// global variables
var renderer;
var scene;
var camera;
var cube;
var control;
var orbit;
// used for drag and drop
var plane;
var selectedObject;
var offset = new THREE.Vector3();
var objects = [];
// based on http://mrdoob.github.io/three.js/examples/webgl_interactive_draggablecubes.html
function init() {
// create a scene, that will hold all our elements such as objects, cameras and lights.
scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render, sets the background color and the size
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xffffff, 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
plane = new THREE.Mesh(new THREE.PlaneGeometry(2000, 2000, 18, 18), new THREE.MeshBasicMaterial({
color: 0x00ff00,
opacity: 0.25,
transparent: true
}));
plane.visible = false;
scene.add(plane);
var dirLight = new THREE.DirectionalLight();
dirLight.position.set(25, 23, 15);
scene.add(dirLight);
var dirLight2 = new THREE.DirectionalLight();
dirLight2.position.set(-25, 23, 15);
scene.add(dirLight2);
for (var i = 0; i < 200; i++) {
// create a cube and add to scene
var cubeGeometry = new THREE.BoxGeometry(2, 2, 2);
var cubeMaterial = new THREE.MeshLambertMaterial({color: Math.random() * 0xffffff});
cubeMaterial.transparent = true;
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
objects.push(cube);
cube.scale.x = Math.random() + 0.5 * 2;
cube.scale.y = Math.random() + 0.5 * 2;
cube.scale.z = Math.random() + 0.5 * 2;
cube.position.x = Math.random() * 50 - 25;
cube.position.y = Math.random() * 50 - 25;
cube.position.z = Math.random() * 50 - 25;
cube.rotation.x = Math.random() * Math.PI * 2;
cube.rotation.y = Math.random() * Math.PI * 2;
cube.rotation.z = Math.random() * Math.PI * 2;
scene.add(cube);
}
// position and point the camera to the center of the scene
camera.position.x = 35;
camera.position.y = 35;
camera.position.z = 53;
camera.lookAt(scene.position);
// add some controls so we can rotate
orbit = new THREE.OrbitControls(camera);
// add the output of the renderer to the html element
document.body.appendChild(renderer.domElement);
// call the render function
render();
}
function render() {
renderer.render(scene, camera);
orbit.update();
requestAnimationFrame(render);
}
document.onmousemove = function (event) {
// make sure we don't access anything else
event.preventDefault();
// get the mouse positions
var mouse_x = ( event.clientX / window.innerWidth ) * 2 - 1;
var mouse_y = -( event.clientY / window.innerHeight ) * 2 + 1;
// get the 3D position and create a raycaster
var vector = new THREE.Vector3(mouse_x, mouse_y, 0.5);
vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position,
vector.sub(camera.position).normalize());
// first check if we've already selected an object by clicking
if (selectedObject) {
// check the position where the plane is intersected
var intersects = raycaster.intersectObject(plane);
// reposition the selectedobject based on the intersection with the plane
selectedObject.position.copy(intersects[0].point.sub(offset));
} else {
// if we haven't selected an object, we check if we might need
// to reposition our plane. We need to do this here, since
// we need to have this position before the onmousedown
// to calculate the offset.
var intersects = raycaster.intersectObjects(objects);
if (intersects.length > 0) {
// now reposition the plane to the selected objects position
plane.position.copy(intersects[0].object.position);
// and align with the camera.
plane.lookAt(camera.position);
}
}
};
document.onmousedown = function (event) {
// get the mouse positions
var mouse_x = ( event.clientX / window.innerWidth ) * 2 - 1;
var mouse_y = -( event.clientY / window.innerHeight ) * 2 + 1;
// use the projector to check for intersections. First thing to do is unproject
// the vector.
var vector = new THREE.Vector3(mouse_x, mouse_y, 0.5);
// we do this by using the unproject function which converts the 2D mouse
// position to a 3D vector.
vector.unproject(camera);
// now we cast a ray using this vector and see what is hit.
var raycaster = new THREE.Raycaster(camera.position,
vector.sub(camera.position).normalize());
// intersects contains an array of objects that might have been hit
var intersects = raycaster.intersectObjects(objects);
if (intersects.length > 0) {
orbit.enabled = false;
// the first one is the object we'll be moving around
selectedObject = intersects[0].object;
// and calculate the offset
var intersects = raycaster.intersectObject(plane);
offset.copy(intersects[0].point).sub(plane.position);
}
};
document.onmouseup = function (event) {
orbit.enabled = true;
selectedObject = null;
}
// calls the init function when the window is done loading.
window.onload = init;
</script>
</head>
<body>
</body>
</html>
"Uncaught TypeError: Cannot read property 'point' of undefined"
"selectedObject.position.copy(intersects[0].point.sub(offset))"
This means, intersects[0] is undefined which means the array intersects has no element (length = 0). You are using raycasting and it isn't working properly.
You should share your modified code so that we can check what is going wrong in your raycasting.
Update: I think your three.js version is greater than 71 while three.js version of this website is 71 or less. In the 72th version, there is an update in the raycaster -
Ignore invisible objects. (#mrdoob, #tschw)
So, the problem is here -
var intersects = raycaster.intersectObject(plane);
Since the plane is invisible, the intersectObject is returning empty array.
Workaround: I found a workaround. You can remove the following line -
plane.visible = false;
You can hide the material of the plane instead in the following way -
plane = new THREE.Mesh(new THREE.PlaneGeometry(2000, 2000, 18, 18), new THREE.MeshBasicMaterial({
color: 0xffff00,
opacity: 0.50,
transparent: true,
visible: false
}));
In this way, the raycaster will work properly and the plane will be invisible as well.

How to properly tween mesh using quaternions?

I have cobbled together a tween function (with enormous help from SO) to tween a mesh...and it's working fine.
My question is that - I feel that the way I am doing it is inefficient and I'm am looking for a better solution.
For example I am cloning my mesh each time I call the function...and that is so I can call rotateOnAxis(). I know what I want is a target quaternion that is my mesh rotation incremented on the axis...but I just haven't been able to find a better way to get it.
As usual any help much appreciated.
PS. really loving threejs!!!
function tweenRot(obj, axis, angle){
var actp = obj.clone(); // seems wasteful
actp.rotateOnAxis( axis, angle );
var targq = actp.quaternion;
var qm = new THREE.Quaternion();
var curQuaternion = obj.quaternion;
var tween = new TWEEN.Tween({t:0}).to({t:1}, 500)
.easing( TWEEN.Easing.Sinusoidal.InOut )
.onUpdate(function(){
THREE.Quaternion.slerp(curQuaternion, targq, qm, this.t);
qm.normalize();
obj.rotation.setFromQuaternion(qm)
});
tween.start();
}
You want to tween an object's rotation by specifying an axis of rotation and an angle. You can to that like so:
var tweenRotateOnAxis = function() {
// axis is assumed to be normalized
// angle is in radians
var qStart = new THREE.Quaternion();
var o = new THREE.Object3D();
return function tweenRotateOnAxis( object, axis, angle ) {
var qEnd, time = { t: 0 };
// start quaternion
qStart.copy( object.quaternion );
// end quaternion
o.quaternion.copy( qStart );
o.rotateOnAxis( axis, angle );
qEnd = o.quaternion;
// tween
new TWEEN.Tween( time )
.to( { t : 1 }, 1000 )
.easing( TWEEN.Easing.Linear.EaseNone )
.onUpdate( function() {
THREE.Quaternion.slerp( qStart, qEnd, object.quaternion, time.t );
} )
.onComplete( function() {
object.quaternion.copy( qEnd ); // to be exact
} )
.start();
};
}();
This should be reasonably efficient, even if you call it repeatedly. The rotation will be in object space.
Don't forget to call TWEEN.update() in your render loop.
three.js r.75

ThreeJS - how to pick just one type of objects?

I'm new to ThreeJS and I have an issue with picking objects by raycasting. I have created some spheres and some lines but only want to change the spheres on mouseover. I think I need to add some condition in the raycast code but I have no idea what...
Here's my code, hope anyone can help:
This creates the objects:
var numSpheres = 10;
var angRand = [numSpheres];
var spread = 10;
var radius = windowY/5;
var radiusControl = 20;
//sphere
var sphereGeometry = new THREE.SphereGeometry(0.35, 100, 100);
//line
var lineGeometry = new THREE.Geometry();
var lineMaterial = new THREE.LineBasicMaterial({
color: 0xCCCCCC
});
//create dynamically
for (var i = 0; i < numSpheres; i++) {
var sphereMaterial = new THREE.MeshBasicMaterial({color: 0x334455});
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
var line = new THREE.Line(lineGeometry, lineMaterial);
angRand[i] = Math.floor((Math.random() * 360) + 1);//random angle for each sphere/line
var radiusIncr = spread * (angRand[i]+200)/180;
var xPos = Math.cos((360/numSpheres * (i) + angRand[i]/2 )) * (radius - radiusIncr);
var yPos = Math.sin((360/numSpheres * (i) + angRand[i]/2 )) * (radius - radiusIncr);
var offsetY = Math.floor((Math.random()*5)+1);
sphere.position.x = xPos/radiusControl;
sphere.position.y = yPos/radiusControl + offsetY;
lineGeometry.vertices.push(
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(sphere.position.x, sphere.position.y, 0)
);
scene.add(sphere);
scene.add(line);
}
And this is my raycast:
var mouse = {
x: 0,
y: 0
},
INTERSECTED;
window.addEventListener('mousemove', onMouseMove, false);
window.requestAnimationFrame(render);
function onMouseMove(event) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
//event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
//console.log(mouse.x + " | " + mouse.y);
}
function mousePos() {
// find intersections
// create a Ray with origin at the mouse position
// and direction into the scene (camera direction)
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
vector.unproject(camera);
var ray = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
ray.linePrecision = 1;
// create an array containing all objects in the scene with which the ray intersects
var intersects = ray.intersectObjects(scene.children, true);
//console.log(intersects.length);
// INTERSECTED = the object in the scene currently closest to the camera
// and intersected by the Ray projected from the mouse position
// if there is one (or more) intersections
if (intersects.length > 0) {
// if the closest object intersected is not the currently stored intersection object
if (intersects[0].object != INTERSECTED) {
// restore previous intersection object (if it exists) to its original color
if (INTERSECTED)
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
// store reference to closest object as current intersection object
INTERSECTED = intersects[0].object;
// store color of closest object (for later restoration)
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
// set a new color for closest object
INTERSECTED.material.color.setHex(0xEE7F00);
//INTERSECTED.radius.set( 1, 2, 2 );
}
} else // there are no intersections
{
// restore previous intersection object (if it exists) to its original color
if (INTERSECTED)
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
//INTERSECTED.scale.set( 1, 1, 1 );
// remove previous intersection object reference
// by setting current intersection object to "nothing"
INTERSECTED = null;
}
}
The raycast returns an intersect array of objects which itself contains information about what the ray hit.
Since you only have spheres and lines you can branch on the geometry type intersects[0].object.geometry.type which would be either 'LineGeometry' or 'SphereGeometry'.
Edit: Obligatory jsfiddle, see console for hit output.
http://jsfiddle.net/z43hjqm9/1/
To simplify working with the mouse, you can use the class EventsControls. Try to make through this example.
<script src="js/controls/EventsControls.js"></script>
EventsControls = new EventsControls( camera, renderer.domElement );
EventsControls.attachEvent('mouseOver', function() {
this.container.style.cursor = 'pointer';
this.mouseOvered.material = selMaterial;
...
});
EventsControls.attachEvent('mouseOut', function() {
this.container.style.cursor = 'auto';
this.mouseOvered.material = autoMaterial;
...
});
//
function render() {
EventsControls.update();
controls.update();
renderer.render(scene, camera);
}
In your code,
var intersects = ray.intersectObjects(scene.children, true);
the first parameter to the call is an object that will be evaluated to see if it, or any of its descendants (recursive is true) intersect the ray.
So, simply create an object target and add the spheres to it (but not the lines).
This will make your call also more effective
1.use different arrays to place different objects
a.for all objectType1,after scene.add(objectType1)-> do array1.push(objectType1)
b.for all objectType 2,after scene.add(objectType2)-> do array2.push(objectType2)
now whichever type of objects you want to interact, pass that array in intersect as-
var intersects = raycaster.intersectObjects( arrayType1,true);
now only the arrayType1 objects will interact.

How to get a point coordinate on a SkinnedMaterial for the human demo of three.js?

For the human demo http://threejs.org/examples/#webgl_morphtargets_human , I wonder how to get a point on the skin of the human. For example when I click some place on the human body, what's the coordinate of that place?
I tried using the raycaster to get that but in vain.The code is like this:
var projector;
init() {
// Others
// ...
projector = new THREE.Projector();
renderer.domElement.addEventListener('mouseup', onMouseUp, false);
}
function onMouseUp(e) {
e.preventDefault();
var vector = new THREE.Vector3(
( event.clientX / window.innerWidth ) * 2 - 1,
- ( event.clientY / window.innerHeight ) * 2 + 1,
0.5
);
projector.unprojectVector( vector, camera );
var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
var intersections = raycaster.intersectObjects( character.root.children );
if (intersections.length > 0) {
debugger;
// ...
}
}
But the intersections is always empty.
Three.js is r67
Thanks in advance.
I'm new to three.js, and i've also tried to draw plots on the human. I manage to do it, but it's not on the "visible" body. In fact, you should first use the intersect method with the recursive arg :
var intersections = raycaster.intersectObjects( scene.children, true );
Thus, you 'll be able to interact with the objects composing the body, but they are not positioned under the "skin". It seems to be that they have been "moved", because you can interact with them if you aim in front of the feet of the body. Unfortunately, I don't know for the moment why, and how to interact with their "visible representation".
Well, finally I find that it's just because the human animation. It works if I comment the animation out.

Resources