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         context.uniform4fv(this._uniformColor, this._colorF32Array);
251         this.textureAtlas.drawNumberOfQuads(this.quadsToDraw, 0);
252     },
253 
254     /**
255      * @function
256      * @param {cc.Color} color3
257      */
258     setColor: null,
259 
260     _setColorForCanvas: function (color3) {
261         var locRealColor = this._realColor;
262         if ((locRealColor.r == color3.r) && (locRealColor.g == color3.g) && (locRealColor.b == color3.b))
263             return;
264         var temp = cc.color(color3.r, color3.g, color3.b);
265         this._colorUnmodified = color3;
266 
267         if (this._opacityModifyRGB) {
268             var locDisplayedOpacity = this._displayedOpacity;
269             temp.r = temp.r * locDisplayedOpacity / 255;
270             temp.g = temp.g * locDisplayedOpacity / 255;
271             temp.b = temp.b * locDisplayedOpacity / 255;
272         }
273         cc.NodeRGBA.prototype.setColor.call(this, color3);
274 
275         if (this.texture) {
276             var element = this._originalTexture.getHtmlElementObj();
277             if (!element)
278                 return;
279             var cacheTextureForColor = cc.textureCache.getTextureColors(element);
280             if (cacheTextureForColor) {
281                 var textureRect = cc.rect(0, 0, element.width, element.height);
282                 element = cc.generateTintImage(element, cacheTextureForColor, this._realColor, textureRect);
283                 var locTexture = new cc.Texture2D();
284                 locTexture.initWithElement(element);
285                 locTexture.handleLoadedTexture();
286                 this.texture = locTexture;
287             }
288         }
289     },
290 
291     _setColorForWebGL: function (color3) {
292         var temp = cc.color(color3.r, color3.g, color3.b);
293         this._colorUnmodified = color3;
294         var locDisplayedOpacity = this._displayedOpacity;
295         if (this._opacityModifyRGB) {
296             temp.r = temp.r * locDisplayedOpacity / 255;
297             temp.g = temp.g * locDisplayedOpacity / 255;
298             temp.b = temp.b * locDisplayedOpacity / 255;
299         }
300         cc.NodeRGBA.prototype.setColor.call(this, color3);
301         var locDisplayedColor = this._displayedColor;
302         this._colorF32Array = new Float32Array([locDisplayedColor.r / 255.0, locDisplayedColor.g / 255.0,
303             locDisplayedColor.b / 255.0, locDisplayedOpacity / 255.0]);
304     },
305 
306     /**
307      * @function
308      * @param {Number} opacity
309      */
310     setOpacity: function (opacity) {
311     },
312 
313     _setOpacityForCanvas: function (opacity) {
314         cc.NodeRGBA.prototype.setOpacity.call(this, opacity);
315         // special opacity for premultiplied textures
316         if (this._opacityModifyRGB) {
317             this.color = this._colorUnmodified;
318         }
319     },
320 
321     _setOpacityForWebGL: function (opacity) {
322         cc.NodeRGBA.prototype.setOpacity.call(this, opacity);
323         // special opacity for premultiplied textures
324         if (this._opacityModifyRGB) {
325             this.color = this._colorUnmodified;
326         } else {
327             var locDisplayedColor = this._displayedColor;
328             this._colorF32Array = new Float32Array([locDisplayedColor.r / 255.0, locDisplayedColor.g / 255.0,
329                 locDisplayedColor.b / 255.0, this._displayedOpacity / 255.0]);
330         }
331     },
332 
333     // cc.Texture protocol
334     /**
335      * returns the used texture
336      * @function
337      * @return {cc.Texture2D}
338      */
339     getTexture: null,
340 
341     _getTextureForCanvas: function () {
342         return  this._textureForCanvas;
343     },
344 
345     _getTextureForWebGL: function () {
346         return  this.textureAtlas.texture;
347     },
348 
349     /**
350      * sets a new texture. it will be retained
351      * @function
352      * @param {cc.Texture2D} texture
353      */
354     setTexture: null,
355 
356     _setTextureForCanvas: function (texture) {
357         this._textureForCanvas = texture;
358     },
359 
360     _setTextureForWebGL: function (texture) {
361         this.textureAtlas.texture = texture;
362         this._updateBlendFunc();
363         this._updateOpacityModifyRGB();
364     },
365 
366     _calculateMaxItems: null,
367 
368     _calculateMaxItemsForCanvas: function () {
369         var selTexture = this.texture;
370         var size = selTexture.getContentSize();
371 
372         this._itemsPerColumn = 0 | (size.height / this._itemHeight);
373         this._itemsPerRow = 0 | (size.width / this._itemWidth);
374     },
375 
376     _calculateMaxItemsForWebGL: function () {
377         var selTexture = this.texture;
378         var size = selTexture.getContentSize();
379         if (this._ignoreContentScaleFactor)
380             size = selTexture.getContentSizeInPixels();
381 
382         this._itemsPerColumn = 0 | (size.height / this._itemHeight);
383         this._itemsPerRow = 0 | (size.width / this._itemWidth);
384     },
385 
386     _updateBlendFunc: function () {
387         if (!this.textureAtlas.texture.hasPremultipliedAlpha()) {
388             this._blendFunc.src = cc.SRC_ALPHA;
389             this._blendFunc.dst = cc.ONE_MINUS_SRC_ALPHA;
390         }
391     },
392 
393     _updateOpacityModifyRGB: function () {
394         this._opacityModifyRGB = this.textureAtlas.texture.hasPremultipliedAlpha();
395     },
396 
397     _setIgnoreContentScaleFactor: function (ignoreContentScaleFactor) {
398         this._ignoreContentScaleFactor = ignoreContentScaleFactor;
399     }
400 });
401 
402 var _p = cc.AtlasNode.prototype;
403 if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
404     _p.initWithTexture = _p._initWithTextureForWebGL;
405     _p.draw = _p._drawForWebGL;
406     _p.setColor = _p._setColorForWebGL;
407     _p.setOpacity = _p._setOpacityForWebGL;
408     _p.getTexture = _p._getTextureForWebGL;
409     _p.setTexture = _p._setTextureForWebGL;
410     _p._calculateMaxItems = _p._calculateMaxItemsForWebGL;
411 } else {
412     _p.initWithTexture = _p._initWithTextureForCanvas;
413     _p.draw = cc.Node.prototype.draw;
414     _p.setColor = _p._setColorForCanvas;
415     _p.setOpacity = _p._setOpacityForCanvas;
416     _p.getTexture = _p._getTextureForCanvas;
417     _p.setTexture = _p._setTextureForCanvas;
418     _p._calculateMaxItems = _p._calculateMaxItemsForCanvas;
419 }
420 
421 // Override properties
422 cc.defineGetterSetter(_p, "opacity", _p.getOpacity, _p.setOpacity);
423 cc.defineGetterSetter(_p, "color", _p.getColor, _p.setColor);
424 
425 // Extended properties
426 /** @expose */
427 _p.texture;
428 cc.defineGetterSetter(_p, "texture", _p.getTexture, _p.setTexture);
429 /** @expose */
430 _p.textureAtlas;
431 /** @expose */
432 _p.quadsToDraw;
433 
434 
435 /** creates a cc.AtlasNode with an Atlas file the width and height of each item and the quantity of items to render
436  * @param {String} tile
437  * @param {Number} tileWidth
438  * @param {Number} tileHeight
439  * @param {Number} itemsToRender
440  * @return {cc.AtlasNode}
441  * @example
442  * // example
443  * var node = cc.AtlasNode.create("pathOfTile", 16, 16, 1);
444  */
445 cc.AtlasNode.create = function (tile, tileWidth, tileHeight, itemsToRender) {
446     return new cc.AtlasNode(tile, tileWidth, tileHeight, itemsToRender);
447 };
448 
449