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 /**
 28  * <p>cc.AtlasNode is a subclass of cc.Node, it knows how to render a TextureAtlas object. </p>
 29  *
 30  * <p>If you are going to render a TextureAtlas consider subclassing cc.AtlasNode (or a subclass of cc.AtlasNode)</p>
 31  *
 32  * <p>All features from cc.Node are valid</p>
 33  *
 34  * <p>You can create a cc.AtlasNode with an Atlas file, the width, the height of each item and the quantity of items to render</p>
 35  *
 36  * @class
 37  * @extends cc.Node
 38  *
 39  * @param {String} tile
 40  * @param {Number} tileWidth
 41  * @param {Number} tileHeight
 42  * @param {Number} itemsToRender
 43  * @example
 44  * var node = new cc.AtlasNode("pathOfTile", 16, 16, 1);
 45  *
 46  * @property {cc.Texture2D}     texture         - Current used texture
 47  * @property {cc.TextureAtlas}  textureAtlas    - Texture atlas for cc.AtlasNode
 48  * @property {Number}           quadsToDraw     - Number of quads to draw
 49  */
 50 cc.AtlasNode = cc.Node.extend(/** @lends cc.AtlasNode# */{
 51     textureAtlas: null,
 52     quadsToDraw: 0,
 53 
 54     //! chars per row
 55     _itemsPerRow: 0,
 56     //! chars per column
 57     _itemsPerColumn: 0,
 58     //! width of each char
 59     _itemWidth: 0,
 60     //! height of each char
 61     _itemHeight: 0,
 62 
 63     _colorUnmodified: null,
 64 
 65     // protocol variables
 66     _opacityModifyRGB: false,
 67     _blendFunc: null,
 68 
 69     // This variable is only used for CCLabelAtlas FPS display. So plz don't modify its value.
 70     _ignoreContentScaleFactor: false,
 71     _className: "AtlasNode",
 72 
 73     /**
 74      * <p>Constructor function, override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function.</p>
 75      * @param {String} tile
 76      * @param {Number} tileWidth
 77      * @param {Number} tileHeight
 78      * @param {Number} itemsToRender
 79      */
 80     ctor: function (tile, tileWidth, tileHeight, itemsToRender) {
 81         cc.Node.prototype.ctor.call(this);
 82         this._colorUnmodified = cc.color.WHITE;
 83         this._blendFunc = {src: cc.BLEND_SRC, dst: cc.BLEND_DST};
 84         this._ignoreContentScaleFactor = false;
 85 
 86         itemsToRender !== undefined && this.initWithTileFile(tile, tileWidth, tileHeight, itemsToRender);
 87     },
 88 
 89     /**
 90      * Updates the Atlas (indexed vertex array).
 91      * Empty implementation, shall be overridden in subclasses
 92      * @function
 93      */
 94     updateAtlasValues: function () {
 95         cc.log(cc._LogInfos.AtlasNode_updateAtlasValues);
 96     },
 97 
 98     /**
 99      * Get color value of the atlas node
100      * @function
101      * @return {cc.Color}
102      */
103     getColor: function () {
104         if (this._opacityModifyRGB)
105             return this._colorUnmodified;
106         return cc.Node.prototype.getColor.call(this);
107     },
108 
109     /**
110      * Set whether color should be changed with the opacity value,
111      * if true, node color will change while opacity changes.
112      * @function
113      * @param {Boolean} value
114      */
115     setOpacityModifyRGB: function (value) {
116         var oldColor = this.color;
117         this._opacityModifyRGB = value;
118         this.color = oldColor;
119     },
120 
121     /**
122      * Get whether color should be changed with the opacity value
123      * @function
124      * @return {Boolean}
125      */
126     isOpacityModifyRGB: function () {
127         return this._opacityModifyRGB;
128     },
129 
130     /**
131      * Get node's blend function
132      * @function
133      * @return {cc.BlendFunc}
134      */
135     getBlendFunc: function () {
136         return this._blendFunc;
137     },
138 
139     /**
140      * Set node's blend function
141      * This function accept either cc.BlendFunc object or source value and destination value
142      * @function
143      * @param {Number | cc.BlendFunc} src
144      * @param {Number} dst
145      */
146     setBlendFunc: function (src, dst) {
147         if (dst === undefined)
148             this._blendFunc = src;
149         else
150             this._blendFunc = {src: src, dst: dst};
151     },
152 
153     /**
154      * Set the atlas texture
155      * @function
156      * @param {cc.TextureAtlas} value The texture
157      */
158     setTextureAtlas: function (value) {
159         this.textureAtlas = value;
160     },
161 
162     /**
163      * Get the atlas texture
164      * @function
165      * @return {cc.TextureAtlas}
166      */
167     getTextureAtlas: function () {
168         return this.textureAtlas;
169     },
170 
171     /**
172      * Get the number of quads to be rendered
173      * @function
174      * @return {Number}
175      */
176     getQuadsToDraw: function () {
177         return this.quadsToDraw;
178     },
179 
180     /**
181      * Set the number of quads to be rendered
182      * @function
183      * @param {Number} quadsToDraw
184      */
185     setQuadsToDraw: function (quadsToDraw) {
186         this.quadsToDraw = quadsToDraw;
187     },
188 
189     _textureForCanvas: null,
190     _originalTexture: null,
191 
192     _uniformColor: null,
193     _colorF32Array: null,
194 
195     /**
196      * Initializes an cc.AtlasNode object with an atlas texture file name, the width, the height of each tile and the quantity of tiles to render
197      * @function
198      * @param {String} tile             The atlas texture file name
199      * @param {Number} tileWidth        The width of each tile
200      * @param {Number} tileHeight       The height of each tile
201      * @param {Number} itemsToRender    The quantity of tiles to be rendered
202      * @return {Boolean}
203      */
204     initWithTileFile: function (tile, tileWidth, tileHeight, itemsToRender) {
205         if (!tile)
206             throw "cc.AtlasNode.initWithTileFile(): title should not be null";
207         var texture = cc.textureCache.addImage(tile);
208         return this.initWithTexture(texture, tileWidth, tileHeight, itemsToRender);
209     },
210 
211     /**
212      * Initializes an CCAtlasNode with an atlas texture, the width, the height of each tile and the quantity of tiles to render
213      * @function
214      * @param {cc.Texture2D} texture    The atlas texture
215      * @param {Number} tileWidth        The width of each tile
216      * @param {Number} tileHeight       The height of each tile
217      * @param {Number} itemsToRender    The quantity of tiles to be rendered
218      * @return {Boolean}
219      */
220     initWithTexture: null,
221 
222     _initWithTextureForCanvas: function (texture, tileWidth, tileHeight, itemsToRender) {
223         this._itemWidth = tileWidth;
224         this._itemHeight = tileHeight;
225 
226         this._opacityModifyRGB = true;
227         this._originalTexture = texture;
228         if (!this._originalTexture) {
229             cc.log(cc._LogInfos.AtlasNode__initWithTexture);
230             return false;
231         }
232         this._textureForCanvas = this._originalTexture;
233         this._calculateMaxItems();
234 
235         this.quadsToDraw = itemsToRender;
236         return true;
237     },
238 
239     _initWithTextureForWebGL: function (texture, tileWidth, tileHeight, itemsToRender) {
240         this._itemWidth = tileWidth;
241         this._itemHeight = tileHeight;
242         this._colorUnmodified = cc.color.WHITE;
243         this._opacityModifyRGB = true;
244 
245         this._blendFunc.src = cc.BLEND_SRC;
246         this._blendFunc.dst = cc.BLEND_DST;
247 
248         var locRealColor = this._realColor;
249         this._colorF32Array = new Float32Array([locRealColor.r / 255.0, locRealColor.g / 255.0, locRealColor.b / 255.0, this._realOpacity / 255.0]);
250         this.textureAtlas = new cc.TextureAtlas();
251         this.textureAtlas.initWithTexture(texture, itemsToRender);
252 
253         if (!this.textureAtlas) {
254             cc.log(cc._LogInfos.AtlasNode__initWithTexture);
255             return false;
256         }
257 
258         this._updateBlendFunc();
259         this._updateOpacityModifyRGB();
260         this._calculateMaxItems();
261         this.quadsToDraw = itemsToRender;
262 
263         //shader stuff
264         this.shaderProgram = cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURE_UCOLOR);
265         this._uniformColor = cc._renderContext.getUniformLocation(this.shaderProgram.getProgram(), "u_color");
266         return true;
267     },
268 
269     /**
270      * Render function using the canvas 2d context or WebGL context, internal usage only, please do not call this function
271      * @function
272      * @param {CanvasRenderingContext2D | WebGLRenderingContext} ctx The render context
273      */
274     draw: null,
275 
276     _drawForWebGL: function (ctx) {
277         var context = ctx || cc._renderContext;
278         cc.nodeDrawSetup(this);
279         cc.glBlendFunc(this._blendFunc.src, this._blendFunc.dst);
280         if(this._uniformColor && this._colorF32Array){
281             context.uniform4fv(this._uniformColor, this._colorF32Array);
282             this.textureAtlas.drawNumberOfQuads(this.quadsToDraw, 0);
283         }
284     },
285 
286     /**
287      * Set node's color
288      * @function
289      * @param {cc.Color} color Color object created with cc.color(r, g, b).
290      */
291     setColor: null,
292 
293     _setColorForCanvas: function (color3) {
294         var locRealColor = this._realColor;
295         if ((locRealColor.r == color3.r) && (locRealColor.g == color3.g) && (locRealColor.b == color3.b))
296             return;
297         var temp = cc.color(color3.r, color3.g, color3.b);
298         this._colorUnmodified = color3;
299 
300         if (this._opacityModifyRGB) {
301             var locDisplayedOpacity = this._displayedOpacity;
302             temp.r = temp.r * locDisplayedOpacity / 255;
303             temp.g = temp.g * locDisplayedOpacity / 255;
304             temp.b = temp.b * locDisplayedOpacity / 255;
305         }
306         cc.Node.prototype.setColor.call(this, color3);
307         this._changeTextureColor();
308     },
309 
310     _changeTextureColor: function(){
311         var locTexture = this.getTexture();
312         if (locTexture && this._originalTexture) {
313             var element = this._originalTexture.getHtmlElementObj();
314             if(!element)
315                 return;
316             var locElement = locTexture.getHtmlElementObj();
317             var textureRect = cc.rect(0, 0, element.width, element.height);
318             if (locElement instanceof HTMLCanvasElement)
319                 cc.generateTintImageWithMultiply(element, this._displayedColor, textureRect, locElement);
320             else {
321                 locElement = cc.generateTintImageWithMultiply(element, this._displayedColor, textureRect);
322                 locTexture = new cc.Texture2D();
323                 locTexture.initWithElement(locElement);
324                 locTexture.handleLoadedTexture();
325                 this.setTexture(locTexture);
326             }
327         }
328     },
329 
330     _setColorForWebGL: function (color3) {
331         var temp = cc.color(color3.r, color3.g, color3.b);
332         this._colorUnmodified = color3;
333         var locDisplayedOpacity = this._displayedOpacity;
334         if (this._opacityModifyRGB) {
335             temp.r = temp.r * locDisplayedOpacity / 255;
336             temp.g = temp.g * locDisplayedOpacity / 255;
337             temp.b = temp.b * locDisplayedOpacity / 255;
338         }
339         cc.Node.prototype.setColor.call(this, color3);
340         var locDisplayedColor = this._displayedColor;
341         this._colorF32Array = new Float32Array([locDisplayedColor.r / 255.0, locDisplayedColor.g / 255.0,
342             locDisplayedColor.b / 255.0, locDisplayedOpacity / 255.0]);
343     },
344 
345     /**
346      * Set node's opacity
347      * @function
348      * @param {Number} opacity The opacity value
349      */
350     setOpacity: function (opacity) {
351     },
352 
353     _setOpacityForCanvas: function (opacity) {
354         cc.Node.prototype.setOpacity.call(this, opacity);
355         // special opacity for premultiplied textures
356         if (this._opacityModifyRGB) {
357             this.color = this._colorUnmodified;
358         }
359     },
360 
361     _setOpacityForWebGL: function (opacity) {
362         cc.Node.prototype.setOpacity.call(this, opacity);
363         // special opacity for premultiplied textures
364         if (this._opacityModifyRGB) {
365             this.color = this._colorUnmodified;
366         } else {
367             var locDisplayedColor = this._displayedColor;
368             this._colorF32Array = new Float32Array([locDisplayedColor.r / 255.0, locDisplayedColor.g / 255.0,
369                 locDisplayedColor.b / 255.0, this._displayedOpacity / 255.0]);
370         }
371     },
372 
373     /**
374      * Get the current texture
375      * @function
376      * @return {cc.Texture2D}
377      */
378     getTexture: null,
379 
380     _getTextureForCanvas: function () {
381         return  this._textureForCanvas;
382     },
383 
384     _getTextureForWebGL: function () {
385         return  this.textureAtlas.texture;
386     },
387 
388     /**
389      * Replace the current texture with a new one
390      * @function
391      * @param {cc.Texture2D} texture    The new texture
392      */
393     setTexture: null,
394 
395     _setTextureForCanvas: function (texture) {
396         this._textureForCanvas = texture;
397     },
398 
399     _setTextureForWebGL: function (texture) {
400         this.textureAtlas.texture = texture;
401         this._updateBlendFunc();
402         this._updateOpacityModifyRGB();
403     },
404 
405     _calculateMaxItems: null,
406 
407     _calculateMaxItemsForCanvas: function () {
408         var selTexture = this.texture;
409         var size = selTexture.getContentSize();
410 
411         this._itemsPerColumn = 0 | (size.height / this._itemHeight);
412         this._itemsPerRow = 0 | (size.width / this._itemWidth);
413     },
414 
415     _calculateMaxItemsForWebGL: function () {
416         var selTexture = this.texture;
417         var size = selTexture.getContentSize();
418         if (this._ignoreContentScaleFactor)
419             size = selTexture.getContentSizeInPixels();
420 
421         this._itemsPerColumn = 0 | (size.height / this._itemHeight);
422         this._itemsPerRow = 0 | (size.width / this._itemWidth);
423     },
424 
425     _updateBlendFunc: function () {
426         if (!this.textureAtlas.texture.hasPremultipliedAlpha()) {
427             this._blendFunc.src = cc.SRC_ALPHA;
428             this._blendFunc.dst = cc.ONE_MINUS_SRC_ALPHA;
429         }
430     },
431 
432     _updateOpacityModifyRGB: function () {
433         this._opacityModifyRGB = this.textureAtlas.texture.hasPremultipliedAlpha();
434     },
435 
436     _setIgnoreContentScaleFactor: function (ignoreContentScaleFactor) {
437         this._ignoreContentScaleFactor = ignoreContentScaleFactor;
438     }
439 });
440 
441 var _p = cc.AtlasNode.prototype;
442 if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
443     _p.initWithTexture = _p._initWithTextureForWebGL;
444     _p.draw = _p._drawForWebGL;
445     _p.setColor = _p._setColorForWebGL;
446     _p.setOpacity = _p._setOpacityForWebGL;
447     _p.getTexture = _p._getTextureForWebGL;
448     _p.setTexture = _p._setTextureForWebGL;
449     _p._calculateMaxItems = _p._calculateMaxItemsForWebGL;
450 } else {
451     _p.initWithTexture = _p._initWithTextureForCanvas;
452     _p.draw = cc.Node.prototype.draw;
453     _p.setColor = _p._setColorForCanvas;
454     _p.setOpacity = _p._setOpacityForCanvas;
455     _p.getTexture = _p._getTextureForCanvas;
456     _p.setTexture = _p._setTextureForCanvas;
457     _p._calculateMaxItems = _p._calculateMaxItemsForCanvas;
458     if(!cc.sys._supportCanvasNewBlendModes)
459         _p._changeTextureColor = function(){
460             var locElement, locTexture = this.getTexture();
461             if (locTexture && this._originalTexture) {
462                 locElement = locTexture.getHtmlElementObj();
463                 if (!locElement)
464                     return;
465                 var element = this._originalTexture.getHtmlElementObj();
466                 var cacheTextureForColor = cc.textureCache.getTextureColors(element);
467                 if (cacheTextureForColor) {
468                     var textureRect = cc.rect(0, 0, element.width, element.height);
469                     if (locElement instanceof HTMLCanvasElement)
470                         cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor, textureRect, locElement);
471                     else {
472                         locElement = cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor, textureRect);
473                         locTexture = new cc.Texture2D();
474                         locTexture.initWithElement(locElement);
475                         locTexture.handleLoadedTexture();
476                         this.setTexture(locTexture);
477                     }
478                 }
479             }
480         };
481 }
482 
483 // Override properties
484 cc.defineGetterSetter(_p, "opacity", _p.getOpacity, _p.setOpacity);
485 cc.defineGetterSetter(_p, "color", _p.getColor, _p.setColor);
486 
487 // Extended properties
488 /** @expose */
489 _p.texture;
490 cc.defineGetterSetter(_p, "texture", _p.getTexture, _p.setTexture);
491 /** @expose */
492 _p.textureAtlas;
493 /** @expose */
494 _p.quadsToDraw;
495 
496 
497 /**
498  * Creates a cc.AtlasNode with an Atlas file the width and height of each item and the quantity of items to render
499  * @deprecated since v3.0, please use new construction instead
500  * @function
501  * @static
502  * @param {String} tile
503  * @param {Number} tileWidth
504  * @param {Number} tileHeight
505  * @param {Number} itemsToRender
506  * @return {cc.AtlasNode}
507  */
508 cc.AtlasNode.create = function (tile, tileWidth, tileHeight, itemsToRender) {
509     return new cc.AtlasNode(tile, tileWidth, tileHeight, itemsToRender);
510 };
511 
512