|
126 | 126 | // **SpringSystem** is a set of Springs that all run on the same physics
|
127 | 127 | // timing loop. To get started with a Rebound animation you first
|
128 | 128 | // 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) { |
130 | 130 | this._springRegistry = {};
|
131 | 131 | this._activeSprings = [];
|
132 |
| - this._listeners = []; |
| 132 | + this.listeners = []; |
133 | 133 | this._idleSpringIndices = [];
|
134 |
| - this._boundFrameCallback = bind(this._frameCallback, this); |
| 134 | + this.looper = looper || new AnimationLooper(); |
| 135 | + this.looper.springSystem = this; |
135 | 136 | };
|
136 | 137 |
|
137 |
| - extend(SpringSystem, {}); |
138 |
| - |
139 | 138 | extend(SpringSystem.prototype, {
|
140 | 139 |
|
141 | 140 | _springRegistry: null,
|
|
146 | 145 |
|
147 | 146 | _activeSprings: null,
|
148 | 147 |
|
149 |
| - _listeners: null, |
| 148 | + listeners: null, |
150 | 149 |
|
151 | 150 | _idleSpringIndices: null,
|
152 | 151 |
|
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; |
155 | 160 | },
|
156 | 161 |
|
157 |
| - _frameCallbackId: null, |
158 |
| - |
159 | 162 | // Create and register a new spring with the SpringSystem. This
|
160 | 163 | // Spring will now be solved for during the physics iteration loop. By default
|
161 | 164 | // the spring will use the default Origami spring config with 40 tension and 7
|
|
190 | 193 | // Get a listing of all the springs registered with this
|
191 | 194 | // SpringSystem.
|
192 | 195 | 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; |
194 | 203 | },
|
195 | 204 |
|
196 | 205 | // registerSpring is called automatically as soon as you create
|
|
239 | 248 | // SpringSystem. This gives you an opportunity to run any post
|
240 | 249 | // integration constraints or adjustments on the Springs in the
|
241 | 250 | // SpringSystem.
|
242 |
| - loop: function() { |
| 251 | + loop: function(currentTimeMillis) { |
243 | 252 | var listener;
|
244 |
| - var currentTimeMillis = Date.now(); |
245 | 253 | if (this._lastTimeMillis === -1) {
|
246 | 254 | this._lastTimeMillis = currentTimeMillis -1;
|
247 | 255 | }
|
248 | 256 | var ellapsedMillis = currentTimeMillis - this._lastTimeMillis;
|
249 | 257 | this._lastTimeMillis = currentTimeMillis;
|
250 | 258 |
|
251 |
| - var i = 0, len = this._listeners.length; |
| 259 | + var i = 0, len = this.listeners.length; |
252 | 260 | for (i = 0; i < len; i++) {
|
253 |
| - var listener = this._listeners[i]; |
| 261 | + var listener = this.listeners[i]; |
254 | 262 | listener.onBeforeIntegrate && listener.onBeforeIntegrate(this);
|
255 | 263 | }
|
256 | 264 |
|
|
261 | 269 | }
|
262 | 270 |
|
263 | 271 | for (i = 0; i < len; i++) {
|
264 |
| - var listener = this._listeners[i]; |
| 272 | + var listener = this.listeners[i]; |
265 | 273 | listener.onAfterIntegrate && listener.onAfterIntegrate(this);
|
266 | 274 | }
|
267 | 275 |
|
268 |
| - compatCancelAnimationFrame(this._frameCallbackId); |
269 | 276 | if (!this._isIdle) {
|
270 |
| - this._frameCallbackId = |
271 |
| - compatRequestAnimationFrame(this._boundFrameCallback); |
| 277 | + this.looper.run(); |
272 | 278 | }
|
273 | 279 | },
|
274 | 280 |
|
|
282 | 288 | }
|
283 | 289 | if (this.getIsIdle()) {
|
284 | 290 | this._isIdle = false;
|
285 |
| - compatCancelAnimationFrame(this._frameCallbackId); |
286 |
| - this._frameCallbackId = |
287 |
| - compatRequestAnimationFrame(this._boundFrameCallback); |
| 291 | + this.looper.run(); |
288 | 292 | }
|
289 | 293 | },
|
290 | 294 |
|
291 | 295 | // Add a listener to the SpringSystem so that you can receive
|
292 | 296 | // before/after integration notifications allowing Springs to be
|
293 | 297 | // constrained or adjusted.
|
294 | 298 | addListener: function(listener) {
|
295 |
| - this._listeners.push(listener); |
| 299 | + this.listeners.push(listener); |
296 | 300 | },
|
297 | 301 |
|
298 | 302 | // Remove a previously added listener on the SpringSystem.
|
299 | 303 | removeListener: function(listener) {
|
300 |
| - removeFirst(this._listeners, listener); |
| 304 | + removeFirst(this.listeners, listener); |
301 | 305 | },
|
302 | 306 |
|
303 | 307 | // Remove all previously added listeners on the SpringSystem.
|
304 | 308 | removeAllListeners: function() {
|
305 |
| - this._listeners = []; |
| 309 | + this.listeners = []; |
306 | 310 | }
|
307 | 311 |
|
308 | 312 | });
|
|
322 | 326 | // will be notified of the updates providing a way to drive an
|
323 | 327 | // animation off of the spring's resolution curve.
|
324 | 328 | var Spring = rebound.Spring = function Spring(springSystem) {
|
325 |
| - this._id = Spring._ID++; |
| 329 | + this._id = 's' + Spring._ID++; |
326 | 330 | this._springSystem = springSystem;
|
327 |
| - this._listeners = []; |
| 331 | + this.listeners = []; |
328 | 332 | this._currentState = new PhysicsState();
|
329 | 333 | this._previousState = new PhysicsState();
|
330 | 334 | this._tempState = new PhysicsState();
|
|
363 | 367 |
|
364 | 368 | _displacementFromRestThreshold: 0.001,
|
365 | 369 |
|
366 |
| - _listeners: null, |
| 370 | + listeners: null, |
367 | 371 |
|
368 | 372 | _timeAccumulator: 0,
|
369 | 373 |
|
370 | 374 | _springSystem: null,
|
371 | 375 |
|
372 | 376 | // Remove a Spring from simulation and clear its listeners.
|
373 | 377 | destroy: function() {
|
374 |
| - this._listeners = []; |
| 378 | + this.listeners = []; |
375 | 379 | this._springSystem.deregisterSpring(this);
|
376 | 380 | },
|
377 | 381 |
|
|
424 | 428 | setCurrentValue: function(currentValue) {
|
425 | 429 | this._startValue = currentValue;
|
426 | 430 | 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]; |
429 | 433 | listener.onSpringUpdate && listener.onSpringUpdate(this);
|
430 | 434 | }
|
431 | 435 | return this;
|
|
465 | 469 | this._startValue = this.getCurrentValue();
|
466 | 470 | this._endValue = endValue;
|
467 | 471 | 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]; |
470 | 474 | listener.onSpringEndStateChange && listener.onSpringEndStateChange(this);
|
471 | 475 | }
|
472 | 476 | return this;
|
|
642 | 646 | notifyAtRest = true;
|
643 | 647 | }
|
644 | 648 |
|
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]; |
647 | 651 | if (notifyActivate) {
|
648 | 652 | listener.onSpringActivate && listener.onSpringActivate(this);
|
649 | 653 | }
|
|
697 | 701 | alpha + this._previousState.velocity * (1 - alpha);
|
698 | 702 | },
|
699 | 703 |
|
| 704 | + getListeners: function() { |
| 705 | + return this.listeners; |
| 706 | + }, |
| 707 | + |
700 | 708 | addListener: function(newListener) {
|
701 |
| - this._listeners.push(newListener); |
| 709 | + this.listeners.push(newListener); |
702 | 710 | return this;
|
703 | 711 | },
|
704 | 712 |
|
705 | 713 | removeListener: function(listenerToRemove) {
|
706 |
| - removeFirst(this._listeners, listenerToRemove); |
| 714 | + removeFirst(this.listeners, listenerToRemove); |
707 | 715 | return this;
|
708 | 716 | },
|
709 | 717 |
|
710 | 718 | removeAllListeners: function() {
|
711 |
| - this._listeners = []; |
| 719 | + this.listeners = []; |
712 | 720 | return this;
|
713 | 721 | },
|
714 | 722 |
|
|
743 | 751 | this.friction = friction;
|
744 | 752 | };
|
745 | 753 |
|
| 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 | + |
746 | 807 | // Math for converting from
|
747 | 808 | // [Origami](http://facebook.github.io/origami/) to
|
748 | 809 | // [Rebound](http://facebook.github.io/rebound).
|
|
813 | 874 | idx != -1 && array.splice(idx, 1);
|
814 | 875 | }
|
815 | 876 |
|
816 |
| - function compatCancelAnimationFrame(id) { |
817 |
| - return typeof window != 'undefined' && |
818 |
| - window.cancelAnimationFrame && |
819 |
| - cancelAnimationFrame(id); |
820 |
| - } |
821 |
| - |
822 | 877 | // Cross browser/node timer functions.
|
823 | 878 | function compatRequestAnimationFrame(func) {
|
824 | 879 | var meth;
|
|
0 commit comments