1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 /**
  8  * @class TextureAtlas纹理集是将许多小的纹理图片整合到一起的一张大图。这个类可根据一个纹理集数据读取纹理小图、精灵动画等。
  9  * @param {Object} atlasData 纹理集数据。它可包含如下数据:
 10  * <ul>
 11  * <li><b>image</b> - 纹理集图片。必需。</li>
 12  * <li><b>width</b> - 纹理集图片宽度。若frames数据为Object时,此属性必需。</li>
 13  * <li><b>height</b> - 纹理集图片高度。若frames数据为Object时,此属性必需。</li>
 14  * <li><b>frames</b> - 纹理集帧数据,可以是Array或Object。必需。
 15  * <ul>
 16  * <li>若为Array,则每项均为一个纹理图片帧数据,如:[[0, 0, 50, 50], [0, 50, 50, 50]。</li>
 17  * <li>若为Object,则需包含frameWidth(帧宽)、frameHeight(帧高)、numFrames(帧数) 属性。</li>
 18  * </ul>
 19  * </li>
 20  * <li><b>sprites</b> - 纹理集精灵动画定义,其每个值均定义一个精灵。为Object对象。可选。
 21  * <ul>
 22  * <li>若为Number,即此精灵只包含一帧,此帧为帧数据中索引为当前值的帧。如:sprites:{'foo':1}。</li>
 23  * <li>若为Array,则每项均为一个帧的索引值。如:sprites:{'foo':[0, 1, 2, 3]}。</li>
 24  * <li>若为Object,则需包含from(起始帧索引值)、to(末帧索引值) 属性。</li>
 25  * </ul>
 26  * </li>
 27  * </ul>
 28  * @module hilo/util/TextureAtlas
 29  * @requires hilo/core/Class
 30  */
 31 var TextureAtlas = (function(){
 32 
 33 return Class.create(/** @lends TextureAtlas.prototype */{
 34     constructor: function(atlasData){
 35         this._frames = parseTextureFrames(atlasData);
 36         this._sprites = parseTextureSprites(atlasData, this._frames);
 37     },
 38 
 39     _frames: null,
 40     _sprites: null,
 41 
 42     /**
 43      * 获取指定索引位置index的帧数据。
 44      * @param {Int} index 要获取帧的索引位置。
 45      * @returns {Object} 帧数据。
 46      */
 47     getFrame: function(index){
 48         var frames = this._frames;
 49         return frames && frames[index];
 50     },
 51 
 52     /**
 53      * 获取指定id的精灵数据。
 54      * @param {String} id 要获取精灵的id。
 55      * @returns {Object} 精灵数据。
 56      */
 57     getSprite: function(id){
 58         var sprites = this._sprites;
 59         return sprites && sprites[id];
 60     },
 61 
 62     Statics: /** @lends TextureAtlas */ {
 63         /**
 64          * Shorthand method to create spirte frames
 65          * @param {String|Array} name Name of one animation|a group of animation
 66          * @param {String} frames Frames message, eg:"0-5" means frame 0 to frame 5.
 67          * @param {Number} w The width of each frame.
 68          * @param {Number} h The height of each frame.
 69          * @param {Boolean} loop Is play in loop.
 70          * @param {Number} duration The time between each frame. default value is 1 (Frame), but if timeBased is true, default value will be duration(milli-second).
 71          * @example
 72          *  //demo1 make one animation
 73          *  createSpriteFrames("walk", "0-5,8,9", meImg, 55, 88, true, 1);
 74          *  //demo2 make a group of animation
 75          *  createSpriteFrames([
 76          *    ["walk", "0-5,8,9", meImg, 55, 88, true, 1],
 77          *    ["jump", "0-5", meImg, 55, 88, false, 1]
 78          *  ]);
 79         */
 80         createSpriteFrames:function(name, frames, img, w, h, loop, duration){
 81             var i, l;
 82             if(Object.prototype.toString.call(name) === "[object Array]"){
 83                 var res = [];
 84                 for(i = 0, l = name.length;i < l;i ++){
 85                     res = res.concat(this.createSpriteFrames.apply(this, name[i]));
 86                 }
 87                 return res;
 88             }
 89             else{
 90                 if(typeof(frames) === "string"){
 91                     var all = frames.split(",");
 92                     frames = [];
 93                     for(var j = 0, jl = all.length;j < jl;j ++){
 94                         var temp = all[j].split("-");
 95                         if(temp.length == 1){
 96                             frames.push(parseInt(temp[0]));
 97                         }
 98                         else{
 99                             for(i = parseInt(temp[0]), l = parseInt(temp[1]);i <= l;i ++){
100                                 frames.push(i);
101                             }
102                         }
103                     }
104                 }
105 
106                 var col = Math.floor(img.width/w);
107                 for(i = 0;i < frames.length;i ++){
108                     var n = frames[i];
109                     frames[i] = {
110                         rect:[w*(n%col), h*Math.floor(n/col), w, h],
111                         image:img,
112                         duration:duration
113                     };
114                 }
115                 frames[0].name = name;
116                 if(loop){
117                     frames[frames.length-1].next = name;
118                 }
119                 else{
120                     frames[frames.length-1].stop = true;
121                 }
122                 return frames;
123             }
124         }
125     }
126 });
127 
128 /**
129  * Parse texture frames
130  * @private
131  */
132 function parseTextureFrames(atlasData){
133     var i, len;
134     var frameData = atlasData.frames;
135     if(!frameData) return null;
136 
137     var frames = [], obj;
138 
139     if(frameData instanceof Array){ //frames by array
140         for(i = 0, len = frameData.length; i < len; i++){
141             obj = frameData[i];
142             frames[i] = {
143                 image: atlasData.image,
144                 rect: obj
145             };
146         }
147     }else{ //frames by object
148         var frameWidth = frameData.frameWidth;
149         var frameHeight = frameData.frameHeight;
150         var cols = atlasData.width / frameWidth | 0;
151         var rows = atlasData.height / frameHeight | 0;
152         var numFrames = frameData.numFrames || cols * rows;
153         for(i = 0; i < numFrames; i++){
154             frames[i] = {
155                 image: atlasData.image,
156                 rect: [i%cols*frameWidth, (i/cols|0)*frameHeight, frameWidth, frameHeight]
157             };
158         }
159     }
160 
161     return frames;
162 }
163 
164 /**
165  * Parse texture sprites
166  * @private
167  */
168 function parseTextureSprites(atlasData, frames){
169     var i, len;
170     var spriteData = atlasData.sprites;
171     if(!spriteData) return null;
172 
173     var sprites = {}, sprite, spriteFrames, spriteFrame;
174 
175     for(var s in spriteData){
176         sprite = spriteData[s];
177         if(isNumber(sprite)){ //single frame
178             spriteFrames = translateSpriteFrame(frames[sprite]);
179         }else if(sprite instanceof Array){ //frames by array
180             spriteFrames = [];
181             for(i = 0, len = sprite.length; i < len; i++){
182                 var spriteObj = sprite[i], frameObj;
183                 if(isNumber(spriteObj)){
184                     spriteFrame = translateSpriteFrame(frames[spriteObj]);
185                 }else{
186                     frameObj = spriteObj.rect;
187                     if(isNumber(frameObj)) frameObj = frames[spriteObj.rect];
188                     spriteFrame = translateSpriteFrame(frameObj, spriteObj);
189                 }
190                 spriteFrames[i] = spriteFrame;
191             }
192         }else{ //frames by object
193             spriteFrames = [];
194             for(i = sprite.from; i <= sprite.to; i++){
195                 spriteFrames[i - sprite.from] = translateSpriteFrame(frames[i], sprite[i]);
196             }
197         }
198         sprites[s] = spriteFrames;
199     }
200 
201     return sprites;
202 }
203 
204 function translateSpriteFrame(frameObj, spriteObj){
205     var spriteFrame = {
206         image: frameObj.image,
207         rect: frameObj.rect
208     };
209 
210     if(spriteObj){
211         spriteFrame.name = spriteObj.name || null;
212         spriteFrame.duration = spriteObj.duration || 0;
213         spriteFrame.stop = !!spriteObj.stop;
214         spriteFrame.next = spriteObj.next || null;
215     }
216 
217     return spriteFrame;
218 }
219 
220 function isNumber(value){
221     return typeof value === 'number';
222 }
223 
224 })();