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