1 /**
  2  * The Render Engine
  3  * Mover2DComponent
  4  *
  5  * @fileoverview An extension of the transform 2D component which adds physical
  6  *               movement properties such as mass, gravity, and velocity.
  7  *
  8  * @author: Brett Fattori (brettf@renderengine.com)
  9  * @author: $Author: bfattori $
 10  * @version: $Revision: 1555 $
 11  *
 12  * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com)
 13  *
 14  * Permission is hereby granted, free of charge, to any person obtaining a copy
 15  * of this software and associated documentation files (the "Software"), to deal
 16  * in the Software without restriction, including without limitation the rights
 17  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 18  * copies of the Software, and to permit persons to whom the Software is
 19  * furnished to do so, subject to the following conditions:
 20  *
 21  * The above copyright notice and this permission notice shall be included in
 22  * all copies or substantial portions of the Software.
 23  *
 24  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 25  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 26  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 27  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 28  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 29  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 30  * THE SOFTWARE.
 31  *
 32  */
 33 
 34 // The class this file defines and its required classes
 35 R.Engine.define({
 36     "class":"R.components.transform.Mover2D",
 37     "requires":[
 38         "R.components.Transform2D",
 39         "R.engine.Events",
 40         "R.lang.Timeout",
 41         "R.math.Point2D",
 42         "R.math.Vector2D"
 43     ]
 44 });
 45 
 46 /**
 47  * @class A mover component that adds simplified physical transformations to an object.
 48  *        Properties include velocity, acceleration, mass, and gravity.
 49  *
 50  * @param name {String} The name of the component
 51  * @param priority {Number} The priority of this component between 0.0 and 1.0
 52  * @extends R.components.Transform2D
 53  * @constructor
 54  * @description Creates a 2d mover component
 55  */
 56 R.components.transform.Mover2D = function () {
 57     "use strict";
 58     return R.components.Transform2D.extend(/** @scope R.components.transform.Mover2D.prototype */{
 59 
 60         velocity:null,
 61         _nVel:null,
 62         vDecay:0,
 63         angularVelocity:0,
 64         lPos:null,
 65         maxVelocity:0,
 66         acceleration:null,
 67         gravity:null,
 68         mass:null,
 69         atRest:null,
 70         checkRest:null,
 71         restingVelocity:null,
 72         lagAdjustment:null,
 73         checkLag:null,
 74         _vec:null,
 75         firstFrame:false,
 76 
 77         /**
 78          * @private
 79          */
 80         constructor:function (name, priority) {
 81             this.base(name, priority || 1.0);
 82             this.velocity = R.math.Vector2D.create(0, 0);
 83             this.angularVelocity = 0;
 84             this._nVel = R.math.Vector2D.create(0, 0);
 85             this.acceleration = R.math.Vector2D.create(0, 0);
 86             this.lPos = R.math.Point2D.create(0, 0);
 87             this.vDecay = 0;
 88             this.maxVelocity = -1;
 89             this.gravity = R.math.Vector2D.create(0, 0);
 90             this.atRest = false;
 91             this.checkRest = true;
 92             this.mass = 1;
 93             this.restingVelocity = R.components.transform.Mover2D.DEFAULT_RESTING_VELOCITY;
 94             this.lagAdjustment = R.components.transform.Mover2D.DEFAULT_LAG_ADJUSTMENT;
 95             this.checkLag = true;
 96             this.firstFrame = true;
 97 
 98             // Temp vector to use in calcs
 99             this._vec = R.math.Vector2D.create(0, 0);
100         },
101 
102         /**
103          * Destroys the component instance
104          */
105         destroy:function () {
106             this.velocity.destroy();
107             this._nVel.destroy();
108             this.acceleration.destroy();
109             this.lPos.destroy();
110             this.gravity.destroy();
111             this._vec.destroy();
112             this.base();
113         },
114 
115         /**
116          * Releases the component back into the object pool. See {@link R.engine.PooledObject#release}
117          * for more information.
118          */
119         release:function () {
120             this.base();
121             this.velocity = null;
122             this._nVel = null;
123             this.vDecay = 0;
124             this.angularVelocity = 0;
125             this.lPos = null;
126             this.maxVelocity = -1;
127             this.acceleration = null;
128             this.gravity = null;
129             this.atRest = null;
130             this.checkRest = null;
131             this.mass = null;
132             this.restingVelocity = null;
133             this.lagAdjustment = null;
134             this.checkLag = null;
135             this._vec = null;
136             this.firstFrame = true;
137         },
138 
139         /**
140          * Updates the transformation of the component, setting the
141          * position and rotation based on the time that has elapsed since
142          * the last update.  This component handles frame rate independent
143          * updates so that lag between frames doesn't affect the relative
144          * position or rotation.
145          *
146          * @param renderContext {R.rendercontexts.AbstractRenderContext} The render context for the component
147          * @param time {Number} The engine time in milliseconds
148          * @param dt {Number} The delta between the world time and the last time the world was updated
149          *          in milliseconds.
150          */
151         execute:function (renderContext, time, dt) {
152             if (!this.isResting()) {
153                 this.lPos.set(this.getPosition());
154                 var rot = this.getRotation();
155 
156                 // If we've just come into the world, we can short circuit with a
157                 // quick addition of the velocity.
158                 if (this.firstFrame) {
159                     this.setPosition(this.lPos.add(this.velocity).add(this.acceleration).add(this.gravity));
160                     this.setRotation(rot);
161                 } else {
162                     if (this.getVelocityDecay() != 0 && this.velocity.len() > 0) {
163                         // We need to decay the velocity by the amount
164                         // specified until velocity is zero, or less than zero
165                         this._vec.set(this.velocity).neg();
166                         this._vec.mul(this.getVelocityDecay());
167                         this.velocity.add(this._vec);
168                     }
169 
170                     // Adjust the step value for lagging frame rates
171                     this._vec.set(this.velocity.add(this.acceleration).add(this.gravity));
172                     this._vec.mul(dt / R.Engine.fpsClock);
173 
174                     if (this.maxVelocity != -1 && this._vec.len() > this.maxVelocity) {
175                         this._vec.normalize().mul(this.maxVelocity);
176                     }
177 
178                     this.setPosition(this.lPos.add(this._vec));
179 
180                     // TODO: Actually implement angular velocity per time step
181                     var angVel = this.angularVelocity * (dt / R.Engine.fpsClock);
182                     this.setRotation(rot + angVel);
183                 }
184 
185                 // Check rest state
186                 if (this.getCheckRestState()) {
187                     if (this.getVelocity().len() < this.getRestingVelocity()) {
188                         this.setVelocity(R.math.Vector2D.ZERO);
189                         this.atRest = true;
190                     }
191                 }
192             }
193 
194             this.firstFrame = false;
195             this.base(renderContext, time, dt);
196         },
197 
198         /**
199          * Setting this to <tt>true</tt> will enable a check for time lag and adjust
200          * calculations by a specified factor.
201          *
202          * @param state {Boolean} <tt>true</tt> to check for lag
203          */
204         setCheckLag:function (state) {
205             this.checkLag = state;
206         },
207 
208         /**
209          * Returns <tt>true</tt> if calculations should determine time lag and adjust.
210          * @return {Boolean}
211          */
212         isCheckLag:function () {
213             return this.checkLag;
214         },
215 
216         /**
217          * Sets the value by which calculations will be adjusted for time lag.
218          *
219          * @param lagAdj {Number} The adjustment value
220          */
221         setLagAdjustment:function (lagAdj) {
222             if (lagAdj != 0) {
223                 this.lagAdjustment = lagAdj;
224             }
225         },
226 
227         /**
228          * Get the value by which calculations are adjusted for time lag.
229          * @return {Number} The adjustment value
230          */
231         getLagAdjustment:function () {
232             return this.lagAdjustment;
233         },
234 
235         /**
236          * Set the velocity vector of the component.
237          *
238          * @param vector {Number|R.math.Vector2D} The velocity vector, or the X velocity
239          * @param [y] {Number} If <tt>vector</tt> is a number, this is the Y velocity
240          */
241         setVelocity:function (vector, y) {
242             this.velocity.set(vector, y);
243             if (!this.velocity.isZero()) {
244                 this.atRest = false;
245             }
246         },
247 
248         /**
249          * Returns the velocity vector of the component
250          * @return {R.math.Vector2D}
251          */
252         getVelocity:function () {
253             return this.velocity;
254         },
255 
256         /**
257          * Returns <tt>true</tt> if the component is in a resting state.
258          * @return {Boolean}
259          */
260         isResting:function () {
261             return this.atRest;
262         },
263 
264         /**
265          * Setting this to <tt>true</tt> will stop further movement calculations
266          * from occuring.  Marks the object as being "at rest".
267          *
268          * @param state {Boolean} <tt>true</tt> to stop movement
269          */
270         setResting:function (state) {
271             this.atRest = state;
272         },
273 
274         /**
275          * Get the magnitude at which velocity is determined to be close enough to
276          * zero to be "at rest".
277          * @return {Number} The resting velocity magnitude
278          */
279         getRestingVelocity:function () {
280             return this.restingVelocity;
281         },
282 
283         /**
284          * Set the magnitude of velocity which determines if the object is "at rest".
285          *
286          * @param mag {Number} The velocity magnitude which is "at rest"
287          */
288         setRestingVelocity:function (mag) {
289             this.restingVelocity = mag;
290         },
291 
292         /**
293          * If set to <tt>true</tt>, the component will check to see if the
294          * velocity magnitude has dropped below a defined rate.  When the
295          * velocity magnitude drops below the set rate, the object will be
296          * marked as "at rest" and no calculations will be made until the
297          * object is set into motion again.
298          *
299          * @param state {Boolean} <tt>true</tt> to check to see if the velocity
300          *                        has reached a "resting state".
301          */
302         setCheckRestState:function (state) {
303             this.checkRest = state;
304             if (this.isResting() && !state) {
305                 this.setResting(false);
306             }
307         },
308 
309         /**
310          * Determine if the component should check to see if the velocity has
311          * dropped below a level which would indicate "at rest".
312          * @return {Boolean} <tt>true</tt> if the component will check for the "resting state"
313          */
314         getCheckRestState:function () {
315             return this.checkRest;
316         },
317 
318         /**
319          * Set the acceleration vector.  Acceleration will be constantly applied to
320          * the last position.
321          *
322          * @param vector {Number|R.math.Vector2D} The acceleration vector, or acceleration along X
323          * @param [y] {Number} If <tt>vector</tt> is a number, the acceleration along Y
324          */
325         setAcceleration:function (vector, y) {
326             this.acceleration.set(vector, y);
327             this.atRest = false;
328         },
329 
330         /**
331          * Get the acceleration vector.
332          * @return {R.math.Vector2D} The acceleration vector
333          */
334         getAcceleration:function () {
335             return this.acceleration;
336         },
337 
338         /**
339          * Set the vector of gravity.
340          * @param vector {Number|R.math.Vector2D} The gravity vector, or gravity along X
341          * @param [y] {Number} If <tt>vector</tt> is a number, the gravity along Y
342          */
343         setGravity:function (vector, y) {
344             this.gravity.set(vector, y);
345             this.atRest = false;
346         },
347 
348         /**
349          * Get the gravity vector
350          * @return {R.math.Vector2D} The gravity vector
351          */
352         getGravity:function () {
353             return this.gravity;
354         },
355 
356         /**
357          * Set the mass of the object which can be subsequently used in
358          * calculations like friction and energy transfer.
359          *
360          * @param mass {Number} The mass of the object
361          */
362         setMass:function (mass) {
363             this.mass = mass;
364         },
365 
366         /**
367          * Get the mass of the object.
368          * @return {Number} The mass of the object
369          */
370         getMass:function () {
371             return this.mass;
372         },
373 
374         /**
375          * Set the maximum velocity.  Setting this value to <tt>zero</tt> indicates that
376          * there is no maximum velocity.
377          * @param maxVel {Number} The maximum velocity
378          */
379         setMaxVelocity:function (maxVel) {
380             this.maxVelocity = maxVel;
381         },
382 
383         /**
384          * Get the maximum velocity.
385          * @return {Number} The maximum velocity
386          */
387         getMaxVelocity:function () {
388             return this.maxVelocity;
389         },
390 
391         /**
392          * Set the decay rate at which the velocity will approach zero.
393          * You can use this value to cause a moving object to eventually
394          * stop moving. (e.g. friction)
395          *
396          * @param decay {Number} The rate at which velocity decays
397          */
398         setVelocityDecay:function (decay) {
399             this.vDecay = decay;
400         },
401 
402         /**
403          * Get the rate at which velocity will decay to zero.
404          * @return {Number} The velocity decay rate
405          */
406         getVelocityDecay:function () {
407             return this.vDecay;
408         },
409 
410         /**
411          * Set the angular velocity.
412          * @param angularVelocity {Number} The angle of change
413          */
414         setAngularVelocity:function (angularVelocity) {
415             this.angularVelocity = angularVelocity;
416         },
417 
418         /**
419          * Returns the angular velocity.
420          * @return {Number}
421          */
422         getAngularVelocity:function () {
423             return this.angularVelocity;
424         }
425     }, /** @scope R.components.transform.Mover2D.prototype */{
426         /**
427          * Get the class name of this object
428          * @return {String} "R.components.transform.Mover2D"
429          */
430         getClassName:function () {
431             return "R.components.transform.Mover2D";
432         },
433 
434         /**
435          * The default velocity magnitude considered to be "at rest"
436          * @type {Number}
437          */
438         DEFAULT_RESTING_VELOCITY:0.2,
439 
440         /**
441          * The default adjustment made to calculations when a lag occurs between the last time
442          * the component was updated and the current time.
443          * @type {Number}
444          */
445         DEFAULT_LAG_ADJUSTMENT:0.01
446     });
447 };