How can an AnimationAction stop at the last frame without looping in three.js? - animation

I'd like to stop an AnimationAction at the last frame that I've created with morph targets.
https://threejs.org/docs/#api/en/animation/AnimationAction
I've tried animationAction.clampWhenFinished = true; but that doesn't seem to work.
I've looked at older stackoverflow questions and searched through forums but the solutions didn't work.
var cubeTarget1 = new THREE.BoxGeometry(20, 10, 10);
var cubeTarget2 = new THREE.BoxGeometry(20, 10, 50);
var cubeTarget3 = new THREE.BoxGeometry(60, 10, 10);
cubeGeometry.morphTargets[0] = {name: 't1', vertices: cubeTarget1.vertices};
cubeGeometry.morphTargets[1] = {name: 't2', vertices: cubeTarget2.vertices};
cubeGeometry.morphTargets[2] = {name: 't3', vertices: cubeTarget3.vertices};
Is there a way I can do something like: (this doesn't work, it loops back to the first morphTarget)
var clip1 = THREE.AnimationClip.CreateFromMorphTargetSequence('run', [cubeGeometry.morphTargets[0],cubeGeometry.morphTargets[1]], 30);
var action1 = mixer.clipAction(clip1);
action1.play(); // starts at cubeTarget1 ends at cubeTarget2 (animating between them, without a loop)
// and at a later point I'd like to do
var clip2 = THREE.AnimationClip.CreateFromMorphTargetSequence('run', [cubeGeometry.morphTargets[1],cubeGeometry.morphTargets[2]], 30);
var action2 = mixer.clipAction(clip2);
action2.play(); // starts at cubeTarget2 ends at cubeTarget3 (animating between them, without a loop)
Here's my fiddle:
https://jsfiddle.net/foreyez/uy8abk6v/

This is my approach that I used with an enemy bot gltf model on my three.js prototype first person shooter. The robot has a single track animation with many frames. I had to split the frames up into sub clips with the following code then applied clampwhenfinished.
var EnemyHeavyBotFallBackClip = THREE.AnimationUtils.subclip(gltf.animations[0], “Take_001”, 1300, 1355);
actionEnemyHeavyBotFallBackMixer = mixer.clipAction(EnemyHeavyBotFallBackClip);
actionEnemyHeavyBotFallBackMixer.clampWhenFinished = true;
actionEnemyHeavyBotFallBackMixer.setLoop(THREE.LoopOnce);
actionEnemyHeavyBotFallBackMixer.play();
https://www.shanebrumback.com/super-soldier-battle-intro.html
Disclaimer: This is my website.

I looked at the three.js code. And inside LoopOnce the section involving clampWhenFinished doesn't get hit at all.
For now I'll do it in a very crude way until I find a better solution:
action.setDuration(5).play();
setTimeout(function()
{
action.paused = true;
},2500); // half of the duration
Another way I've been doing is to use morphTargetInfluences and just increment it on an animation loop:
function animate() {
if (cube.morphTargetInfluences[0] < 1)
cube.morphTargetInfluences[0] += 0.01;
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
Use Tween.js if you need more functionality.

It took me a while to get this working, a lot of the online examples seem to be outdated and/or non-working. Try:
var clips = THREE.AnimationClip.CreateClipsFromMorphTargetSequences(geometry.morphAttributes.position, 60, true);
mixer = new THREE.AnimationMixer(points);
var action = mixer.clipAction(clips[0]).setDuration(10);
action.clampWhenFinished = true;
action.setLoop(THREE.LoopOnce);
action.play();
Note the "noLoop" parameter of CreateClipsFromMorphTargetSequences(name: String, morphTargetSequence: Array, fps: Number, noLoop: Boolean) needs to be "true" along with clampWhenFinished = true and setLoop(THREE.LoopOnce).
Full example here https://jsfiddle.net/jm4ersoq/

Related

Three.js - repositioning vertices in a 'particle' mesh

I have a basic three.js game working and I'd like to add particles. I've been searching online, including multiple questions here, and the closest I've come to getting a 'particle system' working is using a THREE.BufferGeometry, a THREE.BufferAttribute and a THREE.Points mesh. I set it up like this:
const particleMaterial = new THREE.PointsMaterial( { size: 10, map: particleTexture, blending: THREE.AdditiveBlending, transparent: true } );
const particlesGeometry = new THREE.BufferGeometry;
const particlesCount = 300;
const posArray = new Float32Array(particlesCount * 3);
for (let i = 0; i < particlesCount; i++) {
posArray[i] = Math.random() * 10;
}
const particleBufferAttribute = new THREE.BufferAttribute(posArray, 3);
particlesGeometry.setAttribute( 'position', particleBufferAttribute );
const particlesMesh = new THREE.Points(particlesGeometry, particleMaterial);
particlesMesh.counter = 0;
scene.add(particlesMesh);
This part works and displays the particles fine, at their initial positions, but of course I'd like to move them.
I have tried all manner of things, in my 'animate' function, but I am not happening upon the right combination. I'd like to move particles, ideally one vertex per frame.
The current thing I'm doing in the animate function - which does not work! - is this:
particleBufferAttribute.setXYZ( particlesMesh.counter, objects[0].position.x, objects[0].position.y, objects[0].position.z );
particlesGeometry.setAttribute( 'position', particleBufferAttribute );
//posArray[particlesMesh.counter] = objects[0].position;
particlesMesh.counter ++;
if (particlesMesh.counter > particlesCount) {
particlesMesh.counter = 0;
}
If anyone has any pointers about how to move Points mesh vertices, that would be great.
Alternatively, if this is not at all the right approach, please let me know.
I did find Stemkoski's ShaderParticleEngine, but I could not find any information about how to make it work (the docs are very minimal and do not seem to include examples).
You don't need to re-set the attribute, but you do need to tell the renderer that the attribute has changed.
particleBufferAttribute.setXYZ( particlesMesh.counter, objects[0].position.x, objects[0].position.y, objects[0].position.z );
particleBufferAttribute.needsUpdate = true; // This is the kicker!
By setting needsUpdate to true, the renderer knows to re-upload that attribute to the GPU.
This might not be concern for you, but just know that moving particles in this way is expensive, because you re-upload the position attribute every single frame, which includes all the position data for every particle you aren't moving.

Creating a light trail effect by dynamically updating the curve of a TubeBufferGeometry

I have a third person game using react-three-fiber and I want to add a sort of trailing light effect wherever the player moves. The light trail will disappear after a while so I was thinking of having a fixed size array for the points. This was my initial attempt at a solution:
const point = new THREE.Vector3();
const points = new Array(50).fill(null).map(p => new THREE.Vector3());
let index = 0;
const Trail = () => {
const ref = useRef();
const playerBody = useStore(state => state.player); // contains player position
const [path, setPath] = useState(new THREE.CatmullRomCurve3(points));
useFrame(() => { // equivalent to raf
const { x, y, z } = playerBody.position;
point.set(x, y, z);
points[index].copy(point);
index = (index + 1) % 50;
setPath(new THREE.CatmullRomCurve3(points));
if (ref && ref.current) {
ref.current.attributes.position.needsUpdate = true;
ref.current.computeBoundingSphere();
}
});
return (
<mesh>
<tubeBufferGeometry ref={ref} attach="geometry" args={[path, 20, .5, 8, false]} />
<meshBasicMaterial attach="material" color={0xffffff} />
</mesh>
)
}
Basically my thought process was to update the curve on every frame (or every x frames to be more performant) and to use an index to keep track of which position in the array of points to update.
However I get two problems with this:
TubeBufferGeometry doesn't update. Not sure if it's even possible to update the geometry after instantiation.
The pitfall I foresee in using this fixed array / index method is that once I hit the end of the array, I will have to wrap around to index 0. So then the curve interpolation would mess up because I'm assuming it takes the points sequentially. The last point in the array should connect to the first point now but it won't be like that.
To solve #2, I tried something like
points.unshift();
points.push(point.clone);
instead of points[index].copy(point); but I still couldn't get the Tube to update in the first place.
I wanted to see if there's a better solution for this or if this is the right approach for this sort of problem.
If you want to update the path of a TubeBufferGeometry, you also need to update all the vertices and normals, it is like building again the geometry.
Take a look here to understand how it works : https://github.com/mrdoob/three.js/blob/r118/src/geometries/TubeGeometry.js#L135
The important part is the generateSegment() function, and don't forget this part before :
const frames = path.computeFrenetFrames( tubularSegments, closed );
I made an example last year, feel free to use my code : https://codepen.io/soju22/pen/JzzvbR

calling object3D children for unique styles / animations

I'm wondering how I would go about calling the individual children of cube (mesh0, mesh1) so I'm able to set different styles / animations to them both.
AFRAME.registerComponent('multi_box', {
schema: {},
update: function() {
for (var i = 0; i < 2; i++) {
var material = new THREE.MeshBasicMaterial({color: "blue"});
var geometry = new THREE.BoxGeometry(1, 1, 1);
var cube = new THREE.Mesh(geometry, material);
cube.position.x = i == 0 ? -1 : 1;
cube.position.y = 0.5;
cube.position.z = -5;
this.el.setObject3D("mesh"+i, cube); //unique name for each object
}
console.log(this.el.object3DMap)
}
});
Codepen link: https://codepen.io/ubermario/pen/wrwjVG
I can console.log them both and see that they are unique objects to each other but I'm having trouble calling them:
var meshtest = this.el.getObject3D('mesh0')
console.log(meshtest.position)
I'v tried this method but with no luck: aframe get object3d children
Any help is appreciated :)
Instancing
In your for cycle, you create
a new geometry instance
a new material instance
a new mesh that connect the previous two
Each new keyword creates a unique instance that is independent of the others. In your case mesh0 and mesh1 are fully independent of each other. If you change any property of an instance, like for example material color or position, the other(s) will not be affected by that. In fact you do that by assigning a different x position to each cube.
Storage
Your component holds a map of 3D objects. Each is identified by a unique name. You generate this name by concatenating the prefix mesh with the iteration number (value of i).
You can later access the cubes just the same way you created them. Either by name
this.el.getObject3D('mesh0')
this.el.getObject3D('mesh1')
//etc.
or by index
this.el.object3D.children[0]
this.el.object3D.children[1]
and you can manipulate them further. For example you can put his on Ln19 in your Codepen:
this.el.getObject3D('mesh0').position.y = 2;
this.el.object3D.children[1].position.z = -3;
Just for completeness: If you would omit the +i at the end, you would overwrite the same key again and again, so mesh would reference just the last cube and others would get lost.
Changing properties
Three.js has a nice API, but in javascript you always need to think what happens behind the scenes. You will see that while learning new stuff. For example:
this.el.object3D.children[1].position.z = -3;
this.el.object3D.children[1].material.color.set('#ffff00');
this.el.object3D.children[1].material.color = [1, 1, 0];
As you can see the position can be changed directly, but color needs a setter sometimes. Things get more complicated with vectors where you need to watch which methods change the current instance and which produce a new one. You could easily forget to clone it. Say you had:
var pos = new THREE.Vector3(-1, 0.5, -5)
for (var i = 0; i < 2; i++) {
var material = new THREE.MeshBasicMaterial({color: "blue"});
var geometry = new THREE.BoxGeometry(1, 1, 1);
var cube = new THREE.Mesh(geometry, material);
//wrong, since it will assign the object reference,
//i.e. always the same object.
//Thus, the cubes will be at one position in the end
cube.position = pos;
//right
cube.pos = pos.clone();
pos.x += 1;
this.el.setObject3D("mesh"+i, cube); //unique name for each object
}
The difference is a bit more moderate topic on reference and value types. You will find many tutorials for that, for example this small gist I just found.
I hope this was helpful and wish you good progress in learning!

Is it possible to loop through a sprite group in three.js with a tween that ends in a different position for each sprite?

I'm confused.
I've made a group of 10 sprites and added them to a THREE.Object3D() called fireworkGroup. I have another Object3D called explode. The tween loops through the sprites changing them from their initial position to explode.position.
for ( var i = 0; i < fireworkGroup.children.length; i ++ )
{
explode.position.x =(Math.random() - 0.5)*4;
explode.position.y =4;
explode.position.z =2;
var tweenLaunch = new TWEEN.Tween(fireworkGroup.children[i].position).to(explode.position, 4000).easing( TWEEN.Easing.Quartic.In);
tweenLaunch.start();
}
The tween is moving all the sprites from their start position to the same end position. So I thought this might be because "tweenLaunch" is being overwritten with a different explode.position each time as the tween is rendered so I'm only seeing the last one created in the loop. When I refresh the page they do all move to a different position, consistent with the Math.random().
But then why do all the sprites move to the explode.position? If "tweenLaunch" is being overwritten then why is it not moving only the last sprite?
Is it possible to have a loop with a tween in it that also changes?
Many Thanks.
I've managed to work out what was wrong by reading around the subject on Stackoverflow questions and answers, looking at a great particle example by stemkoski then trial and error.
view-source:http://stemkoski.github.io/Three.js/Particles.html
I used console.log to look at explode.position that I was using as the second position in the tween. It wasn't holding the values I wanted (a different Math.random on each loop).
So I created fireworkAttributes:
fireworkAttributes = { startPosition: [], explodePosition: [], nextPosition: [] };
and then cloned the sprite position in the function that created the sprites using:
fireworkAttributes.explodePosition.push( sprite.position.clone() );
then looped it in it's own function:
for (var i = 0; i < fireworkGroup.children.length; i++)
{
fireworkAttributes.explodePosition[i].x = (Math.random() - 0.5)*4;
fireworkAttributes.explodePosition[i].y = 4;
fireworkAttributes.explodePosition[i].z = 2;
}
then changed the code in the original question to:
for ( var a = 0; a < fireworkGroup.children.length; a ++ )
{
//need to use this new object as tweening to fireworkAttributes.explodePosition[a] does not work
explodeSite.position = fireworkAttributes.explodePosition[a];
var tweenLaunch = new TWEEN.Tween(fireworkGroup.children[a].position).to(explodeSite.position, 4000).easing( TWEEN.Easing.Quartic.In);
tweenLaunch.start();
}
There may be a more elegant way to do this and I will be working to clean up the code where possible but this does work.

Three.js Raycaster intersectObjects too slow?

I'm writing a Three.js prototype for interacting with objects using the Leap Motion. Each frame (or regularly anyway), I want to check if the representation of the user's finger is above or beneath an object in the scene.
I've done this with the code below, but the intersectObject call is taking about 200 milliseconds, even though it's just testing one object. This is causing the animation to slow down and become very jerky (I've tried doing it e.g. once every 20 frames instead of every frame, but then it still jerks every 20 frames).
Is there a way to do this quicker? Am I doing something wrong? How do other people deal with this?
Thanks!
Code:
...
var filepath = '../models/Scissors.js';
loader.load(filepath, function(geometry, materials) {
scissors = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial(materials) );
scene.add( scissors );
});
...
function update() {
...
// NB. Sphere1 has been positioned to represent the user's index finger
// in 3D space
var vector = sphere1.position.subSelf( camera.position );
var ray = new THREE.Raycaster( camera.position, vector.clone().normalize() );
var start = new Date().getTime();
var collisions = ray.intersectObjects( [scissors] );
// Takes about 200ms
console.log('Took ' + (new Date().getTime() - start) + ' ms' );
if( collisions.length > 0 ) {
console.log('HIT!');
}
...
requestAnimFrame(update);
}
Silly me, of course the reason it's slow is because the scissors object is a non-trivial model. Now I'm containing it within an invisible cube and testing against that instead. And it's super fast now (0-1 milliseconds) :-)

Resources