Skip to content

Commit b038a66

Browse files
committed
a basic test suite, more to come
1 parent b205c59 commit b038a66

File tree

4 files changed

+261
-45
lines changed

4 files changed

+261
-45
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,11 @@
1616
"keywords": [
1717
"facebook",
1818
"animation"
19-
]
19+
],
20+
"scripts": {
21+
"test": "jasmine-node spec"
22+
},
23+
"devDependencies": {
24+
"jasmine-node": "^1.14.5"
25+
}
2026
}

rebound.js

Lines changed: 99 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -126,16 +126,15 @@
126126
// **SpringSystem** is a set of Springs that all run on the same physics
127127
// timing loop. To get started with a Rebound animation you first
128128
// create a new SpringSystem and then add springs to it.
129-
var SpringSystem = rebound.SpringSystem = function SpringSystem() {
129+
var SpringSystem = rebound.SpringSystem = function SpringSystem(looper) {
130130
this._springRegistry = {};
131131
this._activeSprings = [];
132-
this._listeners = [];
132+
this.listeners = [];
133133
this._idleSpringIndices = [];
134-
this._boundFrameCallback = bind(this._frameCallback, this);
134+
this.looper = looper || new AnimationLooper();
135+
this.looper.springSystem = this;
135136
};
136137

137-
extend(SpringSystem, {});
138-
139138
extend(SpringSystem.prototype, {
140139

141140
_springRegistry: null,
@@ -146,16 +145,20 @@
146145

147146
_activeSprings: null,
148147

149-
_listeners: null,
148+
listeners: null,
150149

151150
_idleSpringIndices: null,
152151

153-
_frameCallback: function() {
154-
this.loop();
152+
// A SpringSystem is iterated by a looper. The looper is responsible
153+
// for executing each frame as the SpringSystem is resolved to idle.
154+
// There are three types of Loopers described below AnimationLooper,
155+
// SimulationLooper, and SteppingSimulationLooper. AnimationLooper is
156+
// the default as it is the most useful for common UI animations.
157+
setLooper: function(looper) {
158+
this.looper = looper
159+
looper.springSystem = this;
155160
},
156161

157-
_frameCallbackId: null,
158-
159162
// Create and register a new spring with the SpringSystem. This
160163
// Spring will now be solved for during the physics iteration loop. By default
161164
// the spring will use the default Origami spring config with 40 tension and 7
@@ -190,7 +193,13 @@
190193
// Get a listing of all the springs registered with this
191194
// SpringSystem.
192195
getAllSprings: function() {
193-
return Object.values(this._springRegistry);
196+
var vals = [];
197+
for (var id in this._springRegistry) {
198+
if (this._springRegistry.hasOwnProperty(id)) {
199+
vals.push(this._springRegistry[id]);
200+
}
201+
}
202+
return vals;
194203
},
195204

196205
// registerSpring is called automatically as soon as you create
@@ -239,18 +248,17 @@
239248
// SpringSystem. This gives you an opportunity to run any post
240249
// integration constraints or adjustments on the Springs in the
241250
// SpringSystem.
242-
loop: function() {
251+
loop: function(currentTimeMillis) {
243252
var listener;
244-
var currentTimeMillis = Date.now();
245253
if (this._lastTimeMillis === -1) {
246254
this._lastTimeMillis = currentTimeMillis -1;
247255
}
248256
var ellapsedMillis = currentTimeMillis - this._lastTimeMillis;
249257
this._lastTimeMillis = currentTimeMillis;
250258

251-
var i = 0, len = this._listeners.length;
259+
var i = 0, len = this.listeners.length;
252260
for (i = 0; i < len; i++) {
253-
var listener = this._listeners[i];
261+
var listener = this.listeners[i];
254262
listener.onBeforeIntegrate && listener.onBeforeIntegrate(this);
255263
}
256264

@@ -261,14 +269,12 @@
261269
}
262270

263271
for (i = 0; i < len; i++) {
264-
var listener = this._listeners[i];
272+
var listener = this.listeners[i];
265273
listener.onAfterIntegrate && listener.onAfterIntegrate(this);
266274
}
267275

268-
compatCancelAnimationFrame(this._frameCallbackId);
269276
if (!this._isIdle) {
270-
this._frameCallbackId =
271-
compatRequestAnimationFrame(this._boundFrameCallback);
277+
this.looper.run();
272278
}
273279
},
274280

@@ -282,27 +288,25 @@
282288
}
283289
if (this.getIsIdle()) {
284290
this._isIdle = false;
285-
compatCancelAnimationFrame(this._frameCallbackId);
286-
this._frameCallbackId =
287-
compatRequestAnimationFrame(this._boundFrameCallback);
291+
this.looper.run();
288292
}
289293
},
290294

291295
// Add a listener to the SpringSystem so that you can receive
292296
// before/after integration notifications allowing Springs to be
293297
// constrained or adjusted.
294298
addListener: function(listener) {
295-
this._listeners.push(listener);
299+
this.listeners.push(listener);
296300
},
297301

298302
// Remove a previously added listener on the SpringSystem.
299303
removeListener: function(listener) {
300-
removeFirst(this._listeners, listener);
304+
removeFirst(this.listeners, listener);
301305
},
302306

303307
// Remove all previously added listeners on the SpringSystem.
304308
removeAllListeners: function() {
305-
this._listeners = [];
309+
this.listeners = [];
306310
}
307311

308312
});
@@ -322,9 +326,9 @@
322326
// will be notified of the updates providing a way to drive an
323327
// animation off of the spring's resolution curve.
324328
var Spring = rebound.Spring = function Spring(springSystem) {
325-
this._id = Spring._ID++;
329+
this._id = 's' + Spring._ID++;
326330
this._springSystem = springSystem;
327-
this._listeners = [];
331+
this.listeners = [];
328332
this._currentState = new PhysicsState();
329333
this._previousState = new PhysicsState();
330334
this._tempState = new PhysicsState();
@@ -363,15 +367,15 @@
363367

364368
_displacementFromRestThreshold: 0.001,
365369

366-
_listeners: null,
370+
listeners: null,
367371

368372
_timeAccumulator: 0,
369373

370374
_springSystem: null,
371375

372376
// Remove a Spring from simulation and clear its listeners.
373377
destroy: function() {
374-
this._listeners = [];
378+
this.listeners = [];
375379
this._springSystem.deregisterSpring(this);
376380
},
377381

@@ -424,8 +428,8 @@
424428
setCurrentValue: function(currentValue) {
425429
this._startValue = currentValue;
426430
this._currentState.position = currentValue;
427-
for (var i = 0, len = this._listeners.length; i < len; i++) {
428-
var listener = this._listeners[i];
431+
for (var i = 0, len = this.listeners.length; i < len; i++) {
432+
var listener = this.listeners[i];
429433
listener.onSpringUpdate && listener.onSpringUpdate(this);
430434
}
431435
return this;
@@ -465,8 +469,8 @@
465469
this._startValue = this.getCurrentValue();
466470
this._endValue = endValue;
467471
this._springSystem.activateSpring(this.getId());
468-
for (var i = 0, len = this._listeners.length; i < len; i++) {
469-
var listener = this._listeners[i];
472+
for (var i = 0, len = this.listeners.length; i < len; i++) {
473+
var listener = this.listeners[i];
470474
listener.onSpringEndStateChange && listener.onSpringEndStateChange(this);
471475
}
472476
return this;
@@ -642,8 +646,8 @@
642646
notifyAtRest = true;
643647
}
644648

645-
for (var i = 0, len = this._listeners.length; i < len; i++) {
646-
var listener = this._listeners[i];
649+
for (var i = 0, len = this.listeners.length; i < len; i++) {
650+
var listener = this.listeners[i];
647651
if (notifyActivate) {
648652
listener.onSpringActivate && listener.onSpringActivate(this);
649653
}
@@ -697,18 +701,22 @@
697701
alpha + this._previousState.velocity * (1 - alpha);
698702
},
699703

704+
getListeners: function() {
705+
return this.listeners;
706+
},
707+
700708
addListener: function(newListener) {
701-
this._listeners.push(newListener);
709+
this.listeners.push(newListener);
702710
return this;
703711
},
704712

705713
removeListener: function(listenerToRemove) {
706-
removeFirst(this._listeners, listenerToRemove);
714+
removeFirst(this.listeners, listenerToRemove);
707715
return this;
708716
},
709717

710718
removeAllListeners: function() {
711-
this._listeners = [];
719+
this.listeners = [];
712720
return this;
713721
},
714722

@@ -743,6 +751,59 @@
743751
this.friction = friction;
744752
};
745753

754+
// Loopers
755+
// -------
756+
// **AnimationLooper** resolves the SpringSystem on an animation timing loop.
757+
var AnimationLooper = rebound.AnimationLooper = function AnimationLooper() {
758+
this.springSystem = null;
759+
var _run = function() {
760+
this.springSystem.loop(Date.now());
761+
};
762+
763+
this.run = function() {
764+
compatRequestAnimationFrame(_run);
765+
}
766+
};
767+
768+
// **SimulationLooper** resolves the SpringSystem to a resting state in a
769+
// blocking loop. This is useful for synchronously generating pre-recorded
770+
// animations that can then be played on a timing loop later.
771+
var SimulationLooper = rebound.SimulationLooper = function SimulationLooper(timestep) {
772+
this.springSystem = null;
773+
var time = 0;
774+
var running = false;
775+
timestep=timestep || 16.667;
776+
777+
this.run = function() {
778+
if (running) {
779+
return;
780+
}
781+
running = true;
782+
while(!this.springSystem.getIsIdle()) {
783+
this.springSystem.loop(time+=timestep);
784+
}
785+
running = false;
786+
}
787+
788+
789+
};
790+
791+
// **SteppingSimulationLooper** resolves the SpringSystem one step at a time controlled
792+
// by an outside loop. This is useful for testing.
793+
var SteppingSimulationLooper = rebound.SteppingSimulationLooper = function(timestep) {
794+
this.springSystem = null;
795+
var time = 0;
796+
var running = false;
797+
798+
// this.run is NOOP'd here to allow control from the outside using this.step.
799+
this.run = function(){};
800+
801+
// Perform one step toward resolving the SpringSystem.
802+
this.step = function(timestep) {
803+
this.springSystem.loop(time+=timestep);
804+
}
805+
}
806+
746807
// Math for converting from
747808
// [Origami](http://facebook.github.io/origami/) to
748809
// [Rebound](http://facebook.github.io/rebound).
@@ -813,12 +874,6 @@
813874
idx != -1 && array.splice(idx, 1);
814875
}
815876

816-
function compatCancelAnimationFrame(id) {
817-
return typeof window != 'undefined' &&
818-
window.cancelAnimationFrame &&
819-
cancelAnimationFrame(id);
820-
}
821-
822877
// Cross browser/node timer functions.
823878
function compatRequestAnimationFrame(func) {
824879
var meth;

0 commit comments

Comments
 (0)