1 /*
2 Copyright (C) 2005 by Ruben Henner Zilibowitz <rzilibowitz@users.sourceforge.net>
3 Part of the Toy Cars Project http://toycars.sourceforge.net
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the license.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12
13 /*
14 * ScTilemap.cpp
15 * Scoobie
16 *
17 * Created by Ruben Henner Zilibowitz on 25/11/04.
18 *
19 */
20
21 #include "ScTilemap.h"
22 #include "ScView.h"
23 #include "ScException.h"
24
25 #ifndef WIN32 || __WIN32__
26 #include <arpa/inet.h>
27 #endif
28 #include <cstdio>
29 #include <cstring>
30
31 #ifndef ntohs
32 const short kShortConst = 1;
33 #define is_bigendian() ( (*(char*)&kShortConst) == 0 )
ntohs(unsigned short x)34 unsigned short ntohs(unsigned short x)
35 {
36 if (is_bigendian())
37 return x;
38 unsigned char *y = (unsigned char *)(&x);
39 unsigned char z[2] = {y[1],y[0]};
40 x = *(unsigned short *)z;
41 return x;
42 }
43 #endif
44 #ifndef htons
45 #define htons ntohs
46 #endif
47
48 //
49 // constructor - Loads tiles texture from pixel data. Call loadMap to
50 // load a tile map. Or call newMap to create an empty map.
51 //
52 // Note: for speed reasons only a single texture is allowed to store the
53 // tiles. The texture is the smallest power of two size which contains
54 // the width and height of the pixel data. For this reason it is advisable
55 // to create your tile image with power of two dimensions.
56 //
57 // border must be 0 or 1. This applies to EACH tile, thereby making the
58 // resulting tiles 2 pixels smaller in width and in height if it is 1.
59 // This is desirable if you want linear filtering to be used.
60 //
ScTilemap(const ScPixelData & pixelData,int inTileWidth,int inTileHeight,short border,short inSeperation,bool inBlend)61 ScTilemap::ScTilemap(const ScPixelData& pixelData, int inTileWidth, int inTileHeight, short border, short inSeperation, bool inBlend)
62 : ScPanel(0, 0),
63 mapWidth(0),
64 mapHeight(0),
65 useBlending(inBlend),
66 tex_id(0),
67 map(NULL)
68 {
69 loadTiles(pixelData, inTileWidth, inTileHeight, border, inSeperation);
70 }
71
72 //
73 // destructor
74 //
~ScTilemap()75 ScTilemap::~ScTilemap()
76 {
77 glDeleteTextures(1, &tex_id);
78 if (map)
79 delete [] map;
80 }
81
82 //
83 // doDraw - Draws the portion of the tile map which is in the currently
84 // active view rect.
85 //
doDraw()86 void ScTilemap::doDraw()
87 {
88 int start_tile_x, end_tile_x;
89 int start_tile_y, end_tile_y;
90
91 int map_x, map_y, tile;
92 ScFloat loc_x, loc_y;
93
94 start_tile_x = int((ScView::sActiveViewRect->left - bounds.left) * invTileWidth) - 1;
95 start_tile_y = int((ScView::sActiveViewRect->bottom - bounds.bottom) * invTileHeight) - 1;
96 end_tile_x = int((ScView::sActiveViewRect->right - bounds.left) * invTileWidth) + 1;
97 end_tile_y = int((ScView::sActiveViewRect->top - bounds.bottom) * invTileHeight) + 1;
98
99 glColor4ub(255, 255, 255, 255);
100 glEnable(GL_TEXTURE_2D);
101
102 if (useBlending)
103 glEnable(GL_BLEND);
104 else
105 glDisable(GL_BLEND);
106
107 glBindTexture(GL_TEXTURE_2D, tex_id);
108
109 glBegin(GL_QUADS);
110 for (map_y = start_tile_y; map_y < end_tile_y; map_y++)
111 {
112 loc_y = map_y*tileHeight + bounds.bottom;
113
114 for (map_x = start_tile_x; map_x < end_tile_x; map_x++)
115 {
116 loc_x = map_x*tileWidth + bounds.left;
117
118 if(map_y >= 0 && map_y < mapHeight && map_x >= 0 && map_x < mapWidth)
119 tile = map[map_y*mapWidth + map_x];
120 else
121 tile = 255;
122
123 if (tile != 255)
124 drawTile(loc_x, loc_y, tile);
125 }
126 }
127 glEnd();
128 }
129
130 //
131 // drawTile - Draws a single tile at a given location
132 //
drawTile(ScFloat x,ScFloat y,int tile)133 void ScTilemap::drawTile(ScFloat x, ScFloat y, int tile)
134 {
135 static ScFloat tx1;
136 static ScFloat ty1;
137 static ScFloat tx2;
138 static ScFloat ty2;
139 static ScFloat x1;
140 static ScFloat y1;
141 static ScFloat x2;
142 static ScFloat y2;
143
144 tx1 = (tile % paletteWidth) * (tileWidth + seperation) * invTextureWidth;
145 ty1 = (tile / paletteWidth) * (tileHeight + seperation) * invTextureHeight;
146 tx2 = tx1 + (tileWidth * invTextureWidth);
147 ty2 = ty1 + (tileHeight * invTextureHeight);
148
149 x1 = x;
150 y1 = y;
151 x2 = x1 + tileWidth;
152 y2 = y1 + tileHeight;
153
154 if (useBorder)
155 {
156 tx1 += invTextureWidth;
157 ty1 += invTextureHeight;
158 tx2 -= invTextureWidth;
159 ty2 -= invTextureHeight;
160 }
161
162 glTexCoord2d(tx1, ty1); glVertex2d(x1, y1);
163 glTexCoord2d(tx2, ty1); glVertex2d(x2, y1);
164 glTexCoord2d(tx2, ty2); glVertex2d(x2, y2);
165 glTexCoord2d(tx1, ty2); glVertex2d(x1, y2);
166 }
167
168 //
169 // newMap - Removes an old map data and makes new map of given dimensions
170 // clearing all tiles to given value.
171 //
newMap(int cols,int rows,unsigned char clearTile)172 void ScTilemap::newMap(int cols, int rows, unsigned char clearTile)
173 {
174 // store map dimensions
175 mapWidth = cols;
176 mapHeight = rows;
177
178 // set the bounds
179 bounds.right = bounds.left + (mapWidth * tileWidth);
180 bounds.top = bounds.bottom + (mapHeight * tileHeight);
181
182 // free current map (if any)
183 if (map)
184 {
185 delete [] map;
186 map = NULL;
187 }
188
189 // allocate memory for new map
190 map = new unsigned char [mapWidth * mapHeight];
191 if(!map)
192 {
193 ScThrowErr("Could not allocate memory for map");
194 }
195
196 // clear map
197 int k = mapWidth*mapHeight;
198 while (k--)
199 {
200 map[k] = clearTile;
201 }
202 }
203
204 //
205 // loadMap - Loads map data from a file. Removes any existing map data.
206 //
loadMap(const char * filename)207 void ScTilemap::loadMap(const char *filename)
208 {
209 FILE *f;
210 char header[5];
211
212 // open the file
213 f = fopen(filename, "r");
214 if(!f)
215 {
216 //printf("Failed to open map file: %s\n", filename);
217 ScThrowErr("Failed to open map file");
218 }
219
220 // verify map header exists
221 header[4] = '\0';
222 fread(header, 1, 4, f);
223 if (strcmp(header, "MAP ") != 0)
224 {
225 fclose(f);
226 ScThrowErr("Map file header does not exist");
227 }
228
229 // read dimensions
230 fread(&mapWidth, sizeof(mapWidth), 1, f);
231 fread(&mapHeight, sizeof(mapHeight), 1, f);
232 mapWidth = ntohs(mapWidth);
233 mapHeight = ntohs(mapHeight);
234
235 // set the bounds
236 bounds.right = bounds.left + (mapWidth * tileWidth);
237 bounds.top = bounds.bottom + (mapHeight * tileHeight);
238
239 // free current map (if any)
240 if (map)
241 {
242 delete [] map;
243 map = NULL;
244 }
245
246 // allocate memory for tile map
247 map = new unsigned char [mapWidth * mapHeight];
248 if(!map)
249 {
250 ScThrowErr("Could not allocate memory for map");
251 }
252
253 // read the map data
254 if(fread(map, mapWidth, mapHeight, f) != mapHeight)
255 {
256 delete [] map;
257 fclose(f);
258 ScThrowErr("Error while loading map");
259 }
260
261 fclose(f);
262 }
263
264 //
265 // saveMap - Saves the map to a file.
266 //
saveMap(const char * filename)267 void ScTilemap::saveMap(const char *filename)
268 {
269 FILE *f;
270 unsigned short data;
271
272 // check if map exists
273 if (!map)
274 {
275 ScThrowErr("Map does not exist");
276 }
277
278 // create the file
279 f = fopen(filename, "w");
280 if(!f)
281 {
282 ScThrowErr("Failed to create map file");
283 }
284
285 // write map header
286 fwrite("MAP ", 1, 4, f);
287
288 // write dimensions
289 data = htons(mapWidth);
290 fwrite(&data, sizeof(data), 1, f);
291
292 data = htons(mapHeight);
293 fwrite(&data, sizeof(data), 1, f);
294
295 // write the map data
296 if(fwrite(map, mapWidth, mapHeight, f) != mapHeight)
297 {
298 ScThrowErr("Error while writing map data");
299 }
300
301 fclose(f);
302 }
303
304 //
305 // loadTiles - Load a texture for the tiles from pixel data.
306 //
loadTiles(const ScPixelData & pixelData,int inTileWidth,int inTileHeight,short border,short inSeperation)307 void ScTilemap::loadTiles(const ScPixelData& pixelData, int inTileWidth, int inTileHeight, short border, short inSeperation)
308 {
309 GLsizei maxtexsize;
310
311 tileWidth = inTileWidth;
312 tileHeight = inTileHeight;
313 seperation = inSeperation;
314
315 textureWidth = 1;
316 while (textureWidth < pixelData.width)
317 textureWidth *= 2;
318
319 textureHeight = 1;
320 while (textureHeight < pixelData.height)
321 textureHeight *= 2;
322
323
324 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxtexsize);
325
326 if (textureWidth > maxtexsize || textureHeight > maxtexsize)
327 {
328 ScThrowErr("Tile texture is to large");
329 }
330
331 paletteWidth = (pixelData.width + seperation) / (tileWidth + seperation);
332 paletteHeight = (pixelData.height + seperation) / (tileHeight + seperation);
333
334 invTileWidth = 1.0 / tileWidth;
335 invTileHeight = 1.0 / tileHeight;
336
337 invTextureWidth = 1.0 / textureWidth;
338 invTextureHeight = 1.0 / textureHeight;
339
340 glGenTextures(1, &tex_id);
341
342 // make sure 2d textures are enabled
343 glEnable(GL_TEXTURE_2D);
344
345 // tell OpenGL what the row length of the image data is
346 glPixelStorei(GL_UNPACK_ROW_LENGTH, pixelData.pitch / pixelData.bytes_pp);
347 switch (pixelData.pitch % 4) {
348 case 0: glPixelStorei(GL_UNPACK_ALIGNMENT, 4); break;
349 case 1: glPixelStorei(GL_UNPACK_ALIGNMENT, 1); break;
350 case 2: glPixelStorei(GL_UNPACK_ALIGNMENT, 2); break;
351 case 3: glPixelStorei(GL_UNPACK_ALIGNMENT, 1); break;
352 }
353
354 // bind to correct texture id
355 glBindTexture(GL_TEXTURE_2D, tex_id);
356
357 // set texture parameters
358 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
359 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
360 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
361 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
362
363 // upload texture to graphics card
364 glTexImage2D(GL_TEXTURE_2D, 0, pixelData.internalformat, textureWidth, textureHeight, 0, pixelData.format, pixelData.type, NULL);
365 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pixelData.width, pixelData.height, pixelData.format, pixelData.type, pixelData.pixels);
366 if (glGetError() != GL_NO_ERROR) {
367 char msg[256];
368 sprintf(msg, "OpenGL error number %d", glGetError());
369 ScThrowErr(msg);
370 }
371
372 // adjust for border
373 useBorder = border != 0;
374 if (useBorder)
375 {
376 tileWidth -= 2;
377 tileHeight -= 2;
378 invTileWidth = 1.0 / tileWidth;
379 invTileHeight = 1.0 / tileHeight;
380 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
381 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
382 }
383 }
384