1 var jaws = (function(jaws) {
  2 
  3 /**
  4 * @class A basic but powerfull sprite for all your onscreen-game objects. "Field Summary" contains options for the Sprite()-constructor.
  5 * @constructor
  6 *  
  7 * @property {int} x     Horizontal position  (0 = furthest left)
  8 * @property {int} y     Vertical position    (0 = top)
  9 * @property {image} image   Image/canvas or string pointing to an asset ("player.png")
 10 * @property {int} alpha     Transparency 0=fully transparent, 1=no transperency
 11 * @property {int} angle     Angle in degrees (0-360)
 12 * @property {bool} flipped    Flip sprite horizontally, usefull for sidescrollers
 13 * @property {string} anchor   String stating how to anchor the sprite to canvas, @see Sprite#anchor ("top_left", "center" etc)
 14 * @property {int} scale_image Scale the sprite by this factor
 15 * @property {string,gradient} color If set, draws a rectangle of dimensions rect() with specified color or gradient (linear or radial)
 16 *
 17 * @example
 18 * // create new sprite at top left of the screen, will use jaws.assets.get("foo.png")
 19 * new Sprite({image: "foo.png", x: 0, y: 0}) 
 20 * 
 21 * // sets anchor to "center" on creation
 22 * new Sprite({image: "topdownspaceship.png", anchor: "center"})
 23 *
 24 */
 25 jaws.Sprite = function Sprite(options) {
 26   if( !(this instanceof arguments.callee) ) return new arguments.callee( options );
 27   this.set(options)  
 28   this.context = options.context ? options.context : jaws.context;  // Prefer given canvas-context, fallback to jaws.context
 29 }
 30 
 31 jaws.Sprite.prototype.default_options = {
 32   x: 0, 
 33   y: 0, 
 34   alpha: 1,
 35   angle: 0,
 36   flipped: false,
 37   anchor_x: 0,
 38   anchor_y: 0,
 39   image: null,
 40   image_path: null,
 41   anchor: null,
 42   scale_image: null,
 43   damping: 1,
 44   scale_x: 1,
 45   scale_y: 1,
 46   scale: 1,
 47   color: "#ddd",
 48   width: 16,
 49   height: 16,
 50   _constructor: null,
 51   context: null,
 52   data: null
 53 }
 54 
 55 /** 
 56  * @private
 57  * Call setters from JSON object. Used to parse options.
 58  */
 59 jaws.Sprite.prototype.set = function(options) {
 60   if(jaws.isString(this.image)) this.image_path = this.image;
 61   jaws.parseOptions(this, options, this.default_options);
 62   
 63   if(this.scale)        this.scale_x = this.scale_y = this.scale;
 64   if(this.image)        this.setImage(this.image);
 65   if(this.scale_image)  this.scaleImage(this.scale_image);
 66   if(this.anchor)       this.setAnchor(this.anchor);
 67   
 68   if(!this.image && this.color && this.width && this.height) {
 69     var canvas = document.createElement('canvas');
 70     var context = canvas.getContext('2d');
 71 	  canvas.width = this.width;
 72   	canvas.height = this.height;
 73     context.fillStyle = this.color;
 74     context.fillRect(0, 0, this.width, this.height);
 75     this.image = canvas;
 76   }
 77 
 78   this.cacheOffsets()
 79 
 80   return this
 81 }
 82 
 83 /** 
 84  * @private
 85  *
 86  * Creates a new sprite from current sprites attributes()
 87  * Checks JawsJS magic property '_constructor' when deciding with which constructor to create it
 88  *
 89  */
 90 jaws.Sprite.prototype.clone = function(object) {
 91   var constructor = this._constructor ? eval(this._constructor) : this.constructor
 92   var new_sprite = new constructor( this.attributes() );
 93   new_sprite._constructor = this._constructor || this.constructor.name
 94   return new_sprite
 95 }
 96 
 97 
 98 /**
 99  * Sets image from image/canvas or asset-string ("foo.png")
100  * If asset isn't previously loaded setImage() will try to load it.
101  */
102 jaws.Sprite.prototype.setImage =      function(value) { 
103   var that = this
104 
105   // An image, great, set this.image and return
106   if(jaws.isDrawable(value)) {
107     this.image = value
108     return this.cacheOffsets() 
109   }
110   // Not an image, therefore an asset string, i.e. "ship.bmp"
111   else {
112     // Assets already loaded? Set this.image
113     if(jaws.assets.isLoaded(value)) { this.image = jaws.assets.get(value); this.cacheOffsets(); }
114 
115     // Not loaded? Load it with callback to set image.
116     else {
117       jaws.log.warn("Image '" + value + "' not preloaded with jaws.assets.add(). Image and a working sprite.rect() will be delayed.")
118       jaws.assets.load(value, {onload: function() { that.image = jaws.assets.get(value); that.cacheOffsets();} } ) 
119     }
120   }
121   return this
122 }
123 
124 /** 
125 * Steps 1 pixel towards the given X/Y. Horizontal and vertical steps are done separately between each callback.
126 * Exits when the continueStep-callback returns true for both vertical and horizontal steps or if target X/Y has been reached.
127 *
128 * @returns  {object}  Object with 2 x/y-properties indicating what plane we moved in when stepToWhile was stopped.
129 */
130 jaws.Sprite.prototype.stepToWhile = function(target_x, target_y, continueStep) { 
131   var step = 1;
132   var step_x = (target_x < this.x) ? -step : step;
133   var step_y = (target_y < this.y) ? -step : step;
134 
135   target_x = parseInt(target_x)
136   target_y = parseInt(target_y)
137 
138   var collision_x = false;
139   var collision_y = false;
140 
141   while( true ) {
142     if(collision_x === false) {
143       if(this.x != target_x)    { this.x += step_x }
144       if( !continueStep(this) ) { this.x -= step_x; collision_x = true }
145     }
146  
147     if(collision_y === false) {
148       if(this.y != target_y)    { this.y += step_y }
149       if( !continueStep(this) ) { this.y -= step_y; collision_y = true }
150     }
151 
152     if( (collision_x || this.x == target_x) && (collision_y || this.y == target_y) )
153         return {x: collision_x, y: collision_y};
154   }
155 }
156 /** 
157 * Moves with given vx/vy velocoties by stepping 1 pixel at the time. Horizontal and vertical steps are done separately between each callback.
158 * Exits when the continueStep-callback returns true for both vertical and horizontal steps or if target X/Y has been reached.
159 *
160 * @returns  {object}  Object with 2 x/y-properties indicating what plane we moved in when stepWhile was stopped.
161 */
162 jaws.Sprite.prototype.stepWhile = function(vx, vy, continueStep) { 
163   return this.stepToWhile(this.x + vx, this.y + vy, continueStep)
164 }
165 
166 /** Flips image vertically, usefull for sidescrollers when player is walking left/right */
167 jaws.Sprite.prototype.flip =          function()      { this.flipped = this.flipped ? false : true; return this }
168 jaws.Sprite.prototype.flipTo =        function(value) { this.flipped = value; return this }
169 /** Rotate sprite by value degrees */
170 jaws.Sprite.prototype.rotate =        function(value) { this.angle += value; return this }
171 /** Force an rotation-angle on sprite */
172 jaws.Sprite.prototype.rotateTo =      function(value) { this.angle = value; return this }
173 
174 /** Set x/y */
175 jaws.Sprite.prototype.moveTo =        function(x, y)  {
176   if(jaws.isArray(x) && y === undefined) {
177     y = x[1]
178     x = x[0]
179   }
180   this.x = x; 
181   this.y = y; 
182   return this;
183 }
184 /** Modify x/y */
185 jaws.Sprite.prototype.move =          function(x, y)   { 
186   if(jaws.isArray(x) && y === undefined) {
187     y = x[1]
188     x = x[0]
189   }
190 
191   if(x) this.x += x;  
192   if(y) this.y += y; 
193   return this 
194 }
195 /** 
196 * scale sprite by given factor. 1=don't scale. <1 = scale down.  1>: scale up.
197 * Modifies width/height. 
198 **/
199 jaws.Sprite.prototype.scaleAll =      function(value) { this.scale_x *= value; this.scale_y *= value; return this.cacheOffsets() }
200 /** set scale factor. ie. 2 means a doubling if sprite in both directions. */
201 jaws.Sprite.prototype.scaleTo =       function(value) { this.scale_x = this.scale_y = value; return this.cacheOffsets() }
202 /** scale sprite horizontally by scale_factor. Modifies width. */
203 jaws.Sprite.prototype.scaleWidth =    function(value) { this.scale_x *= value; return this.cacheOffsets() }
204 /** scale sprite vertically by scale_factor. Modifies height. */
205 jaws.Sprite.prototype.scaleHeight =   function(value) { this.scale_y *= value; return this.cacheOffsets() }
206 
207 /** Sets x */
208 jaws.Sprite.prototype.setX =          function(value) { this.x = value; return this }
209 /** Sets y */
210 jaws.Sprite.prototype.setY =          function(value) { this.y = value; return this }
211 
212 /** Position sprites top on the y-axis */
213 jaws.Sprite.prototype.setTop =        function(value) { this.y = value + this.top_offset; return this }
214 /** Position sprites bottom on the y-axis */
215 jaws.Sprite.prototype.setBottom =     function(value) { this.y = value - this.bottom_offset; return this }
216 /** Position sprites left side on the x-axis */
217 jaws.Sprite.prototype.setLeft =       function(value) { this.x = value + this.left_offset; return this }
218 /** Position sprites right side on the x-axis */
219 jaws.Sprite.prototype.setRight =      function(value) { this.x = value - this.right_offset; return this }
220 
221 /** Set new width. Scales sprite. */
222 jaws.Sprite.prototype.setWidth  =     function(value) { this.scale_x = value/this.image.width; return this.cacheOffsets() }
223 /** Set new height. Scales sprite. */
224 jaws.Sprite.prototype.setHeight =     function(value) { this.scale_y = value/this.image.height; return this.cacheOffsets() }
225 /** Resize sprite by adding width */
226 jaws.Sprite.prototype.resize =        function(width, height) { 
227   if(jaws.isArray(width) && height === undefined) {
228     height = width[1]
229     width = width[0]
230   }
231 
232   this.scale_x = (this.width + width) / this.image.width
233   this.scale_y = (this.height + height) / this.image.height
234   return this.cacheOffsets()
235 }
236 /** 
237  * Resize sprite to exact width/height 
238  */
239 jaws.Sprite.prototype.resizeTo =      function(width, height) {
240   if(jaws.isArray(width) && height === undefined) {
241     height = width[1]
242     width = width[0]
243   }
244 
245   this.scale_x = width / this.image.width
246   this.scale_y = height / this.image.height
247   return this.cacheOffsets()
248 }
249 
250 /**
251 * The sprites anchor could be describe as "the part of the sprite will be placed at x/y"
252 * or "when rotating, what point of the of the sprite will it rotate round"
253 *
254 * @example
255 * For example, a topdown shooter could use setAnchor("center") --> Place middle of the ship on x/y
256 * .. and a sidescroller would probably use setAnchor("center_bottom") --> Place "feet" at x/y
257 */
258 jaws.Sprite.prototype.setAnchor = function(value) {
259   var anchors = {
260     top_left: [0,0],
261     left_top: [0,0],
262     center_left: [0,0.5],
263     left_center: [0,0.5],
264     bottom_left: [0,1],
265     left_bottom: [0,1],
266     top_center: [0.5,0],
267     center_top: [0.5,0],
268     center_center: [0.5,0.5],
269     center: [0.5,0.5],
270     bottom_center: [0.5,1],
271     center_bottom: [0.5,1],
272     top_right: [1,0],
273     right_top: [1,0],
274     center_right: [1,0.5],
275     right_center: [1,0.5],
276     bottom_right: [1,1],
277     right_bottom: [1,1]
278   }
279 
280   if(a = anchors[value]) {
281     this.anchor_x = a[0]
282     this.anchor_y = a[1]
283     if(this.image) this.cacheOffsets();
284   }
285   return this
286 }
287 
288 /** @private */
289 jaws.Sprite.prototype.cacheOffsets = function() {
290   if(!this.image) { return }
291   
292   this.width = this.image.width * this.scale_x
293   this.height = this.image.height * this.scale_y
294   this.left_offset   = this.width * this.anchor_x
295   this.top_offset    = this.height * this.anchor_y
296   this.right_offset  = this.width * (1.0 - this.anchor_x)
297   this.bottom_offset = this.height * (1.0 - this.anchor_y)
298 
299   if(this.cached_rect) this.cached_rect.resizeTo(this.width, this.height);
300   return this
301 }
302 
303 /** Returns a jaws.Rect() perfectly surrouning sprite. Also cache rect in this.cached_rect. */
304 jaws.Sprite.prototype.rect = function() {
305   if(!this.cached_rect && this.width)   this.cached_rect = new jaws.Rect(this.x, this.y, this.width, this.height);
306   if(this.cached_rect)                  this.cached_rect.moveTo(this.x - this.left_offset, this.y - this.top_offset);
307   return this.cached_rect
308 } 
309 
310 /** Draw sprite on active canvas */
311 jaws.Sprite.prototype.draw = function() {
312   if(!this.image) { return this }
313 
314   this.context.save()
315   this.context.translate(this.x, this.y)
316   if(this.angle!=0) { jaws.context.rotate(this.angle * Math.PI / 180) }
317   this.flipped && this.context.scale(-1, 1)
318   this.context.globalAlpha = this.alpha
319   this.context.translate(-this.left_offset, -this.top_offset) // Needs to be separate from above translate call cause of flipped
320   this.context.drawImage(this.image, 0, 0, this.width, this.height)
321   this.context.restore()
322   return this
323 }
324 
325 /**
326  * Scales image using hard block borders. Useful for that cute, blocky retro-feeling.
327  * Depends on gfx.js beeing loaded.
328  */
329 jaws.Sprite.prototype.scaleImage = function(factor) {
330   if(!this.image) return;
331   this.setImage( jaws.retroScaleImage(this.image, factor) )
332   return this
333 }
334 
335 /** 
336  * Returns sprite as a canvas context.
337  * For certain browsers, a canvas context is faster to work with then a pure image.
338  */
339 jaws.Sprite.prototype.asCanvasContext = function() {
340   var canvas = document.createElement("canvas")
341   canvas.width = this.width
342   canvas.height = this.height
343 
344   var context = canvas.getContext("2d")
345   if(jaws.context)  context.mozImageSmoothingEnabled = jaws.context.mozImageSmoothingEnabled;
346 
347   context.drawImage(this.image, 0, 0, this.width, this.height)
348   return context
349 }
350 
351 /** 
352  * Returns sprite as a canvas
353  */
354 jaws.Sprite.prototype.asCanvas = function() {
355   var canvas = document.createElement("canvas")
356   canvas.width = this.width
357   canvas.height = this.height
358 
359   var context = canvas.getContext("2d")
360   if(jaws.context)  context.mozImageSmoothingEnabled = jaws.context.mozImageSmoothingEnabled;
361 
362   context.drawImage(this.image, 0, 0, this.width, this.height)
363   return canvas
364 }
365 
366 jaws.Sprite.prototype.toString = function() { return "[Sprite " + this.x.toFixed(2) + ", " + this.y.toFixed(2) + ", " + this.width + ", " + this.height + "]" }
367 
368 /** returns Sprites state/properties as a pure object */
369 jaws.Sprite.prototype.attributes = function() { 
370   var object = {}                   // Starting with this.options could create circular references through "context"
371   object["_constructor"] = this._constructor || "jaws.Sprite"
372   object["x"] = parseFloat(this.x.toFixed(2))
373   object["y"] = parseFloat(this.y.toFixed(2))
374   object["image"] = this.image_path
375   object["alpha"] = this.alpha
376   object["flipped"] = this.flipped
377   object["angle"] = parseFloat(this.angle.toFixed(2))
378   object["scale_x"] = this.scale_x;
379   object["scale_y"] = this.scale_y;
380   object["anchor_x"] = this.anchor_x
381   object["anchor_y"] = this.anchor_y
382 
383   if(this.data !== null) object["data"] = jaws.clone(this.data); // For external data (for example added by the editor) that you want serialized
384 
385   return object
386 }
387 /**
388  * Load/creates sprites from given data
389  *
390  * Argument could either be
391  * - an array of Sprite objects
392  * - an array of JSON objects
393  * - a JSON.stringified string representing an array of JSON objects
394  *
395  *  @return Array of created sprite
396 *
397  */
398 jaws.Sprite.parse = function(objects) {
399   var sprites = []
400   
401   if(jaws.isArray(objects)) {
402     // If this is an array of JSON representations, parse it
403     if(objects.every(function(item) { return item._constructor })) {
404       parseArray(objects)
405     } else {
406       // This is already an array of Sprites, load it directly
407       sprites = objects
408     }
409   }
410   else if(jaws.isString(objects)) { parseArray( JSON.parse(objects) ); jaws.log.info(objects) }
411   
412   function parseArray(array) {
413     array.forEach( function(data) {
414       var constructor = data._constructor ? eval(data._constructor) : data.constructor
415       if(jaws.isFunction(constructor)) {
416         jaws.log.info("Creating " + data._constructor + "(" + data.toString() + ")", true)
417         var object = new constructor(data)
418         object._constructor = data._constructor || data.constructor.name
419         sprites.push(object);
420       }
421     });
422   }
423 
424   return sprites;
425 }
426 
427 /**
428  * returns a JSON-string representing the state of the Sprite.
429  *
430  * Use this to serialize your sprites / game objects, maybe to save in local storage or on a server
431  *
432  * jaws.game_states.Edit uses this to export all edited objects.
433  *
434  */
435 jaws.Sprite.prototype.toJSON = function() {
436   return JSON.stringify(this.attributes())
437 }
438 
439 return jaws;
440 })(jaws || {});
441 
442 // Support CommonJS require()
443 if(typeof module !== "undefined" && ('exports' in module)) { module.exports = jaws.Sprite }
444 
445 /*
446 // Chainable setters under consideration:
447 jaws.Sprite.prototype.setFlipped =        function(value) { this.flipped = value; return this }
448 jaws.Sprite.prototype.setAlpha =          function(value) { this.alpha = value; return this }
449 jaws.Sprite.prototype.setAnchorX =        function(value) { this.anchor_x = value; this.cacheOffsets(); return this }
450 jaws.Sprite.prototype.setAnchorY =        function(value) { this.anchor_y = value; this.cacheOffsets(); return this }
451 jaws.Sprite.prototype.setAngle =          function(value) { this.angle = value; return this }
452 jaws.Sprite.prototype.setScale =    function(value) { this.scale_x = this.scale_y = value; this.cacheOffsets(); return this }
453 jaws.Sprite.prototype.setScaleX =   function(value) { this.scale_x = value; this.cacheOffsets(); return this }
454 jaws.Sprite.prototype.setScaleY =   function(value) { this.scale_y = value; this.cacheOffsets(); return this }
455 jaws.Sprite.prototype.moveX =         function(x)     { this.x += x; return this }
456 jaws.Sprite.prototype.moveXTo =       function(x)     { this.x = x; return this }
457 jaws.Sprite.prototype.moveY =         function(y)     { this.y += y; return this }
458 jaws.Sprite.prototype.moveYTo =       function(y)     { this.y = y; return this }
459 jaws.Sprite.prototype.scaleWidthTo =  function(value) { this.scale_x = value; return this.cacheOffsets() }
460 jaws.Sprite.prototype.scaleHeightTo = function(value) { this.scale_y = value; return this.cachOfffsets() }
461 */
462 
463