three.js animation logic for spinning cuboids - three.js

I'm trying to create a cuboid from 5 cuboids one of top of the other - that rotates 180 CW degrees every two seconds.
It's kinda working but I'm not getting smooth results... I thought a good strategy would be that during the rotating period- the z-position of the top-left and top-right vertices of a single face will be compared- if they are the same then it's time to stop the rotating period.
The problem is that the difference between the vertices is never zero.. it's very close to zero... so I check if it's in a margin of 0.1 and thus I have a problem with the beginning of a rotation that is jammed because sometimes it's less than 0.1. Also sometimes the rotations continue when they should stop because the difference is not less than 0.1.
var spinningPeriod = false, counter = 0, lastTime = 0;
function animate(){
counter++;
var time = (new Date()).getTime();
var timeDiff = time - lastTime;
var angleChange = 0.2 * timeDiff * 2 * Math.PI / 1000;
if (counter%200==0 && counter > 0) {
spinningPeriod = true;
}
if (spinningPeriod) {
var v1 = cubes[0].geometry.vertices[0].clone();
var v2 = cubes[0].geometry.vertices[3].clone();
cubes[0].updateMatrixWorld();
cubes[0].localToWorld(v1);
cubes[0].localToWorld(v2);
if (Math.abs(v1.x - v2.x) < 0.1) {
spinningPeriod = false;
}
for (var ii =0; ii<5; ++ii) {
cubes[ii].rotation.y += angleChange;
}
}
lastTime = time;
renderer.render(scene, camera);
// request new frame
requestAnimationFrame(function(){
animate();
});
}
// renderer
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// camera
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 500;
// scene
var scene = new THREE.Scene();
// cube
var cubes = new Array();
var currentHeight = -150;
for (var ii =0; ii<5; ++ii) {
cubes[ii] = new THREE.Mesh(new THREE.CubeGeometry(400, 50, 20));
currentHeight += 50;
cubes[ii].position.y = currentHeight;
cubes[ii].overdraw = true;
scene.add(cubes[ii]);
}
// start animation
animate();

I agree, looking at the position is probably not the best way to do it. If you're concerned with the angle, then the angle should be your focus. Basically you want to have your test be based on the spin vs a max spin - when you're more than that, stop and adjust as needed (in this case, it was sometimes > 1 with the amount of angleChange, so I just reset it to 1).
Instead of your animate function above, try this one - it looks like it works pretty reliably for me.
var spinningPeriod = false, counter = 0, lastTime = 0;
var spinAngle = 0, spinMax = 1 * Math.PI;
function animate(){
counter++;
var time = (new Date()).getTime(),
timeDiff = time - lastTime,
speed = 0.2 * 2 * Math.PI / 1000,
angleChange = speed * timeDiff;
if (counter%200==0 && counter > 0) {
spinningPeriod = true;
spinAngle = 0;
}
console.log(counter, spinningPeriod, cubes[0].rotation.y / Math.PI);
if (spinningPeriod) {
spinAngle += angleChange;
if (spinAngle > spinMax) {
spinningPeriod = false;
spinAngle = spinMax;
}
for (var ii =0; ii<5; ++ii) {
cubes[ii].rotation.y = spinAngle;
}
}
lastTime = time;
renderer.render(scene, camera);
// request new frame
requestAnimationFrame(function(){
animate();
});
}

Related

Three.js Move an object to front of camera

