Three.js Tweening issue: start value always remains the same - javascript

I have a problem for tweening my camera.position . I create a codepen with the minimum of code just to reproduce my issue and I annotate all my code. I also put a lot of console.log() for debugging purpose .
Codepen
the start point is my camera.postion
camera.position.z = 30;
and my tween001
var tween001 = gsap.to(camera.position,{ delay:2,duration:5,z:60,onUpdate:function(){
camera.updateProjectionMatrix();
console.log("play");
},onComplete:function(){console.log("complete");},ease:"elastic"});
so the tween is about to move my camera from the Z = 30 to Z = 60
its work perfectly but ... When the user move the camera when the user move/over/click on the 3d its fire and eventlistener that pause "tween001.pause()" I want the tween001 use the "actual" camera.postion and not when the camera.postion used when the tween 001 get fire .
Cause when the tween001 is played again or it resume from a pause the start point used is the default one x=0 y=0 z=30 .
An idle function play the tween001 again
window.setInterval(checkTime, 1000);// every 1 second launch checktime()
function checkTime() { //idleCounter get 1 every second and at 5 second coz timeout is 5 checktime relaunch the tween001
if (idlecounter < timeout) {
idlecounter++;
//console.log("++ ");
} else if (idlecounter == timeout) {
tween001.play();
console.log('timeout');
}
}

So you have to understand that GSAP assumes it's the only thing that's controlling camera.position. So when you declare gsap.to(camera.position, {z: 60}) it'll store internally the starting position (30) and the ending (60) to build its timeline. It doesn't know that you've changed the z-position with the mousewheel, so when you call .play() it'll still assume you want to go from 30 to 60.
What you have to do is re-initialize a new tween each time so it has to look up the starting position when you want to replay it:
var tween001;
function doTween() {
tween001 = gsap.to(camera.position, { delay:2,duration:5, z:60, ease:"elastic", onComplete:function(){
// camera.updateProjectionMatrix();
console.log("complete");
}});
}
Notice that I declared var tween001 outside the function, in the global scope, so you can still call tween001.pause() whenever you need
There's no need to update the projection matrix while changing position.
Now, when you're ready to start the animation again, instead of using tween001.play() you can call doTween() and it'll build a new timeline that re-reads the camera's current z-position to start the animation:
// ...
else if (idlecounter == timeout) {
doTween();
idlecounter = 0;
}
see here for the updated codepen

Related

Make javascript Animation function loop, Until page loads

I have a piece of javascript that I have copied & edited, that is designed for an animated loading ring but the animation only runs once, I would like it to run every 4 seconds, until the page is loaded, but I can't find the right syntax/script to get it to repeat, i do not want it to reload the page only loop that specific script until i set it to stop.
".radial" is the class of the radials contained inside my css & html files.
there is twelve of them & they do-not rotate only the fluorescent .glow animation part makes it appear as they are rotating. the code is;
const radials = [...document.querySelectorAll('.radial')];
let degrees = 29;
for(i=0; i < radials.length; i++) {
degrees += 13;
radials[i].style.transform = `rotate(${degrees}deg)`;
degrees += 34;
}
radials.forEach((radial, index) => {
setTimeout(function() {
radial.classList.add('glow');
},index * 29);
});
:: Update ::
Having read the comments below and searching on Youtube. I think that wrapping the whole script in a function, would be the best option. Including a call to that function within its self & passing it an argument in the parenthesis of a timeout or delay property. But setInterval() & setTimeOut() both use the unsafe eval() function underneath. Which is supposed to be a security concern.
Also a youtube video I watch a while ago, said that setInterval() & setTimeOut() do not achieve 60fps. requestAnimationFrame() Would be A much better option. I'm not sure how legitamate these claims are, or where his sources were from but I will continue searching the Webs.
The glow part looks good but I just haven't been able to get it to repeat.
I am new to Js please be patient.
is there any other workarounds for the setTimeOut() & setInterval().?
Place this code into a function that is passed to a setInterval() timer call.
function loop() {
const radials = [...document.querySelectorAll('.radial')];
let degrees = 29;
for(i=0; i < radials.length; i++) {
degrees += 13;
radials[i].style.transform = `rotate(${degrees}deg)`;
degrees += 34;
}
radials.forEach((radial, index) => {
setTimeout(function() {
radial.classList.add('glow');
},index * 29);
});
setTimeout(loop, 4000);
}
Use setInterval(). The setInterval takes two parameters, the first is the function you want to run and the second is your repeat time in miliseconds. So to run a function every 4 seconds you would do:
setInterval(function() {
// do something
}, 4000);
You can do it with setInterval, as in the other answers, but I think that the logic is clearer if you have an animate function that keeps calling itself.
You are adding a "glow" class, but you are never removing it. The animate function should toggle it on and off. To make it crystal clear, let's make that a separate function, toggleGlow.
Next, each animation loop we kick off the individual toggleGlow functions with a different delay for each radial.
Finally, the animate function will re-call itself after a short, constant, delay each time, until some stop condition is met (like the page loading).
const radials = [...document.querySelectorAll('.radial')];
function toggleGlow(element) {
if (element.classList.contains("glow")) {
element.classList.remove("glow");
} else {
element.classList.add("glow");
}
}
function animate() {
radials.forEach((radial, index) => {
setTimeout(function() {
toggleGlow(radial);
}, index * 29);
});
if (!stopCondition) {
setTimeout(animate, 200);
}
}
// kick it off
animate();
JSFiddle example here: https://jsfiddle.net/duxhy3Lj/

