1 /****************************************************************************
  2  Copyright (c) 2008-2010 Ricardo Quesada
  3  Copyright (c) 2011-2012 cocos2d-x.org
  4  Copyright (c) 2013-2014 Chukong Technologies Inc.
  5 
  6  http://www.cocos2d-x.org
  7 
  8  Permission is hereby granted, free of charge, to any person obtaining a copy
  9  of this software and associated documentation files (the "Software"), to deal
 10  in the Software without restriction, including without limitation the rights
 11  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  copies of the Software, and to permit persons to whom the Software is
 13  furnished to do so, subject to the following conditions:
 14 
 15  The above copyright notice and this permission notice shall be included in
 16  all copies or substantial portions of the Software.
 17 
 18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  THE SOFTWARE.
 25  ****************************************************************************/
 26 
 27 /** <p> cc.AtlasNode is a subclass of cc.Node that implements the cc.RGBAProtocol and<br/>
 28  * cc.TextureProtocol protocol</p>
 29  *
 30  * <p> It knows how to render a TextureAtlas object.  <br/>
 31  * If you are going to render a TextureAtlas consider subclassing cc.AtlasNode (or a subclass of cc.AtlasNode)</p>
 32  *
 33  * <p> All features from cc.Node are valid, plus the following features:  <br/>
 34  * - opacity and RGB colors </p>
 35  * @class
 36  * @extends cc.NodeRGBA
 37  *
 38  * @property {cc.Texture2D}     texture         - Current used texture
 39  * @property {cc.TextureAtlas}  textureAtlas    - Texture atlas for cc.AtlasNode
 40  * @property {Number}           quadsToDraw     - Number of quads to draw
 41  *
 42  */
 43 cc.AtlasNode = cc.NodeRGBA.extend(/** @lends cc.AtlasNode# */{
 44     textureAtlas: null,
 45     quadsToDraw: 0,
 46 
 47     RGBAProtocol: true,
 48     //! chars per row
 49     _itemsPerRow: 0,
 50     //! chars per column
 51     _itemsPerColumn: 0,
 52     //! width of each char
 53     _itemWidth: 0,
 54     //! height of each char
 55     _itemHeight: 0,
 56 
 57     _colorUnmodified: null,
 58 
 59     // protocol variables
 60     _opacityModifyRGB: false,
 61     _blendFunc: null,
 62 
 63     _ignoreContentScaleFactor: false,                               // This variable is only used for CCLabelAtlas FPS display. So plz don't modify its value.
 64     _className: "AtlasNode",
 65 
 66     /**
 67      * Creates a cc.AtlasNode with an Atlas file the width and height of each item and the quantity of items to render
 68      * Constructor of cc.AtlasNode
 69      * @param {String} tile
 70      * @param {Number} tileWidth
 71      * @param {Number} tileHeight
 72      * @param {Number} itemsToRender
 73      * @example
 74      * var node = new cc.AtlasNode("pathOfTile", 16, 16, 1);
 75      */
 76     ctor: function (tile, tileWidth, tileHeight, itemsToRender) {
 77         cc.NodeRGBA.prototype.ctor.call(this);
 78         this._colorUnmodified = cc.color.WHITE;
 79         this._blendFunc = {src: cc.BLEND_SRC, dst: cc.BLEND_DST};
 80         this._ignoreContentScaleFactor = false;
 81 
 82         itemsToRender !== undefined && this.initWithTileFile(tile, tileWidth, tileHeight, itemsToRender);
 83     },
 84 
 85     /** updates the Atlas (indexed vertex array).
 86      * Shall be overridden in subclasses
 87      */
 88     updateAtlasValues: function () {
 89         cc.log(cc._LogInfos.AtlasNode_updateAtlasValues);
 90     },
 91 
 92     /** cc.AtlasNode - RGBA protocol
 93      * @return {cc.Color}
 94      */
 95     getColor: function () {
 96         if (this._opacityModifyRGB)
 97             return this._colorUnmodified;
 98         return cc.NodeRGBA.prototype.getColor.call(this);
 99     },
100 
101     /**
102      * @param {Boolean} value
103      */
104     setOpacityModifyRGB: function (value) {
105         var oldColor = this.color;
106         this._opacityModifyRGB = value;
107         this.color = oldColor;
108     },
109 
110     /**
111      * @return {Boolean}
112      */
113     isOpacityModifyRGB: function () {
114         return this._opacityModifyRGB;
115     },
116 
117     /** cc.AtlasNode - CocosNodeTexture protocol
118      * @return {cc.BlendFunc}
119      */
120     getBlendFunc: function () {
121         return this._blendFunc;
122     },
123 
124     /**
125      * BlendFunc setter
126      * @param {Number | cc.BlendFunc} src
127      * @param {Number} dst
128      */
129     setBlendFunc: function (src, dst) {
130         if (dst === undefined)
131             this._blendFunc = src;
132         else
133             this._blendFunc = {src: src, dst: dst};
134     },
135 
136     /**
137      * @param {cc.TextureAtlas} value
138      */
139     setTextureAtlas: function (value) {
140         this.textureAtlas = value;
141     },
142 
143     /**
144      * @return {cc.TextureAtlas}
145      */
146     getTextureAtlas: function () {
147         return this.textureAtlas;
148     },
149 
150     /**
151      * @return {Number}
152      */
153     getQuadsToDraw: function () {
154         return this.quadsToDraw;
155     },
156 
157     /**
158      * @param {Number} quadsToDraw
159      */
160     setQuadsToDraw: function (quadsToDraw) {
161         this.quadsToDraw = quadsToDraw;
162     },
163 
164     _textureForCanvas: null,
165     _originalTexture: null,
166 
167     _uniformColor: null,
168     _colorF32Array: null,
169 
170     /** initializes an cc.AtlasNode  with an Atlas file the width and height of each item and the quantity of items to render
171      * @param {String} tile
172      * @param {Number} tileWidth
173      * @param {Number} tileHeight
174      * @param {Number} itemsToRender
175      * @return {Boolean}
176      */
177     initWithTileFile: function (tile, tileWidth, tileHeight, itemsToRender) {
178         if (!tile)
179             throw "cc.AtlasNode.initWithTileFile(): title should not be null";
180         var texture = cc.textureCache.addImage(tile);
181         return this.initWithTexture(texture, tileWidth, tileHeight, itemsToRender);
182     },
183 
184     /**
185      * initializes an CCAtlasNode  with a texture the width and height of each item measured in points and the quantity of items to render
186      * @param {cc.Texture2D} texture
187      * @param {Number} tileWidth
188      * @param {Number} tileHeight
189      * @param {Number} itemsToRender
190      * @return {Boolean}
191      */
192     initWithTexture: null,
193 
194     _initWithTextureForCanvas: function (texture, tileWidth, tileHeight, itemsToRender) {
195         this._itemWidth = tileWidth;
196         this._itemHeight = tileHeight;
197 
198         this._opacityModifyRGB = true;
199         this._originalTexture = texture;
200         if (!this._originalTexture) {
201             cc.log(cc._LogInfos.AtlasNode__initWithTexture);
202             return false;
203         }
204         this._textureForCanvas = this._originalTexture;
205         this._calculateMaxItems();
206 
207         this.quadsToDraw = itemsToRender;
208         return true;
209     },
210 
211     _initWithTextureForWebGL: function (texture, tileWidth, tileHeight, itemsToRender) {
212         this._itemWidth = tileWidth;
213         this._itemHeight = tileHeight;
214         this._colorUnmodified = cc.color.WHITE;
215         this._opacityModifyRGB = true;
216 
217         this._blendFunc.src = cc.BLEND_SRC;
218         this._blendFunc.dst = cc.BLEND_DST;
219 
220         var locRealColor = this._realColor;
221         this._colorF32Array = new Float32Array([locRealColor.r / 255.0, locRealColor.g / 255.0, locRealColor.b / 255.0, this._realOpacity / 255.0]);
222         this.textureAtlas = new cc.TextureAtlas();
223         this.textureAtlas.initWithTexture(texture, itemsToRender);
224 
225         if (!this.textureAtlas) {
226             cc.log(cc._LogInfos.AtlasNode__initWithTexture);
227             return false;
228         }
229 
230         this._updateBlendFunc();
231         this._updateOpacityModifyRGB();
232         this._calculateMaxItems();
233         this.quadsToDraw = itemsToRender;
234 
235         //shader stuff
236         this.shaderProgram = cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURE_UCOLOR);
237         this._uniformColor = cc._renderContext.getUniformLocation(this.shaderProgram.getProgram(), "u_color");
238         return true;
239     },
240 
241     draw: null,
242 
243     /**
244      * @param {WebGLRenderingContext} ctx renderContext
245      */
246     _drawForWebGL: function (ctx) {
247         var context = ctx || cc._renderContext;
248         cc.nodeDrawSetup(this);
249         cc.glBlendFunc(this._blendFunc.src, this._blendFunc.dst);
250         if(this._uniformColor && this._colorF32Array){
251             context.uniform4fv(this._uniformColor, this._colorF32Array);
252             this.textureAtlas.drawNumberOfQuads(this.quadsToDraw, 0);
253         }
254     },
255 
256     /**
257      * @function
258      * @param {cc.Color} color3
259      */
260     setColor: null,
261 
262     _setColorForCanvas: function (color3) {
263         var locRealColor = this._realColor;
264         if ((locRealColor.r == color3.r) && (locRealColor.g == color3.g) && (locRealColor.b == color3.b))
265             return;
266         var temp = cc.color(color3.r, color3.g, color3.b);
267         this._colorUnmodified = color3;
268 
269         if (this._opacityModifyRGB) {
270             var locDisplayedOpacity = this._displayedOpacity;
271             temp.r = temp.r * locDisplayedOpacity / 255;
272             temp.g = temp.g * locDisplayedOpacity / 255;
273             temp.b = temp.b * locDisplayedOpacity / 255;
274         }
275         cc.NodeRGBA.prototype.setColor.call(this, color3);
276 
277         if (this.texture) {
278             var element = this._originalTexture.getHtmlElementObj();
279             if (!element)
280                 return;
281             var cacheTextureForColor = cc.textureCache.getTextureColors(element);
282             if (cacheTextureForColor) {
283                 var textureRect = cc.rect(0, 0, element.width, element.height);
284                 element = cc.generateTintImage(element, cacheTextureForColor, this._realColor, textureRect);
285                 var locTexture = new cc.Texture2D();
286                 locTexture.initWithElement(element);
287                 locTexture.handleLoadedTexture();
288                 this.texture = locTexture;
289             }
290         }
291     },
292 
293     _setColorForWebGL: function (color3) {
294         var temp = cc.color(color3.r, color3.g, color3.b);
295         this._colorUnmodified = color3;
296         var locDisplayedOpacity = this._displayedOpacity;
297         if (this._opacityModifyRGB) {
298             temp.r = temp.r * locDisplayedOpacity / 255;
299             temp.g = temp.g * locDisplayedOpacity / 255;
300             temp.b = temp.b * locDisplayedOpacity / 255;
301         }
302         cc.NodeRGBA.prototype.setColor.call(this, color3);
303         var locDisplayedColor = this._displayedColor;
304         this._colorF32Array = new Float32Array([locDisplayedColor.r / 255.0, locDisplayedColor.g / 255.0,
305             locDisplayedColor.b / 255.0, locDisplayedOpacity / 255.0]);
306     },
307 
308     /**
309      * @function
310      * @param {Number} opacity
311      */
312     setOpacity: function (opacity) {
313     },
314 
315     _setOpacityForCanvas: function (opacity) {
316         cc.NodeRGBA.prototype.setOpacity.call(this, opacity);
317         // special opacity for premultiplied textures
318         if (this._opacityModifyRGB) {
319             this.color = this._colorUnmodified;
320         }
321     },
322 
323     _setOpacityForWebGL: function (opacity) {
324         cc.NodeRGBA.prototype.setOpacity.call(this, opacity);
325         // special opacity for premultiplied textures
326         if (this._opacityModifyRGB) {
327             this.color = this._colorUnmodified;
328         } else {
329             var locDisplayedColor = this._displayedColor;
330             this._colorF32Array = new Float32Array([locDisplayedColor.r / 255.0, locDisplayedColor.g / 255.0,
331                 locDisplayedColor.b / 255.0, this._displayedOpacity / 255.0]);
332         }
333     },
334 
335     // cc.Texture protocol
336     /**
337      * returns the used texture
338      * @function
339      * @return {cc.Texture2D}
340      */
341     getTexture: null,
342 
343     _getTextureForCanvas: function () {
344         return  this._textureForCanvas;
345     },
346 
347     _getTextureForWebGL: function () {
348         return  this.textureAtlas.texture;
349     },
350 
351     /**
352      * sets a new texture. it will be retained
353      * @function
354      * @param {cc.Texture2D} texture
355      */
356     setTexture: null,
357 
358     _setTextureForCanvas: function (texture) {
359         this._textureForCanvas = texture;
360     },
361 
362     _setTextureForWebGL: function (texture) {
363         this.textureAtlas.texture = texture;
364         this._updateBlendFunc();
365         this._updateOpacityModifyRGB();
366     },
367 
368     _calculateMaxItems: null,
369 
370     _calculateMaxItemsForCanvas: function () {
371         var selTexture = this.texture;
372         var size = selTexture.getContentSize();
373 
374         this._itemsPerColumn = 0 | (size.height / this._itemHeight);
375         this._itemsPerRow = 0 | (size.width / this._itemWidth);
376     },
377 
378     _calculateMaxItemsForWebGL: function () {
379         var selTexture = this.texture;
380         var size = selTexture.getContentSize();
381         if (this._ignoreContentScaleFactor)
382             size = selTexture.getContentSizeInPixels();
383 
384         this._itemsPerColumn = 0 | (size.height / this._itemHeight);
385         this._itemsPerRow = 0 | (size.width / this._itemWidth);
386     },
387 
388     _updateBlendFunc: function () {
389         if (!this.textureAtlas.texture.hasPremultipliedAlpha()) {
390             this._blendFunc.src = cc.SRC_ALPHA;
391             this._blendFunc.dst = cc.ONE_MINUS_SRC_ALPHA;
392         }
393     },
394 
395     _updateOpacityModifyRGB: function () {
396         this._opacityModifyRGB = this.textureAtlas.texture.hasPremultipliedAlpha();
397     },
398 
399     _setIgnoreContentScaleFactor: function (ignoreContentScaleFactor) {
400         this._ignoreContentScaleFactor = ignoreContentScaleFactor;
401     }
402 });
403 
404 var _p = cc.AtlasNode.prototype;
405 if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
406     _p.initWithTexture = _p._initWithTextureForWebGL;
407     _p.draw = _p._drawForWebGL;
408     _p.setColor = _p._setColorForWebGL;
409     _p.setOpacity = _p._setOpacityForWebGL;
410     _p.getTexture = _p._getTextureForWebGL;
411     _p.setTexture = _p._setTextureForWebGL;
412     _p._calculateMaxItems = _p._calculateMaxItemsForWebGL;
413 } else {
414     _p.initWithTexture = _p._initWithTextureForCanvas;
415     _p.draw = cc.Node.prototype.draw;
416     _p.setColor = _p._setColorForCanvas;
417     _p.setOpacity = _p._setOpacityForCanvas;
418     _p.getTexture = _p._getTextureForCanvas;
419     _p.setTexture = _p._setTextureForCanvas;
420     _p._calculateMaxItems = _p._calculateMaxItemsForCanvas;
421 }
422 
423 // Override properties
424 cc.defineGetterSetter(_p, "opacity", _p.getOpacity, _p.setOpacity);
425 cc.defineGetterSetter(_p, "color", _p.getColor, _p.setColor);
426 
427 // Extended properties
428 /** @expose */
429 _p.texture;
430 cc.defineGetterSetter(_p, "texture", _p.getTexture, _p.setTexture);
431 /** @expose */
432 _p.textureAtlas;
433 /** @expose */
434 _p.quadsToDraw;
435 
436 
437 /** creates a cc.AtlasNode with an Atlas file the width and height of each item and the quantity of items to render
438  * @param {String} tile
439  * @param {Number} tileWidth
440  * @param {Number} tileHeight
441  * @param {Number} itemsToRender
442  * @return {cc.AtlasNode}
443  * @example
444  * // example
445  * var node = cc.AtlasNode.create("pathOfTile", 16, 16, 1);
446  */
447 cc.AtlasNode.create = function (tile, tileWidth, tileHeight, itemsToRender) {
448     return new cc.AtlasNode(tile, tileWidth, tileHeight, itemsToRender);
449 };
450 
451