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.log("cc.AtlasNode.updateAtlasValues(): Shall be overridden in subclasses") ;
 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         if(!tile)
161             throw "cc.AtlasNode.initWithTileFile(): title should not be null";
162         var texture = cc.TextureCache.getInstance().addImage(tile);
163         return this.initWithTexture(texture, tileWidth, tileHeight, itemsToRender);
164     },
165 
166     /**
167      * initializes an CCAtlasNode  with a texture the width and height of each item measured in points and the quantity of items to render
168      * @param {cc.Texture2D} texture
169      * @param {Number} tileWidth
170      * @param {Number} tileHeight
171      * @param {Number} itemsToRender
172      * @return {Boolean}
173      */
174     initWithTexture:null,
175 
176     _initWithTextureForCanvas:function(texture, tileWidth, tileHeight, itemsToRender){
177         this._itemWidth = tileWidth;
178         this._itemHeight = tileHeight;
179 
180         this._opacityModifyRGB = true;
181         this._originalTexture = texture;
182         if (!this._originalTexture) {
183             cc.log("cocos2d: Could not initialize cc.AtlasNode. Invalid Texture.");
184             return false;
185         }
186         this._textureForCanvas = this._originalTexture;
187         this._calculateMaxItems();
188 
189         this._quadsToDraw = itemsToRender;
190         return true;
191     },
192 
193     _initWithTextureForWebGL:function(texture, tileWidth, tileHeight, itemsToRender){
194         this._itemWidth = tileWidth;
195         this._itemHeight = tileHeight;
196         this._colorUnmodified = cc.white();
197         this._opacityModifyRGB = true;
198 
199         this._blendFunc.src = cc.BLEND_SRC;
200         this._blendFunc.dst = cc.BLEND_DST;
201 
202         var locRealColor = this._realColor;
203         this._colorF32Array = new Float32Array([locRealColor.r / 255.0, locRealColor.g / 255.0, locRealColor.b / 255.0, this._realOpacity / 255.0]);
204         this._textureAtlas = new cc.TextureAtlas();
205         this._textureAtlas.initWithTexture(texture, itemsToRender);
206 
207         if (!this._textureAtlas) {
208             cc.log("cocos2d: Could not initialize cc.AtlasNode. Invalid Texture.");
209             return false;
210         }
211 
212         this._updateBlendFunc();
213         this._updateOpacityModifyRGB();
214         this._calculateMaxItems();
215         this._quadsToDraw = itemsToRender;
216 
217         //shader stuff
218         this.setShaderProgram(cc.ShaderCache.getInstance().programForKey(cc.SHADER_POSITION_TEXTURE_UCOLOR));
219         this._uniformColor = cc.renderContext.getUniformLocation(this.getShaderProgram().getProgram(), "u_color");
220         return true;
221     },
222 
223     draw:null,
224 
225     /**
226      * @param {WebGLRenderingContext} ctx renderContext
227      */
228     _drawForWebGL:function (ctx) {
229         var context = ctx || cc.renderContext;
230         cc.NODE_DRAW_SETUP(this);
231         cc.glBlendFunc(this._blendFunc.src, this._blendFunc.dst);
232         context.uniform4fv(this._uniformColor, this._colorF32Array);
233         this._textureAtlas.drawNumberOfQuads(this._quadsToDraw, 0);
234     },
235 
236     /**
237      * @param {cc.Color3B} color3
238      */
239     setColor:null,
240 
241     _setColorForCanvas:function (color3) {
242         var locRealColor = this._realColor;
243         if ((locRealColor.r == color3.r) && (locRealColor.g == color3.g) && (locRealColor.b == color3.b))
244             return;
245         var temp = new cc.Color3B(color3.r,color3.g,color3.b);
246         this._colorUnmodified = color3;
247 
248         if (this._opacityModifyRGB) {
249             var locDisplayedOpacity = this._displayedOpacity;
250             temp.r = temp.r * locDisplayedOpacity / 255;
251             temp.g = temp.g * locDisplayedOpacity / 255;
252             temp.b = temp.b * locDisplayedOpacity / 255;
253         }
254         cc.NodeRGBA.prototype.setColor.call(this, color3);
255 
256         if (this.getTexture()) {
257             var element = this._originalTexture.getHtmlElementObj();
258             if(!element)
259                 return;
260             var cacheTextureForColor = cc.TextureCache.getInstance().getTextureColors(element);
261             if (cacheTextureForColor) {
262                 var textureRect = cc.rect(0, 0, element.width, element.height);
263                 element = cc.generateTintImage(element, cacheTextureForColor, this._realColor, textureRect);
264                 var locTexture = new cc.Texture2D();
265                 locTexture.initWithElement(element);
266                 locTexture.handleLoadedTexture();
267                 this.setTexture(locTexture);
268             }
269         }
270     },
271 
272     _setColorForWebGL:function (color3) {
273         var temp = cc.Color3B(color3.r,color3.g,color3.b);
274         this._colorUnmodified = color3;
275         var locDisplayedOpacity = this._displayedOpacity;
276         if (this._opacityModifyRGB) {
277             temp.r = temp.r * locDisplayedOpacity / 255;
278             temp.g = temp.g * locDisplayedOpacity / 255;
279             temp.b = temp.b * locDisplayedOpacity / 255;
280         }
281         cc.NodeRGBA.prototype.setColor.call(this, color3);
282         var locDisplayedColor = this._displayedColor;
283         this._colorF32Array = new Float32Array([locDisplayedColor.r / 255.0, locDisplayedColor.g / 255.0,
284             locDisplayedColor.b / 255.0, locDisplayedOpacity / 255.0]);
285     },
286 
287     /**
288      * @param {Number} opacity
289      */
290     setOpacity: null,
291 
292     _setOpacityForCanvas: function (opacity) {
293         cc.NodeRGBA.prototype.setOpacity.call(this, opacity);
294         // special opacity for premultiplied textures
295         if (this._opacityModifyRGB) {
296             this.setColor(this._colorUnmodified);
297         }
298     },
299 
300     _setOpacityForWebGL: function (opacity) {
301         cc.NodeRGBA.prototype.setOpacity.call(this, opacity);
302         // special opacity for premultiplied textures
303         if (this._opacityModifyRGB) {
304             this.setColor(this._colorUnmodified);
305         } else {
306             var locDisplayedColor = this._displayedColor;
307             this._colorF32Array = new Float32Array([locDisplayedColor.r / 255.0, locDisplayedColor.g / 255.0,
308                 locDisplayedColor.b / 255.0, this._displayedOpacity / 255.0]);
309         }
310     },
311 
312     // cc.Texture protocol
313     /**
314      * returns the used texture
315      * @return {cc.Texture2D}
316      */
317     getTexture: null,
318 
319     _getTextureForCanvas: function () {
320         return  this._textureForCanvas;
321     },
322 
323     _getTextureForWebGL: function () {
324         return  this._textureAtlas.getTexture();
325     },
326 
327     /** sets a new texture. it will be retained
328      * @param {cc.Texture2D} texture
329      */
330     setTexture: null,
331 
332     _setTextureForCanvas: function (texture) {
333         this._textureForCanvas = texture;
334     },
335 
336     _setTextureForWebGL: function (texture) {
337         this._textureAtlas.setTexture(texture);
338         this._updateBlendFunc();
339         this._updateOpacityModifyRGB();
340     },
341 
342     _calculateMaxItems:null,
343 
344     _calculateMaxItemsForCanvas:function () {
345         var selTexture = this.getTexture();
346         var size = selTexture.getContentSize();
347 
348         this._itemsPerColumn = 0 | (size.height / this._itemHeight);
349         this._itemsPerRow = 0 | (size.width / this._itemWidth);
350     },
351 
352     _calculateMaxItemsForWebGL:function () {
353         var selTexture = this.getTexture();
354         var size = selTexture.getContentSize();
355         if(this._ignoreContentScaleFactor)
356             size = selTexture.getContentSizeInPixels();
357 
358         this._itemsPerColumn = 0 | (size.height / this._itemHeight);
359         this._itemsPerRow = 0 | (size.width / this._itemWidth);
360     },
361 
362     _updateBlendFunc:function () {
363         if (!this._textureAtlas.getTexture().hasPremultipliedAlpha()) {
364             this._blendFunc.src = gl.SRC_ALPHA;
365             this._blendFunc.dst = gl.ONE_MINUS_SRC_ALPHA;
366         }
367     },
368 
369     _updateOpacityModifyRGB:function () {
370         this._opacityModifyRGB = this._textureAtlas.getTexture().hasPremultipliedAlpha();
371     },
372 
373     _setIgnoreContentScaleFactor:function(ignoreContentScaleFactor){
374         this._ignoreContentScaleFactor = ignoreContentScaleFactor;
375     }
376 });
377 
378 if(cc.Browser.supportWebGL){
379     cc.AtlasNode.prototype.initWithTexture = cc.AtlasNode.prototype._initWithTextureForWebGL;
380     cc.AtlasNode.prototype.draw = cc.AtlasNode.prototype._drawForWebGL;
381     cc.AtlasNode.prototype.setColor = cc.AtlasNode.prototype._setColorForWebGL;
382     cc.AtlasNode.prototype.setOpacity = cc.AtlasNode.prototype._setOpacityForWebGL;
383     cc.AtlasNode.prototype.getTexture = cc.AtlasNode.prototype._getTextureForWebGL;
384     cc.AtlasNode.prototype.setTexture = cc.AtlasNode.prototype._setTextureForWebGL;
385     cc.AtlasNode.prototype._calculateMaxItems = cc.AtlasNode.prototype._calculateMaxItemsForWebGL;
386 } else {
387     cc.AtlasNode.prototype.initWithTexture = cc.AtlasNode.prototype._initWithTextureForCanvas;
388     cc.AtlasNode.prototype.draw = cc.Node.prototype.draw;
389     cc.AtlasNode.prototype.setColor = cc.AtlasNode.prototype._setColorForCanvas;
390     cc.AtlasNode.prototype.setOpacity = cc.AtlasNode.prototype._setOpacityForCanvas;
391     cc.AtlasNode.prototype.getTexture = cc.AtlasNode.prototype._getTextureForCanvas;
392     cc.AtlasNode.prototype.setTexture = cc.AtlasNode.prototype._setTextureForCanvas;
393     cc.AtlasNode.prototype._calculateMaxItems = cc.AtlasNode.prototype._calculateMaxItemsForCanvas;
394 }
395 
396 /** creates a cc.AtlasNode with an Atlas file the width and height of each item and the quantity of items to render
397  * @param {String} tile
398  * @param {Number} tileWidth
399  * @param {Number} tileHeight
400  * @param {Number} itemsToRender
401  * @return {cc.AtlasNode}
402  * @example
403  * // example
404  * var node = cc.AtlasNode.create("pathOfTile", 16, 16, 1);
405  */
406 cc.AtlasNode.create = function (tile, tileWidth, tileHeight, itemsToRender) {
407     var ret = new cc.AtlasNode();
408     if (ret.initWithTileFile(tile, tileWidth, tileHeight, itemsToRender))
409         return ret;
410     return null;
411 };
412 
413