FBX animations not running properly with three js

I am having an issue with animations on some fbx models. If I have, for example an animation that lasts 20 secs, the model will stay still for 19 secs and then all changes will happen within the last second or so. On other fbx models the animation runs correctly.
The code that I am using to run the animation is a follows:
The loader.load callback is:
var clock = new THREE.Clock();
var mixers = [];
function(object){
object.position.set(0,0,0);
object.mixer = new THREE.AnimationMixer(object);
mixers.push(object.mixer);
console.log(object);
for (var a = 0; a < object.animations.length; a++){
var action = object.mixer.clipAction(object.animations[a]);
action.play();
console.log(action);
}
scene.add(object);
animate();
}
And the animate code is:
function animate() {
requestAnimationFrame(animate);
for(var i = 0; i < mixers.length; i++){
mixers[i].update(clock.getDelta());
}
render();
stats.update();
}
function render() {
if (mixer) {
mixer.update(clock.getDelta());
}
renderer.render(scene, camera);
}
Any ideas? Thanks!
From experience, I can tell you that the fbx ascii export process (at least for Autodesk Maya) doesn't always give either
the correct start and end times set in Maya or
gives a set of numbers that threejs doesn't import properly.
What you end up getting is -- as you describe -- a lot of time in the animation where nothing happens. As far as I've seen, it's usually trailing at the end, but it could certainly be at the beginning as well.
You could fix the fbx file manually, but it might be easier just add a function to set the beginning time to the time of your first frame (and if the first frame is the issue, start with the second frame).
I have the code for this somewhere, let me find it and then I'll add it to this answer.

Updating a QML property from another Javascript Script (Canvas3D)

