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