Hello I'm trying to move an object to front of camera, and when it reached to target position, I want it to stop. but it doesn't work.
function objectToCamera(mX, mY, object)
{
var vector = new THREE.Vector3(mX, mY, 1);
vector.unproject(camera);
vector.sub(object.position);
var dx = object.position.x - camera.position.x;
var dy = object.position.y - camera.position.y;
var dz = object.position.z - camera.position.z;
var distance = Math.sqrt(dx*dx + dy*dy + dz*dz);
if(lastDistance < distance && lastDistance != -1)
keepOut = -1;
lastDistance = distance;
setTimeout(function(){
if( distance > 200 && keepOut == 1)
{
var amount = (1)*(indexForZoom/3);
amount = (amount>15) ? 15 : (1)*(indexForZoom/3);
if(distance - amount < 200)
amount = (distance-200)+1;
indexForZoom++;
object.translateZ(amount);
controls.target.addVectors(controls.target,vector.setLength(amount));
objectToCamera(mX, mY, object)
}
else
{
// stopForZoom = 1;
keepOut = -1;
objectClickHandler(object.name, object);
}
}, 10);
}
I'm checking the distance between camera and object, and if target distance has reached I'm letting it stop, but it doesn't work.
In coordinates, if i'm in positive X coordinates, distance is decreasing, and otherwise, distance is increasing.
I think, in my codes, distance should be decreasing always, but it is not.
Please help. Thanks.
you can use object.position.lerp(target, amount) to move an object toward target. Amount is a value from 0 to 1 with 1 = 100% all the way to target and 0.5 = 50% way to target.
If you want to move at a fixed speed then you can get the distance to the target
distance = object.position.distanceTo(target);
Say you want a max of 0.1 units per interation. then
moveSpeed = 0.1;
distance = object.position.distanceTo(target);
amount = Math.min(moveSpeed, distance) / distance;
object.position.lerp(target, amount)
All that's left is for you to choose a target.
The position in front of the camera is
const distanceFromCamera = 3; // 3 units
const target = new THREE.Vector3(0, 0, -distanceToCamera);
target.applyMatrix4(camera.matrixWorld);
So for example if you move the camera (drag with mouse, use scrollwheel). Note: in the code the speed is adjusted to be frame rate independent.
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 45;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 10, 20);
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(0, 0, 0);
controls.update();
const scene = new THREE.Scene();
scene.background = new THREE.Color('lightblue');
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(0, 10, 0);
light.target.position.set(-5, 0, 0);
scene.add(light);
scene.add(light.target);
}
const gridHelper = new THREE.GridHelper(100, 10);
scene.add(gridHelper);
gridHelper.position.set(0, -5, 0);
const cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(1, 1, 1),
new THREE.MeshPhongMaterial({color: 'red'}),
);
scene.add(cube);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
let then = 0;
function render(now) {
now *= 0.001; // convert to seconds
const deltaTime = now - then;
then = now;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
cube.rotation.x = now;
cube.rotation.y = now * 1.1;
// move cube in front of camera
{
const distanceFromCamera = 3; // 3 units
const target = new THREE.Vector3(0, 0, -distanceFromCamera);
target.applyMatrix4(camera.matrixWorld);
const moveSpeed = 15; // units per second
const distance = cube.position.distanceTo(target);
if (distance > 0) {
const amount = Math.min(moveSpeed * deltaTime, distance) / distance;
cube.position.lerp(target, amount);
cube.material.color.set('green');
} else {
cube.material.color.set('red');
}
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body { margin: 0; }
#c { width: 100vw; height: 100vh; display: block; }
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r112/build/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r112/examples/js/controls/OrbitControls.js"></script>
<canvas id="c"></canvas>
Note, you might want to call camera.updateMatrixWorld() before all that math to make sure the target isn't one frame late.
If the object is in a hierarchy then there's more to do. You can do the math or you can use just attach the object to the scene and then attach it it back to its place in the hierarchy
const parent = object.parent;
// move object to scene without changing it's world orientation
scene.attach(object);
// do stuff above
// move object to parent without changing it's world orientation
parent.attach(object);

ThreeJS - How do I scale down intersected object on "mouseout"

I have n+1 hexshapes in a honeycomb grid. The objects are stacked close together. With this code:
// Get intersected objects, a.k.a objects "hit" by mouse, a.k.a objects that are mouse-overed
const intersects = raycaster.intersectObjects(hexObjects);
// If there is one (or more) intersections
let scaleTween = null;
if (intersects.length > 0) {
// If mouse is not currently over an object
// Set cursor to pointer so that the user can see that the object is clickable
document.body.style.cursor = 'pointer';
// Get the last intersected object, it's most likely that object we are currently hovering
const is = intersects.length > 0 ? intersects.length - 1 : 0;
// Is the object hovered over for the first time?
if (INTERSECTED === null) {
// Save current hovered object
INTERSECTED = intersects[is].object;
// HIGHLIGHT
// Save current color
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
// Set highlight color
INTERSECTED.material.color.setHex(COLOR_HIGHLIGHT);
// SCALE UP
// Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
try {
scaleTween.stop();
} catch (e) {}
// Create tween, save it so we can try to stop it, if needed
scaleTween = scale_tween(
INTERSECTED,
INTERSECTED.scale.clone(),
{
x: 1.5,
y: 1.5
},
100
);
scaleTween.start();
// SET Z-INDEX
INTERSECTED.position.z = 10;
} else {
// If the mouse is over an object
// Do we have a previous hovered item?
if (INTERSECTED !== null) {
// Revert color
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
// SCALE DOWN
// Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
try {
scaleTween.stop();
} catch (e) {}
// Create tween, save it so we can try to stop it, if needed
scaleTween = scale_tween(
INTERSECTED,
INTERSECTED.scale.clone(),
{
x: 1,
y: 1
},
100
);
scaleTween.start();
// REVERT Z-INDEX
INTERSECTED.position.z = 1;
}
// Save current intersected object
INTERSECTED = intersects[is].object;
// HIGHLIGHT
// Save current color
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
// Set highlight color
INTERSECTED.material.color.setHex(COLOR_HIGHLIGHT);
// SCALE UP
// Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
try {
scaleTween.stop();
} catch (e) {}
// Create tween, save it so we can try to stop it, if needed
scaleTween = scale_tween(
INTERSECTED,
INTERSECTED.scale.clone(),
{
x: 1.5,
y: 1.5
},
100
);
scaleTween.start();
// SET Z-INDEX
INTERSECTED.position.z = 10;
}
} else {
// If there are no intersections
// Reset cursor
document.body.style.cursor = 'default';
// Restore previous intersection object (if it exists) to its original color
if (INTERSECTED !== null) {
// REVERT COLOR
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
// SCALE DOWN
// Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
try {
scaleTween.stop();
} catch (e) {}
// Create tween, save it so we can try to stop it, if needed
scaleTween = scale_tween(
INTERSECTED,
INTERSECTED.scale.clone(),
{
x: 1,
y: 1
},
100
);
scaleTween.start();
// REVERT "Z-INDEX"
INTERSECTED.position.z = 1;
}
// Remove previous intersection object reference by setting current intersection object to "nothing"
INTERSECTED = null;
}
I've managed to highlight the object and scale it up with a tween quite nicely, but when I move the mouse out of the object onto the next object (the scaled object is scaled over the next object a bit), the highlight is gone, but the scale persists. How do I manage to scale the object down? And preferably with a tween?
A pen for this code can be found here: https://codepen.io/phun-ky/pen/erBZZy, the relevant part is at about line 1284 or search for INTERSECTED.
I wrote my own one. It's hell imperfect, but, at least, it scales up and down the hexagons:
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({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x101010);
document.body.appendChild(renderer.domElement);
var hexes = [];
var colCount = 5;
var rowCount = 4;
var hexDiameter = 3;
var xStart = -(colCount) * hexDiameter * 0.5;
var rowSpace = Math.sqrt(3) * hexDiameter * 0.5;
var yStart = (rowCount - 1) * rowSpace * 0.5;
var hexGeom = new THREE.CylinderGeometry(hexDiameter * 0.5, hexDiameter * 0.5, 0.0625, 6, 1);
hexGeom.rotateX(Math.PI * 0.5);
for (let j = 0; j < rowCount; j++) {
for (let i = 0; i < colCount + (j % 2 === 0 ? 0 : 1); i++) {
let hex = new THREE.Mesh(hexGeom, new THREE.MeshBasicMaterial({
color: Math.random() * 0x7e7e7e + 0x7e7e7e,
wireframe: false
}));
hex.position.set(xStart + i * hexDiameter + (j % 2 === 0 ? 0.5 * hexDiameter : 0), yStart - j * rowSpace, 0);
hex.userData.scaleUp = function(h) {
if (h.userData.scaleDownTween) h.userData.scaleDownTween.stop();
let initScale = h.scale.clone();
let finalScale = new THREE.Vector3().setScalar(2);
h.userData.scaleUpTween = new TWEEN.Tween(initScale).to(finalScale, 500).onUpdate(function(obj) {
h.scale.copy(obj)
}).start();
}
hex.userData.scaleDown = function(h) {
if (h.userData.scaleUpTween) h.userData.scaleUpTween.stop();
let initScale = h.scale.clone();
let finalScale = new THREE.Vector3().setScalar(1);
h.userData.scaleUpTween = new TWEEN.Tween(initScale).to(finalScale, 500).onUpdate(function(obj) {
h.scale.copy(obj)
}).start();
}
scene.add(hex);
hexes.push(hex);
}
}
window.addEventListener("mousemove", onMouseMove, false);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects = [];
var intersected;
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObjects(hexes);
if (intersects.length > 0) {
if (intersected != intersects[0].object) {
if (intersected) intersected.userData.scaleDown(intersected);
intersected = intersects[0].object;
intersected.userData.scaleUp(intersected);
}
} else {
if (intersected) intersected.userData.scaleDown(intersected);
intersected = null;
}
}
render();
function render() {
requestAnimationFrame(render);
TWEEN.update();
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.min.js"></script>

Threejs lerp, lerpVectors work without animation

I am trying to move 10 meshes in different directions after some amount of time through functions lerp, lerpVectors. Both of them gave me the same result, they just teleported meshes in new positions without animation of moving there. Here is my code (with "lerp"):
var newPos;
var timer = 0;
function render() {
if (timer === 120) {
for (var i = 0; i < count; i++) {
mesh = meshes[i];
newPos = new THREE.Vector3(Math.random() * 200 - 100, Math.random() * 200 - 100, Math.random() * 200 - 100);
mesh.position.lerp(newPos, 0.5);
}
}
timer++;
renderer.render(scene, camera);
}
I am sure there is another way through calculated distances and then decreasing them to 0. But I suppose lerp and leprVectors do the same thing, so the question is what I do wrong?
r83
Maybe these both functions work not as I expect.
Okay, I've just figured out where was the problem. Lerp in the official documentation looks as .lerp(v3, alpha) and the value alpha should changing dynamically, so I added clock:
var delta = clock.getDelta();
and alpha now:
alpha += delta;
also I added boolean variable to the each mesh to check whether it's enough to do lerp or not, moreover, I set a new position during initiating for each of them as a parameter like so:
mesh.newPosition = new THREE.Vector3(
Math.random() * 200 - 100, Math.random() * 200 - 100, Math.random() * 200 - 100
);
and as a result I updated my render function:
var timer = 0;
var alpha = 0;
var delta;
var currentX, currentY, currentZ;
function render() {
delta = clock.getDelta();
for (var i = 0; i < count; i++) {
mesh = meshes[i];
if (timer === 120) {
mesh.endTransition = false;
}
if (!mesh.endTransition ) {
currentX = Math.abs(mesh.newPosition.x - mesh.position.x);
currentY = Math.abs(mesh.newPosition.y - mesh.position.y);
currentZ = Math.abs(mesh.newPosition.z - mesh.position.z);
if (currentX >= 1 || currentY >= 1 || currentZ >= 1) {
alpha += delta;
mesh.position.lerp(mesh.newPosition, alpha);
} else {
mesh.endTransition = true;
}
}
}
timer++;
renderer.render(scene, camera);
}
Perhaps my solution can be improved.

Picking Object3D loaded via OBJMTLLoader

When we load an Object3D with OBJMTLLoader, it is not possible to use raycaster to pick this object with mouse. Intersection array length is always 0. Any one knows the reason? Below is the code...
The loader routine
var loader2 = new THREE.OBJMTLLoader();
loader2.load('/assets/unwrap/masa/dogtasmasa.obj', '/assets/unwrap/masa/dogtasmasa.mtl', function (object) {
object.position.y = 1.5;
object.position.x = 0;
object.position.z = 2;
object.rotateX(-Math.PI / 2);
object.rotateZ(-Math.PI / 2);
object.scale.set(0.04, 0.04, 0.04);
object.castShadow = true;
scene.add(object);
});
and the picking
function onDocumentMouseDown(event) {
event.preventDefault();
SCREEN_WIDTH = window.innerWidth - 5;
SCREEN_HEIGHT = window.innerHeight - 5;
var vector = new THREE.Vector3((event.clientX / SCREEN_WIDTH) * 2 - 1, -(event.clientY / SCREEN_HEIGHT) * 2 + 1, 0.5);
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
for (var i = 0; i < intersects.length; i++) {
var obj = intersects[i].object;
controls.enabled = false;
tControls.attach();
}
}
else {
controls.enabled = true;
tControls.detach();
}
}
The scene is the whole browser window. Any other mesh cerated via THREE types can be picked, but object3d not...
Thanks for all kinds of help
Add the recursive flag like so:
var intersects = raycaster.intersectObjects( objects, true );
three.js r.66

Accessing single particles in THREE.js Particle System

I really tried every example, searched the web for hours but I can't seem to get it working!
So I simply tried to implement a little particle system simulating falling snow, just like this: http://www.aerotwist.com/tutorials/creating-particles-with-three-js/
But I only can access it in whole. Meaning I can rotate it as such but as soon as I try to iterate over it's vertices, the whole animation is getting the hiccups! I would really appreciate some help here!
-
Here are the key parts:
-> Setting up the particle system:
var partikelGeo = new THREE.Geometry();
var partikelMaterial = new THREE.ParticleBasicMaterial({
color:0xffffff,
size: 10,
map: THREE.ImageUtils.loadTexture('snowflake2.png'),
blending: THREE.AdditiveBlending,
transparent:true
});
var partikelAnzahl = 3500;
for (var p = 0; p < partikelAnzahl; p++) {
var pX = Math.random() * 1000 -500;
var pY = Math.random() * 1000 -500;
var pZ = Math.random() * 1000 -500;
var partikel = new THREE.Vertex(new THREE.Vector3(pX,pY,pZ));
partikel.velocity = new THREE.Vector3(0,-Math.random(),0);
partikelGeo.vertices.push(partikel);
}
var partikelSystem = new THREE.ParticleSystem(partikelGeo, partikelMaterial);
partikelSystem.sortParticles = true;
scene.add(partikelSystem);
-> Rendering & Animation on mouseclick
var frame = 0;
function animate(){
// request new frame
requestAnimationFrame(function(){
animate();
});
// render
render();
}
animate();
var check = 0;
onmousedown = function(){
if (check) {
check = 0;
}else{
check = 1;
}
}
function render() {
if (check) {
clickDo();
}
camera.lookAt(new THREE.Vector3(0,0,0));
renderer.render(scene,camera);
}
function clickDo() {
frame++;
partikelSystem.rotation.y += 0.01;
var pCount = partikelAnzahl;
while(pCount--) {
// get the particle
var particle =
partikelGeo.vertices[pCount];
// check if we need to reset
if(particle.position.y < -200) {
particle.position.y = 200;
particle.velocity.y = 0;
}
// update the velocity with
// a splat of randomniz
particle.velocity.y -=
Math.random() * .1;
// and the position
particle.position.addSelf(
particle.velocity);
}
// flag to the particle system
// that we've changed its vertices.
partikelSystem.
geometry.
__dirtyVertices = true;
}
Rah
Your code looks good to me. I would just suggest to try not sorting your particles as you use an additive blending:
partikelSystem.sortParticles = false;

Resources