I'm having trouble reading a javascript variable from QML. I know this seems easy, but this is a particular case :
I'm using canvas3D for generating lots of 3D Spheres and, as the instenciation is really long, I want to display a progress bar.
To do that, I've done this :
import "test6.js" as GLCode
Item {
id: mainview
width: 1280
height: 768
visible: true
// THE PROGRESS BAR
ProgressBar {
id : progressBar
anchors.centerIn: parent
value: canvas3d.progress
z:1
}
//THE CANVAS3D (WebGL)
Canvas3D {
id: canvas3d
anchors.fill: parent
focus: true
property double progress:0 //MY VARIABLE I WANT TO UPDATE FROM test6.js
property var list : []
// Emitted when one time initializations should happen
onInitializeGL: {
GLCode.initializeGL(canvas3d);
}
I have a property name progress in my canvas3d which I'm trying to modify from the test6.js script
In the initializeGL(canvas3d) function I'm updating the value of progress each time I add a sphere :
for ( i = 0; i < spheresNum; i ++ ) {
var x = list[i][0];
var y = list[i][1];
var z = list[i][2];
drawSphere(x,y,z,i);
canvas3d.progress = i/spheresNum*100;
}
Now, the problem is that I get the updated value of progress only when initializeGL() ends. Right now it's like :
Progress Bar to 0%
(Waiting for all the sphere to be instanciated)
(initializeGL() ends)
Progress Bar to 100%
Which is useless. I would prefer having the bar moving each time a sphere is created.
Do you know how can I do that ?
You only see 0% and 100% as progress, because the for-loop in initializeGL() is fully executed before the QML engine will respond to the changed value of canvas3d.progress and update the value of progessBar.value. The for-loop and the updating of the property binding from progessBar.value to canvas3d.progress run in the same thread.
The way to solve this problem is to call initializeGL() only for one step and then yield the CPU for updating the progress. My idea would be to use a single-shot timer that calls itself numSphere times and initialises the i-th spere in the i-th shot.
The step-wise initialisation function would be defined in test6.js as follows:
function initializeGLSphere(i) {
var x = list[i][0];
var y = list[i][1];
var z = list[i][2];
drawSphere(x,y,z,i);
}
After the instance of Canvas3d, you add the single-shot timer:
property int currentSphere = 0
Timer {
id: timer
repeat: false
interval: 0
onTriggered: {
GLCode.initializeGLSphere(currentSphere)
++currentSphere
progessBar.progress = currentSphere / GLCode.numSpheres
if (currentSphere < GLCode.numSpheres) {
timer.restart()
}
}
}
The timer is started in onInitializeGL:
onInitializeGL: timer.start()
Starting a single-shot timer means that an Event is put into the main Qt event loop. The timer fires once the timer interval expires. An interval of 0ms simply means that the timer fires and executes onTriggered as soon as possible the timer event reaches the front the event queue (loop).
In-between working on the timer events, the event queue will also give the QML engine some time to update the property binding for progressBar.progress. So, you should see quite a few intermediate progress values between 0 and 100. However, you will not see all because multiple timer events might be handled before a property-binding update happens. If you want to see more progress updates, you can simply increase the timer interval.

Javascript : setTimeout and interface freezing

Context
I've got about 10 complex graphs which take 5sec each to refresh. If I do a loop on these 10 graphs, it takes about 50 seconds to refresh. During these 50 seconds, the user can move a scrollbar. If the scrollbar is moved, the refresh must stop and when the scrollbar stops to move, the refresh occurs again.
I'm using the setTimeout function inside the loop to let the interface refresh.
the algorithm is :
render the first graph
setTimeout(render the second graph, 200)
when the second graph is rendered, render the third one in 200ms, and so on
The setTimeout allows us to catch the scrollbar event and to clearTimeout the next refresh to avoid to wait 50sec before moving the scrollbar...
The problem is that it does not run anytime.
Take the simple following code (you can try it in this fiddle : http://jsfiddle.net/BwNca/5/) :
HTML :
<div id="test" style="width: 300px;height:300px; background-color: red;">
</div>
<input type="text" id="value" />
<input type="text" id="value2" />
Javascript :
var i = 0;
var j = 0;
var timeout;
var clicked = false;
// simulate the scrollbar update : each time mouse move is equivalent to a scrollbar move
document.getElementById("test").onmousemove = function() {
// ignore first move (because onclick send a mousemove event)
if (clicked) {
clicked = false;
return;
}
document.getElementById("value").value = i++;
clearTimeout(timeout);
}
// a click simulates the drawing of the graphs
document.getElementById("test").onclick = function() {
// ignore multiple click
if (clicked) return;
complexAlgorithm(1000);
clicked = true;
}
// simulate a complexe algorithm which takes some time to execute (the graph drawing)
function complexAlgorithm(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
document.getElementById("value2").value = j++;
// launch the next graph drawing
timeout = setTimeout(function() {complexAlgorithm(1000);}, 1);
}
The code does :
when you move your mouse into the red div, it updates a counter
when you click on the red div, it simulates a big processing of 1sec (so it freezes the interface due to javascript mono thread)
after the freezing, wait 1ms, and resimulate the processing and so on until the mouse move again
when the mouse move again, it breaks the timeout to avoid infinite loop.
The problem
When you click one time and move the mouse during the freeze, I was thinking that the next code that will be executed when a setTimeout will occurs is the code of the mousemove event (and so it will cancel the timeout and the freeze) BUT sometimes the counter of click gains 2 or more points instead of gaining only 1 point due to the mouvemove event...
Conclusion of this test : the setTimeout function does not always release resource to execute a code during a mousemove event but sometimes kept the thread and execute the code inside the settimeout callback before executing another code.
The impact of this is that in our real example, the user can wait 10 sec (2 graphs are rendered) instead of waiting 5 seconds before using the scrollbar. This is very annoying and we need to avoid this and to be sure that only one graph is rendered (and other canceled) when the scrollbar is moved during a render phase.
How to be sure to break the timeout when the mouse move ?
PS: in the simple example below, if you update the timeout with 200ms, all runs perfectly but it is not an acceptable solution (the real problem is more complex and the problem occurs with a 200ms timer and a complex interface). Please do not provide a solution as "optimize the render of the graphs", this is not the problem here.
EDIT : cernunnos has a better explanation of the problem :
Also, by "blocking" the process on your loop you are ensuring no event can be handled until that loop has finished, so any event will only be handled (and the timeout cleared) inbetween the execution of each loop (hence why you sometimes have to wait for 2 or more full executions before interrupting).
The problem is exactly contains in bold words : I want to be sure to interrupt the execution when I want and not to wait 2 or more full executions before interrupting
Second EDIT :
In summary : takes this jsfiddle : http://jsfiddle.net/BwNca/5/ (the code above).
Update this jsfiddle and provide a solution to :
Mouse move on the red div. Then click and continue moving : the right counter must raise only once. But sometimes it raises 2 or 3 times before the first counter can run again... this is the problem, it must raise only once !
The BIG problem here is setTimeout is unpredictable once it started, and especially when it is doing some heavy lifiting.
You can see the demo here:
http://jsfiddle.net/wao20/C9WBg/
var secTmr = setTimeout(function(){
$('#display').append('Timeout Cleared > ');
clearTimeout(secTmr);
// this will always shown
$('#display').append('I\'m still here! ');
}, 100);
There are two things you can do to minimize the impact on the browser performance.
Store all the intances of the setTimeoutID, and loop through it when you want to stop
var timers = []
// When start the worker thread
timers.push( setTimeout(function () { sleep(1000);}, 1) );
// When you try to clear
while (timers.length > 0) {
clearTimeout(timers.pop());
}
Set a flag when you try to stop process and check that flag inside your worker thread just in case clearTimeout failed to stop the timer
// Your flag
var STOPForTheLoveOfGod = false;
// When you try to stop
STOPForTheLoveOfGod = true;
while (timers.length > 0) {
clearTimeout(timers.pop());
}
// Inside the for loop in the sleep function
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if (STOPForTheLoveOfGod) {
break;
}
// ...
}
}
You can try out this new script.
http://jsfiddle.net/wao20/7PPpS/4/
I may have understood the problem but assuming you are trying to block the interface after a click for a minimum of 1 second and unblocking it by moving the mouse (after that 1 second minimum):
This is not a good implementation of sleep, as you are keeping the process running the whole time (doing nothing != sleeping), this results in a waste of resources.
Why not create an overlay (a semi/fully transparent div), put it on top of the rest of the interface (position fixed, full width and full height) and use it to prevent any interaction with the underlying interface. Then destroy it when the conditions are right (a second has passed and the user moved the mouse).
This behaves more like a sleep (has some initial processing time but then releases the processor for a given amount of time) and should help you achieve the behavior you need (assuming i understood it right).
It has the added bonus of allowing you to give the user some visual cue that some processing is being done.
Edit:
Also, by "blocking" the process on your loop you are ensuring no event can be handled until that loop has finished, so any event will only be handled (and the timeout cleared) inbetween the execution of each loop (hence why you sometimes have to wait for 2 or more full executions before interrupting).
Surprising enough you have not figured out that, when you setTimeout(); you can input a check after that. A variable is true then trash the wait, or trash it. Now there is a method that you can check to scroll with a scroll bar. After you have checked it true inside a variabled using the means, then you will find this will repeat inifite times as they scroll the bar, making many executing times of 5 seconds. To fix this add a 1 second wait to make sure it doesn't over repeat. Your welcome :)
Any long-running function is going to tie up your browser window. Consider moving your complexAlgorithm() outside of your main javascript code using WebWorkers.
The answer is in your question
...the refresh must stop and when the scrollbar stops to move, the
refresh occurs again.
You should write complexAlgorithm in such way that you can almost instantly brake it in a middle (just when you know you will have to re run)
so main code should look something like
stopAllRefresh; //should instantly(or after completing small chunk) stop refresh
setTimeout(startRefresh, 100);
and render graph in small chunks (each runs < 1sec) in setTimeout
like
var curentGraph = 0;
var curentChunk = 0;
function renderGraphChunk(){
if (needToBreak) //check if break rendering
{exit};
// Render chunk here
render(curentGraph, curentChunk);
curentChunk +=1;
setTimeout(renderGraphChunk, 1);
}
this is just a idea sketch, real implementation can be completely different
What you want to do can not be done without web worker, that is only implemented in some latest browser specially Chrome.
Otherwise, you have to break your algorithm in queue. Just like jQuery UI puts every next animation calculation in queue. http://api.jquery.com/jQuery.queue/
It is a simple queue and next instruction set is queued with help of setTimeout.
for (i=0; i <1000; i++)
{
process (i) ;
}
Can be translated to
function queue(s,n, f)
{
this.i=s;
this.n=n;
this.f=f;
this.step = function(){
if ( this.i <this.n)
{
this.f(this.i);
this.i = this.i +1;
var t = this;
setTimeout( function ( ) { t.step(); } , 5);
}
}
this.step();
}
queue ( O, 1000, function(i){
process(i);
}) ;
This is just an example of how Synchronous for loop can be written to execute same logic asynchronously using smaller independent iteration.
Try and check out web workers. I think it will be useful in this situation.
http://en.wikipedia.org/wiki/Web_worker
http://www.html5rocks.com/en/tutorials/workers/basics/

Sequencing Events in Javascript

I am trying to make a simple hidden object game using javascript. When the user finds and clicks an image, I want 3 things to happen in the following order; a sound plays, the image size increases, and the image goes invisible. The problem I am running into is getting the 3 events to happen sequentially, not concurrent. Right now, seems that all three events happen all at the same time.
I've tried using setTimeout(), and while that does create a delay, it still runs all functions at the same time, even if each function is nested in setTimeout.
Example: (all this does is waits 1.5 sec then plays the sound and makes the image invisible):
function FindIt(image, id){
var t = setTimeout('sound()',10);
var b = setTimeout('bigger(' + image + ')',30);
var h = setTimeout('hide(' + image + ')',1500);
}
Below are the functions I am currently using and the actual results are: click the image, nothing happens for 2 seconds, then the sound plays and the image goes invisible.
function FindIt(image, id){
sound();
bigger(image);
hide(image);
}
function sound(){
document.getElementById("sound_element").innerHTML= "<embed src='chime.wav' hidden=true autostart=true loop=false>";
}
function bigger(image){
var img = document.getElementById(image);
img.style.width = 112;
img.style.height = 112;
}
function hide(id){
var ms = 2000;
ms += new Date().getTime();
while (new Date() < ms){} //Create a 2 second delay
var img = document.getElementById(id);
img.style.visibility='hidden';
}
Any guidance would be greatly appreciated!
To trigger things sequentially, you need to execute the second item some amount of time after the first one completes, execute the third item some amount of time after the second one completes, etc...
Only your sound() function actually takes some time, so I'd suggest the following:
function FindIt(image, id){
sound();
// set timer to start next action a certain time after the sound starts
setTimeout(function() {
bigger(image);
// set timer to start next action a certain time after making the image bigger
setTimeout (function() {
hide(image);
}, 1000); // set this time for how long you want to wait after bigger, before hide
}, 1000); // set the time here for how long you want to wait after starting the sound before making it bigger
}
FYI, the animation capabilities in libraries like jQuery or YUI make this sort of thing a lot easier.
Also, please don't use this kind of construct in your JS:
while (new Date() < ms){}
That locks up the browser for that delay and is very unfriendly to the viewer. Use setTimeout to create a delay.
For reference, using the animation libraries in jQuery, the jQuery code to handle a click on the object and then animate it over a 2 second period to a larger size, delay for 1 second, then slideup to disappear is as follows:
$("#rect").click(function() {
$(this).animate({height: 200, width: 400}, 2000).delay(1000).slideUp();
});
jQuery manages an animation queue and handles setting all the timers and doing all the sequencing and animation for you. It's a lot, lot easier to program and gives a very nice result.
You can see it work and play with it here: http://jsfiddle.net/kC4Mz/.
why don't use "event" approach. like onTaskDone();
function task1(arg, onTask1Done){
console.log(arg);
if(onTask1Done)onTask1Done();
}
task1("working", function(){console.log("task2");});
The Frame.js library is designed to elegantly handle situations like this:
function FindIt(image, id){
Frame(10, function(next) { sound(); next(); });
Frame(30, function(next) { bigger(image); next(); });
Frame(1500, function(next) { hide(image); next(); });
Frame.start();
}
Frame.js offers many advantages over using standard timeouts, especially if you are doing a lot of this kind of thing, which for a game, you likely are.
https://github.com/bishopZ/Frame.js

Categories

Resources