1 /****************************************************************************
  2  Copyright (c) 2010-2012 cocos2d-x.org
  3  Copyright (c) 2008-2010 Ricardo Quesada
  4  Copyright (c) 2011      Zynga 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 cc.AtlasNode = cc.NodeRGBA.extend(/** @lends cc.AtlasNode# */{
 39     RGBAProtocol:true,
 40     //! chars per row
 41     _itemsPerRow:0,
 42     //! chars per column
 43     _itemsPerColumn:0,
 44     //! width of each char
 45     _itemWidth:0,
 46     //! height of each char
 47     _itemHeight:0,
 48 
 49     _colorUnmodified:null,
 50     _textureAtlas:null,
 51 
 52     // protocol variables
 53     _opacityModifyRGB:false,
 54     _blendFunc:null,
 55 
 56     // quads to draw
 57     _quadsToDraw:0,
 58     _ignoreContentScaleFactor:false,                               // This variable is only used for CCLabelAtlas FPS display. So plz don't modify its value.
 59 
 60     ctor:function () {
 61         cc.NodeRGBA.prototype.ctor.call(this);
 62         this._colorUnmodified = cc.white();
 63         this._blendFunc = {src:cc.BLEND_SRC, dst:cc.BLEND_DST};
 64         this._ignoreContentScaleFactor = false;
 65     },
 66 
 67     /** updates the Atlas (indexed vertex array).
 68      * Shall be overridden in subclasses
 69      */
 70     updateAtlasValues:function () {
 71         cc.Assert(false, "cc.AtlasNode:Abstract updateAtlasValue not overridden");
 72     },
 73 
 74     /** cc.AtlasNode - RGBA protocol
 75      * @return {cc.Color3B}
 76      */
 77     getColor:function () {
 78         if (this._opacityModifyRGB)
 79             return this._colorUnmodified;
 80         return cc.NodeRGBA.prototype.getColor.call(this);
 81     },
 82 
 83     /**
 84      * @param {Boolean} value
 85      */
 86     setOpacityModifyRGB:function (value) {
 87         var oldColor = this.getColor();
 88         this._opacityModifyRGB = value;
 89         this.setColor(oldColor);
 90     },
 91 
 92     /**
 93      * @return {Boolean}
 94      */
 95     isOpacityModifyRGB:function () {
 96         return this._opacityModifyRGB;
 97     },
 98 
 99     /** cc.AtlasNode - CocosNodeTexture protocol
100      * @return {cc.BlendFunc}
101      */
102     getBlendFunc:function () {
103         return this._blendFunc;
104     },
105 
106     /**
107      * BlendFunc setter
108      * @param {Number | cc.BlendFunc} src
109      * @param {Number} dst
110      */
111     setBlendFunc:function (src, dst) {
112         if (arguments.length == 1)
113             this._blendFunc = src;
114         else
115             this._blendFunc = {src:src, dst:dst};
116     },
117 
118     /**
119      * @param {cc.TextureAtlas} value
120      */
121     setTextureAtlas:function (value) {
122         this._textureAtlas = value;
123     },
124 
125     /**
126      * @return {cc.TextureAtlas}
127      */
128     getTextureAtlas:function () {
129         return this._textureAtlas;
130     },
131 
132     /**
133      * @return {Number}
134      */
135     getQuadsToDraw:function () {
136         return this._quadsToDraw;
137     },
138 
139     /**
140      * @param {Number} quadsToDraw
141      */
142     setQuadsToDraw:function (quadsToDraw) {
143         this._quadsToDraw = quadsToDraw;
144     },
145 
146     _textureForCanvas:null,
147     _originalTexture:null,
148 
149     _uniformColor:null,
150     _colorF32Array:null,
151 
152     /** initializes an cc.AtlasNode  with an Atlas file the width and height of each item and the quantity of items to render
153      * @param {String} tile
154      * @param {Number} tileWidth
155      * @param {Number} tileHeight
156      * @param {Number} itemsToRender
157      * @return {Boolean}
158      */
159     initWithTileFile:function (tile, tileWidth, tileHeight, itemsToRender) {
160         cc.Assert(tile != null, "title should not be null");
161         var texture = cc.TextureCache.getInstance().addImage(tile);
162         return this.initWithTexture(texture, tileWidth, tileHeight, itemsToRender);
163     },
164 
165     /**
166      * initializes an CCAtlasNode  with a texture the width and height of each item measured in points and the quantity of items to render
167      * @param {cc.Texture2D} texture
168      * @param {Number} tileWidth
169      * @param {Number} tileHeight
170      * @param {Number} itemsToRender
171      * @return {Boolean}
172      */
173     initWithTexture:null,
174 
175     _initWithTextureForCanvas:function(texture, tileWidth, tileHeight, itemsToRender){
176         this._itemWidth = tileWidth;
177         this._itemHeight = tileHeight;
178 
179         this._opacityModifyRGB = true;
180         this._originalTexture = texture;
181         if (!this._originalTexture) {
182             cc.log("cocos2d: Could not initialize cc.AtlasNode. Invalid Texture.");
183             return false;
184         }
185         this._textureForCanvas = this._originalTexture;
186         this._calculateMaxItems();
187 
188         this._quadsToDraw = itemsToRender;
189         return true;
190     },
191 
192     _initWithTextureForWebGL:function(texture, tileWidth, tileHeight, itemsToRender){
193         this._itemWidth = tileWidth;
194         this._itemHeight = tileHeight;
195         this._colorUnmodified = cc.WHITE;
196         this._opacityModifyRGB = true;
197 
198         this._blendFunc.src = cc.BLEND_SRC;
199         this._blendFunc.dst = cc.BLEND_DST;
200 
201         var locRealColor = this._realColor;
202         this._colorF32Array = new Float32Array([locRealColor.r / 255.0, locRealColor.g / 255.0, locRealColor.b / 255.0, this._realOpacity / 255.0]);
203         this._textureAtlas = new cc.TextureAtlas();
204         this._textureAtlas.initWithTexture(texture, itemsToRender);
205 
206         if (!this._textureAtlas) {
207             cc.log("cocos2d: Could not initialize cc.AtlasNode. Invalid Texture.");
208             return false;
209         }
210 
211         this._updateBlendFunc();
212         this._updateOpacityModifyRGB();
213         this._calculateMaxItems();
214         this._quadsToDraw = itemsToRender;
215 
216         //shader stuff
217         this.setShaderProgram(cc.ShaderCache.getInstance().programForKey(cc.SHADER_POSITION_TEXTURE_UCOLOR));
218         this._uniformColor = cc.renderContext.getUniformLocation(this.getShaderProgram().getProgram(), "u_color");
219         return true;
220     },
221 
222     draw:null,
223 
224     /**
225      * @param {WebGLRenderingContext} ctx renderContext
226      */
227     _drawForWebGL:function (ctx) {
228         var context = ctx || cc.renderContext;
229         cc.NODE_DRAW_SETUP(this);
230         cc.glBlendFunc(this._blendFunc.src, this._blendFunc.dst);
231         context.uniform4fv(this._uniformColor, this._colorF32Array);
232         this._textureAtlas.drawNumberOfQuads(this._quadsToDraw, 0);
233     },
234 
235     /**
236      * @param {cc.Color3B} color3
237      */
238     setColor:null,
239 
240     _setColorForCanvas:function (color3) {
241         var locRealColor = this._realColor;
242         if ((locRealColor.r == color3.r) && (locRealColor.g == color3.g) && (locRealColor.b == color3.b))
243             return;
244         var temp = new cc.Color3B(color3.r,color3.g,color3.b);
245         this._colorUnmodified = color3;
246 
247         if (this._opacityModifyRGB) {
248             var locDisplayedOpacity = this._displayedOpacity;
249             temp.r = temp.r * locDisplayedOpacity / 255;
250             temp.g = temp.g * locDisplayedOpacity / 255;
251             temp.b = temp.b * locDisplayedOpacity / 255;
252         }
253         cc.NodeRGBA.prototype.setColor.call(this, color3);
254 
255         if (this.getTexture()) {
256             var element = this._originalTexture.getHtmlElementObj();
257             if(!element)
258                 return;
259             var cacheTextureForColor = cc.TextureCache.getInstance().getTextureColors(element);
260             if (cacheTextureForColor) {
261                 var textureRect = cc.rect(0, 0, element.width, element.height);
262                 element = cc.generateTintImage(element, cacheTextureForColor, this._realColor, textureRect);
263                 var locTexture = new cc.Texture2D();
264                 locTexture.initWithElement(element);
265                 locTexture.handleLoadedTexture();
266                 this.setTexture(locTexture);
267             }
268         }
269     },
270 
271     _setColorForWebGL:function (color3) {
272         var temp = cc.Color3B(color3.r,color3.g,color3.b);
273         this._colorUnmodified = color3;
274         var locDisplayedOpacity = this._displayedOpacity;
275         if (this._opacityModifyRGB) {
276             temp.r = temp.r * locDisplayedOpacity / 255;
277             temp.g = temp.g * locDisplayedOpacity / 255;
278             temp.b = temp.b * locDisplayedOpacity / 255;
279         }
280         cc.NodeRGBA.prototype.setColor.call(this, color3);
281         var locDisplayedColor = this._displayedColor;
282         this._colorF32Array = new Float32Array([locDisplayedColor.r / 255.0, locDisplayedColor.g / 255.0,
283             locDisplayedColor.b / 255.0, locDisplayedOpacity / 255.0]);
284     },
285 
286     /**
287      * @param {Number} opacity
288      */
289     setOpacity: null,
290 
291     _setOpacityForCanvas: function (opacity) {
292         cc.NodeRGBA.prototype.setOpacity.call(this, opacity);
293         // special opacity for premultiplied textures
294         if (this._opacityModifyRGB) {
295             this.setColor(this._colorUnmodified);
296         }
297     },
298 
299     _setOpacityForWebGL: function (opacity) {
300         cc.NodeRGBA.prototype.setOpacity.call(this, opacity);
301         // special opacity for premultiplied textures
302         if (this._opacityModifyRGB) {
303             this.setColor(this._colorUnmodified);
304         } else {
305             var locDisplayedColor = this._displayedColor;
306             this._colorF32Array = new Float32Array([locDisplayedColor.r / 255.0, locDisplayedColor.g / 255.0,
307                 locDisplayedColor.b / 255.0, this._displayedOpacity / 255.0]);
308         }
309     },
310 
311     // cc.Texture protocol
312     /**
313      * returns the used texture
314      * @return {cc.Texture2D}
315      */
316     getTexture: null,
317 
318     _getTextureForCanvas: function () {
319         return  this._textureForCanvas;
320     },
321 
322     _getTextureForWebGL: function () {
323         return  this._textureAtlas.getTexture();
324     },
325 
326     /** sets a new texture. it will be retained
327      * @param {cc.Texture2D} texture
328      */
329     setTexture: null,
330 
331     _setTextureForCanvas: function (texture) {
332         this._textureForCanvas = texture;
333     },
334 
335     _setTextureForWebGL: function (texture) {
336         this._textureAtlas.setTexture(texture);
337         this._updateBlendFunc();
338         this._updateOpacityModifyRGB();
339     },
340 
341     _calculateMaxItems:null,
342 
343     _calculateMaxItemsForCanvas:function () {
344         var selTexture = this.getTexture();
345         var size = selTexture.getContentSize();
346 
347         this._itemsPerColumn = 0 | (size.height / this._itemHeight);
348         this._itemsPerRow = 0 | (size.width / this._itemWidth);
349     },
350 
351     _calculateMaxItemsForWebGL:function () {
352         var selTexture = this.getTexture();
353         var size = selTexture.getContentSize();
354         if(this._ignoreContentScaleFactor)
355             size = selTexture.getContentSizeInPixels();
356 
357         this._itemsPerColumn = 0 | (size.height / this._itemHeight);
358         this._itemsPerRow = 0 | (size.width / this._itemWidth);
359     },
360 
361     _updateBlendFunc:function () {
362         if (!this._textureAtlas.getTexture().hasPremultipliedAlpha()) {
363             this._blendFunc.src = gl.SRC_ALPHA;
364             this._blendFunc.dst = gl.ONE_MINUS_SRC_ALPHA;
365         }
366     },
367 
368     _updateOpacityModifyRGB:function () {
369         this._opacityModifyRGB = this._textureAtlas.getTexture().hasPremultipliedAlpha();
370     },
371 
372     _setIgnoreContentScaleFactor:function(ignoreContentScaleFactor){
373         this._ignoreContentScaleFactor = ignoreContentScaleFactor;
374     }
375 });
376 
377 if(cc.Browser.supportWebGL){
378     cc.AtlasNode.prototype.initWithTexture = cc.AtlasNode.prototype._initWithTextureForWebGL;
379     cc.AtlasNode.prototype.draw = cc.AtlasNode.prototype._drawForWebGL;
380     cc.AtlasNode.prototype.setColor = cc.AtlasNode.prototype._setColorForWebGL;
381     cc.AtlasNode.prototype.setOpacity = cc.AtlasNode.prototype._setOpacityForWebGL;
382     cc.AtlasNode.prototype.getTexture = cc.AtlasNode.prototype._getTextureForWebGL;
383     cc.AtlasNode.prototype.setTexture = cc.AtlasNode.prototype._setTextureForWebGL;
384     cc.AtlasNode.prototype._calculateMaxItems = cc.AtlasNode.prototype._calculateMaxItemsForWebGL;
385 } else {
386     cc.AtlasNode.prototype.initWithTexture = cc.AtlasNode.prototype._initWithTextureForCanvas;
387     cc.AtlasNode.prototype.draw = cc.Node.prototype.draw;
388     cc.AtlasNode.prototype.setColor = cc.AtlasNode.prototype._setColorForCanvas;
389     cc.AtlasNode.prototype.setOpacity = cc.AtlasNode.prototype._setOpacityForCanvas;
390     cc.AtlasNode.prototype.getTexture = cc.AtlasNode.prototype._getTextureForCanvas;
391     cc.AtlasNode.prototype.setTexture = cc.AtlasNode.prototype._setTextureForCanvas;
392     cc.AtlasNode.prototype._calculateMaxItems = cc.AtlasNode.prototype._calculateMaxItemsForCanvas;
393 }
394 
395 /** creates a cc.AtlasNode with an Atlas file the width and height of each item and the quantity of items to render
396  * @param {String} tile
397  * @param {Number} tileWidth
398  * @param {Number} tileHeight
399  * @param {Number} itemsToRender
400  * @return {cc.AtlasNode}
401  * @example
402  * // example
403  * var node = cc.AtlasNode.create("pathOfTile", 16, 16, 1);
404  */
405 cc.AtlasNode.create = function (tile, tileWidth, tileHeight, itemsToRender) {
406     var ret = new cc.AtlasNode();
407     if (ret.initWithTileFile(tile, tileWidth, tileHeight, itemsToRender))
408         return ret;
409     return null;
410 };
411 
412