I am trying to make a robot that can perform certain animations when keys are pressed.
When I press the "J" key I would like the robot to jump up and down.
I am using Three.js and Tween.js to accomplish this. So far I have a scene made with light and a camera. I am creating the different parts of the robot through functions. I have an example of one of these functions below.
function createHead() {
const head = new THREE.Mesh( new THREE.BoxGeometry(30, 30, 30),
new THREE.MeshLambertMaterial({ color: 0x669999 }));
head.position.y = 30;
head.position.z = -250;
head.rotation.y = 5;
scene.add(head);
var jumpTween = new TWEEN.Tween(head.position).to({y: head.position.y+20}, 1000).repeat(1).yoyo(true).start();
}
Here is my function for updating
function update (event) {
if (String.fromCharCode(event.keyCode) == "J" || isJumping)
{
isJumping = true;
TWEEN.update();
}
// Draw!
renderer.render(scene, camera);
// Schedule the next frame.
requestAnimationFrame(update);
}
When I press "J" my robot jumps once and only once. I cannot press "J" again to get the robot to perform another jump. I was hoping there would be away to just simply "call" a tween on command and have it play on command. However, it seems like TWEEN.update() is the only function that actually runs the tween and in this case, it only runs once. I have been reading the documentation for Tween.js but I am not finding anything helpful.
Edit: I have also tried adding this to my tween.
.onComplete(() => {isJumping = false})
so that it would set isJumping back to false when the animation completes but this did not work.
Thanks for the help.
Related
I'm trying to destroy (or destruct or dispose) an 'instance' of Three.JS using this:
Full example: https://jsfiddle.net/v7oLzs4m/58/
function kill() {
const rendererDomWas = renderer.domElement;
renderer.dispose();
controls.dispose();
renderer = scene = camera = controls = null;
document.body.removeChild( rendererDomWas );
shouldRender = false;
}
function animate() {
if(!shouldRender) {return} // no more requesting animation frames if 'shouldRender' is false
frameID = window.requestAnimationFrame( animate );
renderer.render( scene, camera );
}
(i.e. disposing, setting references to null, and stopping the draw loop from touching renderer while shouldRender is false)
It appears to work at first (The renderer content stops showing) but when I re-create the instance, it never comes back.
It's as if something is still... holding onto the GLContext which prevents it from being invoked again.
Why can't I re-create a new instance of Three.JS?
I'm not sure why this works (since I was already doing this in my Fiddle...)
But it turns out the secret ingredient (for me) to letting Three.JS get GC'd/disposed is this:
window.cancelAnimationFrame(frameID);
I suppose that this stops the render function from being stored in the hardware-level draw loop, which holds references to GLContext (and the chain)
The GC can't ever happen unless the 'loop is broken'
(Correct me if I'm wrong please)
I am experimenting with creating games using Three.js and I would like to ask if there is a way to animate a GLFT model but instead of having the animation loop I would like to use random intervals. In my case, the game is about taking care of frogs (but I'm cool so I spelled it phrogs) and as an idle animation I want the frogs to croak randomly. Further on in development I also want to add randomly playing animations for movement, eating etc but right now I only need the croaking animation.
Here is the code that loads the model and animates it:
const basePhrog = new GLTFLoader()
var obj
basePhrog.load('assets/models/Phrogs/base phrog/phrog.gltf', function (gltf) {
mixer = new THREE.AnimationMixer(gltf.scene)
obj = gltf.scene
var action = mixer.clipAction(gltf.animations[0])
scene.add(gltf.scene)
action.play()
})
My animate loop looks like this:
function animate () {
requestAnimationFrame(animate)
var delta = clock.getDelta()
if (mixer) mixer.update(delta)
renderer.render(scene, camera)
}
animate()
I hope you can help and thank you for any suggestions!
The glTF file provides an animation (or THREE.AnimationClip in three.js terminology), and the playback of that clip is managed by a THREE.AnimationAction instance. The code in your example will begin playing that animation immediately, and loop it forever, but the AnimationAction does not have to be used that way.
Instead, you could wait and play the action at random intervals:
var action = mixer.clipAction( animation );
action.loop = THREE.LoopOnce;
// Play animation randomly every 1-10s.
setInterval(() => {
action
.reset()
.play();
}, Math.random() * 9000 + 1000);
You can continue to update the AnimationMixer as you are doing now, regardless of whether the animation is playing at any given time.
If you're later trying to play multiple animations on the same object, some of the APIs for transitioning between two animations may be helpful, see the animation / skinning / morph example.
At first the texture works fine and the video plays as expected, but when VR is entered via VRDisplay.requestPresent it stops. Why is this and how to fix it?
The VR display has its own render loop. Usually needsUpdate is automatically set to true on every animation frame by three.js, but this is only true for the default display.
To fix this, get the VR display from the vrdisplayconnect event and create your own update loop. E.g.
let display = e.display;
let displayUpdateLoop = () =>
{
// May get a warning if getFrameData is not called.
let frameData = new VRFrameData();
display.getFrameData(frameData);
videoTexture.needsUpdate = true;
// Stop loop if no longer presenting.
if (display.isPresenting)
display.requestAnimationFrame(displayUpdateLoop);
}
display.requestAnimationFrame(displayUpdateLoop);
I'm using Babylon.js 2.4.0.
I have a mesh (in the shape of a couch) loaded from a .obj file, and a camera set up like this:
let camera = new BABYLON.FreeCamera('camera1', new BABYLON.Vector3(0, 2, 0), scene);
camera.checkCollisions = true;
camera.applyGravity = true;
camera.ellipsoid = new BABYLON.Vector3(1, 1, 1);
camera.attachControl(canvas, false);
camera.speed = 0.5;
camera.actionManager = new BABYLON.ActionManager(scene);
I want to set up an event so that when I walk through the couch, "intersection" is logged to the console:
let action = new BABYLON.ExecuteCodeAction(
{ trigger: BABYLON.ActionManager.OnIntersectionEnterTrigger, parameter: { mesh: couchMesh }},
(evt) => {
console.log("intersection");
}
);
this.camera.actionManager.registerAction(action);
When I walk through the mesh, nothing is logged to the console.
I've created an example on the Babylon.js Playground using an example that they provide to check that it wasn't a problem with my mesh or camera set up, and it doesn't appear to be (the playground doesn't work either).
A camera in Babylon.js has no action manager, so even if you set one it won't really work.
To get this to work using action managers, you could define an invisible box around the camera, with a predefined size and attach the action manager to the mesh created. then set the mesh's parent to be the camera, and you are done. Here is your playground with those changes - http://www.babylonjs-playground.com/#KNXZF#3
Another solution is to use the internal collision system of babylon js, and set the camera's onCollide function to actually do something :) Here is en example - http://www.babylonjs-playground.com/#KNXZF#4
Notice that in the second playground, the camera won't go throug the box, as the collision system prevents it from doing so. I am not sure about your usecase, so it is hard to say which one of the two will work better.
If you need a "gate" system (knowing when a player moved through a gate, for example), use the 1st method. The 2nd is much cleaner, but has its downsides.
I have two boxes. One spawns on the ground and the other is dropped on top of it. Gravity is turned on. I am trying to get the collision event listener to fire on the bottom box which is resting on the ground. However, nothing is logged.
var c = new Physijs.BoxMesh( new THREE.CubeGeometry( 5, 5, 5 ), new THREE.MeshBasicMaterial({ color: 0x888888 }) );
c.__dirtyPosition = true;
c.position.set(10, 0,-5);
c.addEventListener('collision', function(object){
console.log("hello world"); // NOT FIRING
});
scene.add(c);
var p = c.clone();
p.__dirtyPosition = true;
p.position.y = 50;
scene.add(p);
I can't figure out what I'm doing wrong - could it be because of __dirtyPosition?
EDIT: I tested it without clone() and creating the second box anew, but it doesn't make a difference.
EDIT 2: It's worth mentioning that the simulation runs fine I just can't get the listener to work.
Relevant GitHub Issue
It looks like the clone method is part of THREEjs, not Physijs. So your code just clones the physical material, not the physical mesh. You're just going to have to create another BoxMesh using the same code you did for c.