Many updates, see CHANGLOG diff
[rokon:rokon.git] / src / com / stickycoding / rokon / Texture.java
1 package com.stickycoding.rokon;\r
2 \r
3 import javax.microedition.khronos.opengles.GL10;\r
4 \r
5 import android.graphics.Bitmap;\r
6 import android.graphics.BitmapFactory;\r
7 import android.graphics.Canvas;\r
8 import android.graphics.Paint;\r
9 import android.graphics.Rect;\r
10 import android.opengl.GLUtils;\r
11 \r
12 import com.stickycoding.rokon.vbo.ArrayVBO;\r
13 import com.stickycoding.rokon.vbo.VBO;\r
14 \r
15 /**\r
16  * Texture.java\r
17  * An object representing one texture (one image file)\r
18  * Contains information linking the actual asset file for the texture\r
19  * \r
20  * @author Richard\r
21  */\r
22 \r
23 public class Texture {\r
24         \r
25         protected TextureAtlas parentAtlas;\r
26         protected int atlasX, atlasY, atlasIndex;\r
27         protected Bitmap bmp;\r
28         \r
29         protected int textureWidth, textureHeight;\r
30         protected int width, height, columns, rows, tileCount;\r
31         protected String path;\r
32         protected BufferObject[] buffer;\r
33         private int textureIndex = -1;\r
34         \r
35         protected ArrayVBO[] vbo;\r
36         \r
37         protected boolean reload = false;\r
38         \r
39         public float minFilter = GL10.GL_LINEAR;\r
40         public float magFilter = GL10.GL_LINEAR;\r
41         public float wrapS = GL10.GL_CLAMP_TO_EDGE;\r
42         public float wrapT = GL10.GL_CLAMP_TO_EDGE;\r
43         public float envMode = GL10.GL_MODULATE;\r
44         \r
45         protected void setUnloaded() {\r
46                 reload = false;\r
47                 textureIndex = -1;\r
48                 if(parentAtlas != null && parentAtlas.getTextureIndex() != -1) parentAtlas.setUnloaded();\r
49         }\r
50         \r
51         public void reload() {\r
52                 this.reload = true;\r
53                 if(parentAtlas != null) parentAtlas.reload = true;\r
54         }\r
55         \r
56         protected void setReloaded() {\r
57                 this.reload = false;\r
58                 if(parentAtlas != null) parentAtlas.setReloaded();\r
59         }\r
60         \r
61         public boolean isReload() {\r
62                 return reload;\r
63         }\r
64         \r
65         protected void setTextureIndex(int index) {\r
66                 textureIndex = index;\r
67         }\r
68         \r
69         /**\r
70          * Returns the texture index of the Texture on the hardware\r
71          * \r
72          * @return -1 if not loaded\r
73          */\r
74         public int getTextureIndex() {\r
75                 return textureIndex;\r
76         }\r
77         \r
78         public int getTileCols() {\r
79                 return columns;\r
80         }\r
81         \r
82         public int getTileRows() {\r
83                 return rows;\r
84         }\r
85         \r
86         public int getTileCount() {\r
87                 return tileCount;\r
88         }\r
89         \r
90         /**\r
91          * Returns an array of BufferObjects relating to each tile on the Texture\r
92          * \r
93          * @return BufferObject array\r
94          */\r
95         public BufferObject[] getBuffer() {\r
96                 return buffer;\r
97         }\r
98         \r
99         /**\r
100          * Returns the BufferObject relating to a specific tile on the Texture\r
101          * \r
102          * @param index the index of the tile\r
103          * \r
104          * @return\r
105          */\r
106         public BufferObject getBuffer(int index) {\r
107                 return buffer[index];\r
108         }\r
109         \r
110         /**\r
111          * Creates a texture, with a file from the assets\r
112          * \r
113          * @param filename path in assets, relative to RokonActivity.getGraphicsPath\r
114          */\r
115         public Texture(String filename) {\r
116                 this(filename, 1, 1);\r
117         }\r
118         \r
119         protected Texture() {\r
120 \r
121         }\r
122         \r
123         /**\r
124          * Creates a texture, with a file from the assets\r
125          * \r
126          * @param filename path in assets, relative to RokonActivity.getGraphicsPath\r
127          * @param columns number of columns in the texture\r
128          * @param rows number of rows in the texture\r
129          */\r
130         public Texture(String filename, int columns, int rows) {\r
131                 path = RokonActivity.graphicsPath + filename;\r
132                 BitmapFactory.Options opts = new BitmapFactory.Options();\r
133                 opts.inJustDecodeBounds = true;\r
134                 try {\r
135                         BitmapFactory.decodeStream(Rokon.currentActivity.getAssets().open(path), null, opts);\r
136                 } catch (Exception e) {\r
137                         Debug.error("Tried creating a Texture, failed while decoding, " + path);\r
138                         e.printStackTrace();\r
139                         return;\r
140                 }\r
141                 width = opts.outWidth;\r
142                 height = opts.outHeight;\r
143                 this.columns = columns;\r
144                 this.rows = rows;\r
145                 tileCount = columns * rows;\r
146                 textureWidth = nextPowerOfTwo(width);\r
147                 textureHeight = nextPowerOfTwo(height);\r
148         }\r
149         \r
150         protected void prepareBuffers() {\r
151                 if(parentAtlas == null) {\r
152                         buffer = new BufferObject[tileCount];\r
153                         for(int i = 0; i < buffer.length; i++) {\r
154                                 float col = i % columns;\r
155                                 float row = (i - col) / (float)columns;\r
156                                 buffer[i] = new BufferObject(8);\r
157                                 float x = col * (float)(width / columns);\r
158                                 float y = row * (float)(height / rows);\r
159                                 buffer[i].update(x / textureWidth, y / textureHeight, (float)(width / columns) / (float)textureWidth, (float)(height / rows) / (float)textureHeight);\r
160                         }\r
161                 } else {\r
162                         buffer = new BufferObject[tileCount];\r
163                         for(int i = 0; i < buffer.length; i++) {\r
164                                 float col = i % columns;\r
165                                 float row = (i - col) / (float)columns;\r
166                                 buffer[i] = new BufferObject(8);\r
167                                 float x = col * (float)(width / columns);\r
168                                 float y = row * (float)(height / rows);\r
169                                 \r
170                                 float finalX = x / textureWidth;\r
171                                 float finalY = y / textureHeight;\r
172                                 float finalWidth = (float)(width / columns) / (float)textureWidth;\r
173                                 float finalHeight = (float)(height / rows) / (float)textureHeight;\r
174                                 \r
175                                 float realX = (float)atlasX / (float)parentAtlas.atlasWidth;\r
176                                 float realY = (float)atlasY / (float)parentAtlas.atlasHeight;\r
177                                 float realWidth = (float)textureWidth / (float)parentAtlas.atlasWidth;\r
178                                 float realHeight = (float)textureHeight / (float)parentAtlas.atlasHeight;\r
179                                 \r
180                                 float theX = realX + (finalX * realWidth);\r
181                                 float theY = realY + (finalY * realHeight);\r
182                                 float theWidth = finalWidth * realWidth;\r
183                                 float theHeight = finalHeight * realHeight;\r
184 \r
185                                 buffer[i].update(theX, theY, theWidth, theHeight);\r
186                         }\r
187                 }\r
188                 if(DrawPriority.drawPriority == DrawPriority.PRIORITY_VBO) {\r
189                         vbo = new ArrayVBO[buffer.length];\r
190                         for(int i = 0; i < buffer.length; i++) {\r
191                                 vbo[i] = new ArrayVBO(buffer[i], VBO.STATIC);\r
192                         }\r
193                 }\r
194         }\r
195         \r
196         protected void freeBuffers() {\r
197                 for(int i = 0; i < buffer.length; i++) {\r
198                         buffer[i].free();\r
199                 }\r
200         }\r
201         \r
202         /**\r
203          * @param x integer\r
204          * @return TRUE if x is a power of two, FALSE otherwise\r
205          */\r
206         public static boolean isPowerOfTwo(int x) {\r
207                 return (x != 0) && ((x & (x - 1)) == 0);\r
208         }\r
209         \r
210         /**\r
211          * Finds the next power of two, from a given minimum\r
212          * \r
213          * @param minimum integer\r
214          * @return the next (or same, if minimum is power-of-two) power-of-two\r
215          */\r
216         public static int nextPowerOfTwo(int minimum) {\r
217                 if(isPowerOfTwo(minimum)) {\r
218                         return minimum;\r
219                 }\r
220                 int i = 0;\r
221                 while(true) {\r
222                         i++;\r
223                         if(Math.pow(2, i) >= minimum) {\r
224                                 return (int)Math.pow(2, i);\r
225                         }\r
226                 }\r
227         }\r
228         \r
229         protected Bitmap getBitmap() {\r
230         try {\r
231                 BitmapFactory.Options opts = new BitmapFactory.Options();\r
232                 opts.inDither = true;\r
233                 if(bmp != null && !bmp.isRecycled()) {\r
234                         bmp.recycle();\r
235                 }\r
236                 Bitmap tBmp = BitmapFactory.decodeStream(Rokon.currentActivity.getAssets().open(path), new Rect(), opts );\r
237                 bmp = Bitmap.createBitmap(tBmp.getWidth(), tBmp.getHeight(), Bitmap.Config.ARGB_8888);\r
238                 Canvas canvas = new Canvas(bmp);\r
239                 canvas.drawBitmap(tBmp, 0, 0, new Paint());\r
240                 canvas.save();\r
241                 tBmp.recycle();\r
242                 tBmp = null;\r
243                 return bmp;\r
244         } catch (Exception e) {\r
245                 e.printStackTrace();\r
246                 Debug.error("Texture.getBitmap() error, bad asset?");\r
247                 return null;\r
248         }\r
249         }\r
250         \r
251         protected void onLoadTexture(GL10 gl) {\r
252                 if(!reload) {\r
253                         if(parentAtlas == null) {\r
254                                 prepareBuffers();\r
255                                 int[] nameArray = new int[1];\r
256                                 GLHelper.enableTextures();\r
257                                 gl.glGenTextures(1, nameArray, 0);\r
258                                 textureIndex = nameArray[0];\r
259                                 GLHelper.bindTexture(textureIndex);\r
260                                 Bitmap bmp = Bitmap.createBitmap(textureWidth, textureHeight, Bitmap.Config.ARGB_8888);\r
261                                 GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);\r
262                         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,  minFilter);\r
263                         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, magFilter);\r
264                         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, wrapS);\r
265                         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, wrapT);\r
266                         gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, envMode);\r
267                         bmp.recycle();\r
268                         bmp = null;\r
269                         System.gc();\r
270                         bmp = getBitmap();\r
271                         GLUtils.texSubImage2D(GL10.GL_TEXTURE_2D, 0, 0, 0, bmp);\r
272                         clearBitmap();\r
273                         bmp = null;\r
274                                 TextureManager.addToActive(this);\r
275                         } else {\r
276                                 parentAtlas.onLoadTexture(gl);\r
277                                 TextureManager.addToActive(this);\r
278                         }\r
279                 } else {\r
280                         if(parentAtlas == null) {\r
281                                 GLHelper.bindTexture(textureIndex);\r
282                                 Bitmap bmp = Bitmap.createBitmap(textureWidth, textureHeight, Bitmap.Config.ARGB_8888);\r
283                                 GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);\r
284                         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,  minFilter);\r
285                         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, magFilter);\r
286                         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, wrapS);\r
287                         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, wrapT);\r
288                         gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, envMode);\r
289                         bmp.recycle();\r
290                         bmp = null;\r
291                         System.gc();\r
292                         bmp = getBitmap();\r
293                         GLUtils.texSubImage2D(GL10.GL_TEXTURE_2D, 0, 0, 0, bmp);\r
294                         clearBitmap();\r
295                         bmp = null;\r
296                                 TextureManager.addToActive(this);\r
297                         } else {\r
298                                 parentAtlas.onLoadTexture(gl);\r
299                                 TextureManager.addToActive(this);\r
300                         }\r
301                 }               \r
302         }\r
303         \r
304         protected void clearBitmap() {\r
305                 if(bmp == null) return;\r
306                 bmp.recycle();\r
307                 bmp = null;\r
308         }\r
309         \r
310         /**\r
311          * Gets the width of this Texture\r
312          * \r
313          * @return Texture width\r
314          */\r
315         public int getWidth() {\r
316                 return width;\r
317         }\r
318         \r
319         /**\r
320          * Gets the height of this Texture\r
321          * \r
322          * @return Texture height\r
323          */\r
324         public int getHeight() {\r
325                 return height;\r
326         }\r
327         \r
328         /**\r
329          * Gets the width of one tile on this Texture\r
330          * \r
331          * @return Texture tile width\r
332          */\r
333         public int getTileWidth() {\r
334                 return width / columns;\r
335         }\r
336         \r
337         /**\r
338          * Gets the height of one tile on this Texture\r
339          * \r
340          * @return Texture tile height\r
341          */\r
342         public int getTileHeight() {\r
343                 return height / rows;\r
344         }\r
345         \r
346         /**\r
347          * Gets the ratio of height to width for the Texture\r
348          * \r
349          * @return Texture aspect ratio\r
350          */\r
351         public float getRatio() {\r
352                 return (float)width / columns / (float)height;\r
353         }\r
354         \r
355         /**\r
356          * Gets the ratio of height to width for one tile in the Texture\r
357          * \r
358          * @return Texture tile aspect ratio\r
359          */\r
360         public float getTileRatio() {\r
361                 return ((float)width / columns) / ((float)height / rows);\r
362         }\r
363         \r
364         /**\r
365          * Calculates the height of one tile, based on the aspect ratio and given width\r
366          * \r
367          * @param width width value\r
368          * \r
369          * @return a height, matching width at a fixed aspect ratio\r
370          */\r
371         public float getTileHeight(float width) {\r
372                 return (width / columns) * getRatio();\r
373         }\r
374         \r
375         /**\r
376          * Calculates the height of the Texture, based on the aspect ratio and given width\r
377          * \r
378          * @param width width value\r
379          * \r
380          * @return a height, matching width at a fixed aspect ratio\r
381          */\r
382         public float getHeight(float width) {\r
383                 return width * getRatio();\r
384         }\r
385 \r
386 }\r