camera to face selected plane three.js - three.js

i have 600 planes being added to a scene in random x,y,z positions, each plane is clickable. when clicked i animate to the selected plane. All works but i am struggling for the camera to face the selected plane / or make sure the plane is centred in the view. I have tried getting the direction vector of the clicked item but not sure how make sure the camera is always a set distance away. here is the function and below a link to the test. Any ideas? Many thanks
http://adigitalengagement.co.uk/webauth_stickies/plane/
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!');
});
}

I'm sure there can be a better solution, but this one can be a starting point based on this SO answer.
I've changed your toObj() function and added a global variable:
var lookAtVector = new THREE.Vector3(0,0,0);
...
function toObj(obj) {
var normalMatrix = new THREE.Matrix3().getNormalMatrix( obj.matrixWorld );
var worldNormal = new THREE.Vector3(0,0,1).applyMatrix3( normalMatrix ).normalize();
var camPosition = new THREE.Vector3().copy(obj.position).add(worldNormal.multiplyScalar(100));
var rotateTween = new TWEEN.Tween(lookAtVector)
.to({
x: obj.position.x,
y: obj.position.y,
z: obj.position.z
}, 4000)
.easing(TWEEN.Easing.Quadratic.InOut)
.onUpdate(function(){
camera.lookAt(lookAtVector);
})
.onComplete(function(){
lookAtVector.copy(obj.position);
})
.start();
var goTween = new TWEEN.Tween(camera.position)
.to(camPosition, 4000)
.easing(TWEEN.Easing.Quadratic.InOut)
.start();
}
jsfiddle example

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 rotation on specific axis

