Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reset three.js clock?

I want to reset the clock so that clock.getElapsedTime() gives me a new time from when I reset the clock (for example, useful when restarting a game level/scene the second time).

I am initiating clock = new THREE.Clock(); in init(), and in my game loop update(), I am using this clock. But when the game is over, I want to reset the clock (I am not initiating the level again and am just positioning the player back to the beginning so I am not initiating a new clock).

How can I achieve this?

like image 559
krikey Avatar asked Jan 21 '16 21:01

krikey


1 Answers

Bad news: It's impossible to reset the THREE.Clock to zero time, as of r73, released Oct 2015. The explanation is below, and the only possible workarounds are at the end of this answer.

The Problematic Design of Three.Clock Investigated in Depth

The design of Clock is dangerous...

-- Mrdoob, GitHub issue comment

To understand the flaw(s) in THREE.Clock we must inspect the source code to see how it works. We can see that in the constructor for a Clock, a few variables are instantiated, which at first glace looks like we can overwrite on our Clock instance to reset to zero:

this.startTime = 0;
this.oldTime = 0;
this.elapsedTime = 0;

However, digging a little deeper, we need to figure out happens when getElapsedTime() is called. Under the hood, it calls getDelta(), which mutates this.elapsedTime, which means that getDelta and getElapsedTime are dangerous, conflicting functions, but we still need to look closer at getDelta:

var newTime = self.performance.now();

diff = 0.001 * ( newTime - this.oldTime );
this.oldTime = newTime;
this.elapsedTime += diff;

This function is referencing some unknown, implicit global variable, self, and calling some odd function, self.performance.now(). Red flag! But let's keep digging...

It turns out Three defines a global variable, self, with a property performance with a method now() in the "main" Three.js file.

Stepping back to THREE.Clock for a moment, look how it calculates this.elapsedTime. It's based on the value returned by self.performance.now(). That seems innocent enough on the surface, except the true problem arises here. We can see that self.performance.now() creates a true "private" variable in a closure, meaning no one in the outside world can ever see / access it:

( function () {
    var start = Date.now();
    self.performance.now = function () {
        return Date.now() - start;
    }
} )();

This start variable is the start time of the app, as returned in milliseconds from Date.now().

That means that when the Three.js library loads, start will get set to the value Date.now(). To clarify, simply including the Three.js script has global, irreversible side effects for the timer. Looking back on the clock, we can see that the return value of self.performance.now() is used to calculate the current elapsed time of a Clock. Because this is based on the private, inaccessible "start time" of Three.js, it will always be relative to when you include the Three.js script.

Workaround #1

Store startTime = clock.getElapsedTime() on your game/level start and making all your calculations relative to that. Something like var currentTime = clock.getElapsedTime() - startTime which is the only way to get the true absolute time from your scene load / start.

Workaround #2

Three.Clock under the hood is really just a thin wrapper around Date.now(), which is an IE9+ available method. You're probably better off creating your own tiny abstraction around this to get a sane clock, with an easy to implement reset() method. If I find an npm package with this functionality, or make my own, I will update this answer.

Workaround #3

Wait for the Three.js Three.Clock source code to be updated, possibly by me. Since there is an open ticket for it, a pull request to fix it will likely be accepted.

like image 50
Andy Ray Avatar answered Oct 21 '22 15:10

Andy Ray