I'd like to stop a Three.js animation properly in order to execute a function afterwards.
This is the code I have :
var anim;
function animate() {
anim = requestAnimationFrame(animate);
checkRotation();
renderer.render(scene, camera);
setTimeout(cancel, 10000);
}
function cancel() {
cancelAnimationFrame(anim);
finalCoords();
}
animate();
Being put like this, the animation checkRotation() indeed stops but the function finalCoords() keeps looping as if it was trapped in some kind of recursion.
I have also tried this :
var anim;
function animate(anima) {
anima = requestAnimationFrame(animate);
checkRotation();
renderer.render(scene, camera);
}
function cancel(anima) {
cancelAnimationFrame(anima);
finalCoords();
}
animate(anim);
setTimeout(cancel(anim), 10000);
Now the loop stops but the function finalCoords() doesn't return proper results and the animation doesn't stop.
What did I do wrong ? and how can I fix it ?
Thanks!
1) The problem in the first example is that in each animation frame you install another new timeout. Accordingly, for example, if you have 60 frames per second for 10,000 seconds you will set 600 million new timeouts.
2) The problem is the second example that you just are not transmitted in time-out function, but once it is called. Plus you are confused with the names of the variables in which to store the identifier of the frame of animation.
3) An improved version of the second example:
var stopAnimate = false;
function animate(time) {
if (!stopAnimate) {
checkRotation(time);
requestAnimationFrame(animate);
} else {
finalCoords('final');
}
}
animate();
setTimeout( function() {
stopAnimate = true;
}, 1000);
[ https://jsfiddle.net/02d37pxs/ ]
Related
I want to load a model in my scene, and the loading works well, but I want to keep a reference to it, so I can edit position and other variables later.
As you can see by the naming, the model is a boat. (not that that matters a whole bunch)
When I write the code like this it works, and I can edit the scale of the model:
function Boat(scene, loader) {
var boatObject;
this.SpawnBoat = function() {
loader.load('resources/SimpleBoat.gltf', handleLoad,
undefined, function (error) {
console.error(error);
});
}
function handleLoad(gltf) {
boatObject = gltf.scene;
scene.add(boatObject);
boatObject.scale.set(0.1,0.1,0.1);
} }
However, when I want to edit the variable after it's been loaded it says the variable is "undefined" and won't run the code.
function Boat(scene, loader) {
var boatObject;
this.SpawnBoat = function() {
loader.load('resources/SimpleBoat.gltf', handleLoad,
undefined, function (error) {
console.error(error);
});
boatObject.scale.set(0.1,0.1,0.1);
}
function handleLoad(gltf) {
boatObject = gltf.scene;
scene.add(boatObject);
} }
The boat function is called as followed, in a module that has reference to Three.js and GLTFLoader
const loader = new GLTFLoader();
const boat = new Boat(scene, loader);
const boatModel = boat.SpawnBoat();
Is there a way I can keep reference to the loaded model so I can edit proporties later?
been stuck for hours trying to figure out how to make my 3d model spin like a coin spinning. my code is below anything help i really appreciate you guys.
// add a light
const light = new THREE.AmbientLight(0xffffff,5)
scene.add(light);
// controls
// load object
const loader = new THREE.GLTFLoader();
loader.load('yattee.gltf', function (gltf) {
scene.add(gltf.scene);
})
const render = function() {
requestAnimationFrame(render)
// camera.rotation.z += .0010
renderer.render(scene, camera);
}
render();
Try it like so:
let myModel;
const loader = new GLTFLoader();
loader.load( 'model.gltf', function ( gltf ) {
myModel = gltf.scene;
scene.add( myModel );
} );
const render = function() {
requestAnimationFrame(render)
if ( myModel ) {
myModel.rotation.z += 0.01;
}
renderer.render(scene, camera);
}
The problem is, you are defining the model gltf inside a function, which means that it cannot be accessed from render(). In order to spin the model, you need to define the variable gltf prior to loading the model, so it can be stored inside of it:
const loader = new THREE.GLTFLoader();
var gltf; //By defining it prior to loading, it is now in global scope.
loader.load('yattee.gltf', function (gltf) {
scene.add(gltf.scene);
})
Then, you can add the flipping effect in the render() loop. Make sure that you add the given if statement so it doesn't crash before the model even loads!
const render = function() {
requestAnimationFrame(render);
if(gltf) {
gltf.rotation.z += .0010; //Make sure you rotate the gltf, not the camera.
}
renderer.render(scene, camera);
}
And that should do it!
Note: I've taken a look at some of the other answers, and it seems like they either don't work, or they don't explain what they are doing properly. I think that this should help you achieve what you want.
I have a page that can be deployed in an offline situation, and want to handle playing youtube videos in a particular container on screen. I don't want to load iframe_api from youtube until it's needed, since there's a large chance it won't be.
function EmbedAndPlay_Youtube(videoid, container) {
// todo: allow script caching
$.getScript("//www.youtube.com/iframe_api")
.fail(function () {
// some kind of alert to the user ...
})
.done(function () {
var $container = $(container);
var w = $(container).width(),
h = $(container).height(),
player = new YT.Player('ytvideo', {
height: h,
width: w,
videoId: videoid,
events: {
'onReady': function (event) {
event.target.playVideo();
}
}
});
});
}
sometimes, depending on the platform and its network capability, I get a script undefined kind of error, which I think means that the "done" function has executed before the youtube script has finished executing itself.
Is there a way to ensure that the loaded script has finished executing before calling a "done" method?
Your problem there is that the script loaded async in the url //youtube.com/player_api loads another script asynchronously (the JS player itself, which defines YT.Player). In your case, your done() callback is called when the script is loaded, but the player script may not be loaded yet. (Technically, the script in //youtube.com/player_api is just a loader)
To use YT.Player safely, bind your callback to window.onYouTubeIframeAPIReady, which is called by YouTube's script:
function EmbedAndPlay_Youtube(videoid, container) {
$.getScript("//www.youtube.com/iframe_api")
.fail(function () {
// some kind of alert to the user ...
})
.done(function () {
window.onYouTubeIframeAPIReady = function () {
var $container = $(container);
var w = $(container).width(),
h = $(container).height(),
player = new YT.Player('ytvideo', {
height: h,
width: w,
videoId: videoid,
events: {
'onReady': function (event) {
event.target.playVideo();
}
}
});
}
});
}
I am trying to load multiple pieces simultaneously. Either only one piece shows up, correctly, or both pieces show up, with one correct and one incorrect. Here's my code:
var loader = THREE.ColladaLoader();
loader.load('model.dae', function colladaReady(result) {
var piece = result.scene.children[0];
piece.position.set(-100, 0, 0);
scene.add(piece);
renderer.render(scene, camera);
});
loader.load('model2.dae', function colladaReady2(result2) {
var piece2 = result2.scene.children[0];
piece2.position.set(100, 0, 0);
scene.add(piece2);
renderer.render(scene, camera);
});
The reason is because ColladaLoader is not thread-safe, so multiple load() calls cannot be made on the same object. Simply create a new instance of ColladaLoader like so and all should work
var loader = THREE.ColladaLoader();
loader.load('model.dae', function colladaReady(result) {
var piece = result.scene.children[0];
piece.position.set(-100, 0, 0);
scene.add(piece);
renderer.render(scene, camera);
});
/*** Line added here! ***/
var loader2 = THREE.ColladaLoader();
loader.load('model2.dae', function colladaReady2(result2) {
var piece2 = result2.scene.children[0];
piece2.position.set(100, 0, 0);
scene.add(piece2);
renderer.render(scene, camera);
});
let's say i do this:
$('field').addEvents ({
'focus' : function() { // do some stuff }
'blur' : function() { // do other stuff }
});
this is the default behaviour for all my text input fields. what i now want to do is something like this
<button id='shinyButton' name='shinyButton'>Poke Me</button>
then:
$('shinyButton').addEvent('click', function() {
stopDefaultBlurFunctionInCurrentlyFocussedField();
// do seriously cool stuff
if (finishedDoingSeriouslyCoolStuff) {
$('field').focus(); // return focus to input field
}
}
so:
1) how do i stopDefaultBlurFunctionInCurrentlyFocussedField();?
2) how do i tell if i'm actually finishedDoingSeriouslyCoolStuff?
Strictly speaking you can't do what you want to do because the blur event fires before your click handler does.
Dimitar's suggestion of disabling the event on mouseover works fine for mouse users, but prevents users from triggering the button with the keyboard.
One option (but a bit of a hack) would be to introduce a tiny delay into the blur event handler, and use a variable flag to control the event firing (you might need to tune the delay so it's imperceptible but still long enough for your purposes:
var disableBlurMethod = false;
$('field').addEvents ({
'focus' : function() { // do some stuff }
'blur' : function() {
(function() {
if (!disableBlurMethod) {
// do some stuff
}
}).delay(50);
}
});
$('shinyButton').addEvent('click', function() {
disableBlurMethod = true;
// do seriously cool stuff
if (finishedDoingSeriouslyCoolStuff) {
disableBlurMethod = false;
$('field').focus(); // return focus to input field
}
}