How can I have an ThreeJS object rotate on an axis that is at angle.
Like from this codepen I made, the left square rotates on its center and moved up when you click on the "rotate up" button.
I want the left cure rotate along the red axis in the image attached and move along on that axis when click on the "Rotate 45 degree" button. I can make up move along the axis, but the rotation is wrong. How can I make the cute rotate along the red axis?
****EDIT:
PS: Sorry I cannot get the snippet working here. Please use the codepen link for the demo.
https://codepen.io/eforblue/pen/MWpYggY
var upTl = gsap.timeline();
var degreeTl = gsap.timeline();
var width = window.innerWidth;
var height = window.innerHeight;
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
var scene = new THREE.Scene();
var cubeGeometry = new THREE.CubeGeometry(100, 100, 100);
var cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
scene.add(cube);
var cube2 = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube2.position.set(200, 200, 0);
cube2.rotation.z = 45;
scene.add(cube2);
var camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 10000);
camera.position.set(0, 500, 3000);
camera.lookAt(cube.position);
scene.add(camera);
var skyboxGeometry = new THREE.CubeGeometry(100, 100, 100);
var skyboxMaterial = new THREE.MeshBasicMaterial({ color: 0x000000, side: THREE.BackSide });
var skybox = new THREE.Mesh(skyboxGeometry, skyboxMaterial);
var pointLight = new THREE.PointLight(0xffffff);
pointLight.position.set(0, 300, 200);
scene.add(pointLight);
var clock = new THREE.Clock();
function render() {
requestAnimationFrame(render);
// cube.rotation.y -= clock.getDelta();
renderer.render(scene, camera);
}
render();
$('.up').on('click', () => {
console.log('click')
upTl.to( cube.rotation, 1, { y: Math.PI * 3 }, 0 )
.to( cube.position, 1, { y: cube.position.y + 400 }, 0 )
})
$('.degree').on('click', () => {
degreeTl.to( cube2.rotation, 1, { y: Math.PI * 3 }, 0 )
.to( cube2.position, 1, { x: cube2.position.x + 300, y:cube2.position.y + 300 }, 0 )
});
canvas {
position: fixed;
top: 0;
left: 0;
}
button {
position:relative;
z-index: 99;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.6.1/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="./main.js"></script>
<button class="up">rotate up</button>
<button class="degree">rotate 45deg</button>
In your case it's easier to use quaternions to rotate around your red axis, you can use the following function and define your rotation axis in the "axis" parameter.
static rotateAroundWorldAxis(mesh, point, axis, angle) {
const q = new Quaternion();
q.setFromAxisAngle(axis, angle);
mesh.applyQuaternion(q);
mesh.position.sub(point);
mesh.position.applyQuaternion(q);
mesh.position.add(point);
return mesh;
}

Three.js place one box upon another

To display rack structure, placing one box upon another. But y Position calculation fails.Currently creates gap between boxes. Please inform how could it be fixed, whether camera or light effect creates a problem. As per rack size, altering y position. Data contain size and starting place.
```
var data = [{"id": 10075,"size": 3,"slotNumber": 1},{"id": 10174,"size": 7,"slotNumber": 4}];
var rackListGroup;
init();
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x999999 );
var light = new THREE.AmbientLight( 0xffffff );
light.position.set( 0.5, 1.0, 0.5 ).normalize();
scene.add( light );
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.fromArray([0, 0, 140]);
scene.add( camera );
rackListGroup = new THREE.Mesh();
rackListGroup.name = "Rack List"
var i;
for (i = 0; i < 1; i++) {
rackListGroup.add(drawRack(10, i))
}
scene.add(rackListGroup);
render();
}
function drawRack(size, rackNo){
var rackGroup = new THREE.Group();
rackGroup.name = "rack "+rackNo;
var yPosition = -42;
var xPosition = -20 + parseInt(rackNo)*40;
var slot = 1, counter = 0;
var slotWidth = 5;
while(slot <= parseInt(size)){
var slotSize = data[counter].size;
slot = slot + slotSize;
yPosition = yPosition + slotSize* slotWidth;
var geometry = new THREE.BoxGeometry( 30, slotWidth*slotSize, 5 );
var material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
var shape = new THREE.Mesh( geometry, material );
shape.name = data[counter].name;
shape.position.set(xPosition, yPosition, 0);
rackGroup.add(shape);
var boxGeometry = new THREE.BoxBufferGeometry( 30, slotWidth*slotSize, 5, 1, 1, 1 );
var boxMaterial = new THREE.MeshBasicMaterial( { wireframe:true } );
var box = new THREE.Mesh( boxGeometry, boxMaterial );
box.name = data[counter].name;
box.position.set(xPosition, yPosition, 0);
rackGroup.add(box);
if(counter+1 < data.length){
counter++;
}
}
return rackGroup;
}
```
I've tried your code and I see a misunderstanding between the objects position and the objects height to be able to stack them on top of each other.
You use one variable for yPosition and you need 2 variables, the reason is that geometries are positioned based on its axes center, so it means a 15 units height mesh positioned at y=0 it will place indeed at -7.5 units below the y=0 position and the upper side of the geometry will be at 7.5. So next slot to stack will be needed to place (conceptually) at y = 7.5 + (topSlotHeight / 2).
That's why your calculation of the next slot to stack y position is wrong. I have created this fiddle with the solution, and I have added a gridHelper at y=0 for your reference and the OrbitControls to be able to check it better. Now it works perfectly doing like this, storing the accumulated base position of the previous slot in yBaseHeight and the yPosition for the slot on top:
var slotHeight = (slotSize * slotWidth);
yPosition = yBaseHeight + (slotHeight / 2);
yBaseHeight = yBaseHeight + slotHeight;
PD.- I saw you start placing objects at y=-42, I started from y=0 to show better the effect.

Camera transition with rotation

I got a question about camera object. I'm trying to make a transition between cameras and I got it partially working. Camera is moving well but it does not rotate correct. I suppose my code does not calculate lookAtVector right but I cannot find information how to do it correct.
Here is the code I'm using:
var new_position = new_camera.position.clone();
var new_rotation = new_camera.rotation.clone();
var new_quaternion = new_camera.quaternion.clone();
camera.rotation.clone(new_rotation);
camera.quaternion.clone(new_quaternion);
newlookAtVector = new THREE.Vector3(0, 0, -1);
newlookAtVector.applyEuler(new_rotation, new_camera.eulerOrder);
new TWEEN.Tween( camera.position ).to( {
x: new_position.x,
y: new_position.y,
z: new_position.z}, 600 ).onUpdate(function () {
camera.lookAt(newlookAtVector);
}).onComplete(function () {
camera.lookAt(newlookAtVector);
}).easing( TWEEN.Easing.Sinusoidal.Out).start();
Thank you!

Tween camera to coordinates but preserve camera angle in three.js

I've seen a couple of questions on stack overflow about this but none resolve my issue.
For my first project in Three.Js I'm trying to create a tree and have the camera zoom in on specific nodes. I have the following code:
var selectedObject = fourthParent;
camera.target = selectedObject;
var tween = new TWEEN.Tween(camera.position).to({
x: selectedObject.position.x,
y: selectedObject.position.y,
z: 5
},1000).easing(TWEEN.Easing.Linear.None).onUpdate(function () {
camera.lookAt(camera.target);
}).onComplete(function () {
camera.lookAt(selectedObject.position);
}).start();
var tween = new TWEEN.Tween(camera.target).to({
x: selectedObject.position.x,
y: selectedObject.position.y,
z: 0
}).easing(TWEEN.Easing.Linear.None).onUpdate(function () {
}).onComplete(function () {
camera.lookAt(selectedObject.position);
}).start();
This moves the camera correctly but right at the end of the animation it changes the camera angle. I'm not changing the angle in my code so I have no idea why it is changing. Can anyone help?
Thanks,
Joe
Here is the working code:
updateControls = false;
var selectedObject = fourthParent;
camera.target = selectedObject;
var tween = new TWEEN.Tween(camera.position).to({
x: selectedObject.position.x,
y: selectedObject.position.y,
z: 20
},1000).easing(TWEEN.Easing.Linear.None).onComplete(function () {
controls.target0.set(selectedObject.position.x, selectedObject.position.y, selectedObject.position.z);
controls.position0.set(selectedObject.position.x, selectedObject.position.y, 20);
controls.reset();
updateControls = true;
}).start();
var tween = new TWEEN.Tween(camera.target).to({
x: selectedObject.position.x,
y: selectedObject.position.y,
z: 20
}).easing(TWEEN.Easing.Linear.None).start();
// Render loop
var render = function () {
requestAnimationFrame(render);
TWEEN.update();
renderer.render(scene, camera);
if (updateControls) {
controls.update();
}
};
By calling camera.lookAt() you're changing the camera angle.
You should just remove every instance of that code.

Resources