/* Copyright (C) 2005 by Ruben Henner Zilibowitz Part of the Toy Cars Project http://toycars.sourceforge.net This program is free software; you can redistribute it and/or modify it under the terms of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the COPYING file for more details. */ /* * ScTilemap.cpp * Scoobie * * Created by Ruben Henner Zilibowitz on 25/11/04. * */ #include "ScTilemap.h" #include "ScView.h" #include "ScException.h" #ifndef WIN32 || __WIN32__ #include #endif #include #include #ifndef ntohs const short kShortConst = 1; #define is_bigendian() ( (*(char*)&kShortConst) == 0 ) unsigned short ntohs(unsigned short x) { if (is_bigendian()) return x; unsigned char *y = (unsigned char *)(&x); unsigned char z[2] = {y[1],y[0]}; x = *(unsigned short *)z; return x; } #endif #ifndef htons #define htons ntohs #endif // // constructor - Loads tiles texture from pixel data. Call loadMap to // load a tile map. Or call newMap to create an empty map. // // Note: for speed reasons only a single texture is allowed to store the // tiles. The texture is the smallest power of two size which contains // the width and height of the pixel data. For this reason it is advisable // to create your tile image with power of two dimensions. // // border must be 0 or 1. This applies to EACH tile, thereby making the // resulting tiles 2 pixels smaller in width and in height if it is 1. // This is desirable if you want linear filtering to be used. // ScTilemap::ScTilemap(const ScPixelData& pixelData, int inTileWidth, int inTileHeight, short border, short inSeperation, bool inBlend) : ScPanel(0, 0), mapWidth(0), mapHeight(0), useBlending(inBlend), tex_id(0), map(NULL) { loadTiles(pixelData, inTileWidth, inTileHeight, border, inSeperation); } // // destructor // ScTilemap::~ScTilemap() { glDeleteTextures(1, &tex_id); if (map) delete [] map; } // // doDraw - Draws the portion of the tile map which is in the currently // active view rect. // void ScTilemap::doDraw() { int start_tile_x, end_tile_x; int start_tile_y, end_tile_y; int map_x, map_y, tile; ScFloat loc_x, loc_y; start_tile_x = int((ScView::sActiveViewRect->left - bounds.left) * invTileWidth) - 1; start_tile_y = int((ScView::sActiveViewRect->bottom - bounds.bottom) * invTileHeight) - 1; end_tile_x = int((ScView::sActiveViewRect->right - bounds.left) * invTileWidth) + 1; end_tile_y = int((ScView::sActiveViewRect->top - bounds.bottom) * invTileHeight) + 1; glColor4ub(255, 255, 255, 255); glEnable(GL_TEXTURE_2D); if (useBlending) glEnable(GL_BLEND); else glDisable(GL_BLEND); glBindTexture(GL_TEXTURE_2D, tex_id); glBegin(GL_QUADS); for (map_y = start_tile_y; map_y < end_tile_y; map_y++) { loc_y = map_y*tileHeight + bounds.bottom; for (map_x = start_tile_x; map_x < end_tile_x; map_x++) { loc_x = map_x*tileWidth + bounds.left; if(map_y >= 0 && map_y < mapHeight && map_x >= 0 && map_x < mapWidth) tile = map[map_y*mapWidth + map_x]; else tile = 255; if (tile != 255) drawTile(loc_x, loc_y, tile); } } glEnd(); } // // drawTile - Draws a single tile at a given location // void ScTilemap::drawTile(ScFloat x, ScFloat y, int tile) { static ScFloat tx1; static ScFloat ty1; static ScFloat tx2; static ScFloat ty2; static ScFloat x1; static ScFloat y1; static ScFloat x2; static ScFloat y2; tx1 = (tile % paletteWidth) * (tileWidth + seperation) * invTextureWidth; ty1 = (tile / paletteWidth) * (tileHeight + seperation) * invTextureHeight; tx2 = tx1 + (tileWidth * invTextureWidth); ty2 = ty1 + (tileHeight * invTextureHeight); x1 = x; y1 = y; x2 = x1 + tileWidth; y2 = y1 + tileHeight; if (useBorder) { tx1 += invTextureWidth; ty1 += invTextureHeight; tx2 -= invTextureWidth; ty2 -= invTextureHeight; } glTexCoord2d(tx1, ty1); glVertex2d(x1, y1); glTexCoord2d(tx2, ty1); glVertex2d(x2, y1); glTexCoord2d(tx2, ty2); glVertex2d(x2, y2); glTexCoord2d(tx1, ty2); glVertex2d(x1, y2); } // // newMap - Removes an old map data and makes new map of given dimensions // clearing all tiles to given value. // void ScTilemap::newMap(int cols, int rows, unsigned char clearTile) { // store map dimensions mapWidth = cols; mapHeight = rows; // set the bounds bounds.right = bounds.left + (mapWidth * tileWidth); bounds.top = bounds.bottom + (mapHeight * tileHeight); // free current map (if any) if (map) { delete [] map; map = NULL; } // allocate memory for new map map = new unsigned char [mapWidth * mapHeight]; if(!map) { ScThrowErr("Could not allocate memory for map"); } // clear map int k = mapWidth*mapHeight; while (k--) { map[k] = clearTile; } } // // loadMap - Loads map data from a file. Removes any existing map data. // void ScTilemap::loadMap(const char *filename) { FILE *f; char header[5]; // open the file f = fopen(filename, "r"); if(!f) { //printf("Failed to open map file: %s\n", filename); ScThrowErr("Failed to open map file"); } // verify map header exists header[4] = '\0'; fread(header, 1, 4, f); if (strcmp(header, "MAP ") != 0) { fclose(f); ScThrowErr("Map file header does not exist"); } // read dimensions fread(&mapWidth, sizeof(mapWidth), 1, f); fread(&mapHeight, sizeof(mapHeight), 1, f); mapWidth = ntohs(mapWidth); mapHeight = ntohs(mapHeight); // set the bounds bounds.right = bounds.left + (mapWidth * tileWidth); bounds.top = bounds.bottom + (mapHeight * tileHeight); // free current map (if any) if (map) { delete [] map; map = NULL; } // allocate memory for tile map map = new unsigned char [mapWidth * mapHeight]; if(!map) { ScThrowErr("Could not allocate memory for map"); } // read the map data if(fread(map, mapWidth, mapHeight, f) != mapHeight) { delete [] map; fclose(f); ScThrowErr("Error while loading map"); } fclose(f); } // // saveMap - Saves the map to a file. // void ScTilemap::saveMap(const char *filename) { FILE *f; unsigned short data; // check if map exists if (!map) { ScThrowErr("Map does not exist"); } // create the file f = fopen(filename, "w"); if(!f) { ScThrowErr("Failed to create map file"); } // write map header fwrite("MAP ", 1, 4, f); // write dimensions data = htons(mapWidth); fwrite(&data, sizeof(data), 1, f); data = htons(mapHeight); fwrite(&data, sizeof(data), 1, f); // write the map data if(fwrite(map, mapWidth, mapHeight, f) != mapHeight) { ScThrowErr("Error while writing map data"); } fclose(f); } // // loadTiles - Load a texture for the tiles from pixel data. // void ScTilemap::loadTiles(const ScPixelData& pixelData, int inTileWidth, int inTileHeight, short border, short inSeperation) { GLsizei maxtexsize; tileWidth = inTileWidth; tileHeight = inTileHeight; seperation = inSeperation; textureWidth = 1; while (textureWidth < pixelData.width) textureWidth *= 2; textureHeight = 1; while (textureHeight < pixelData.height) textureHeight *= 2; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxtexsize); if (textureWidth > maxtexsize || textureHeight > maxtexsize) { ScThrowErr("Tile texture is to large"); } paletteWidth = (pixelData.width + seperation) / (tileWidth + seperation); paletteHeight = (pixelData.height + seperation) / (tileHeight + seperation); invTileWidth = 1.0 / tileWidth; invTileHeight = 1.0 / tileHeight; invTextureWidth = 1.0 / textureWidth; invTextureHeight = 1.0 / textureHeight; glGenTextures(1, &tex_id); // make sure 2d textures are enabled glEnable(GL_TEXTURE_2D); // tell OpenGL what the row length of the image data is glPixelStorei(GL_UNPACK_ROW_LENGTH, pixelData.pitch / pixelData.bytes_pp); switch (pixelData.pitch % 4) { case 0: glPixelStorei(GL_UNPACK_ALIGNMENT, 4); break; case 1: glPixelStorei(GL_UNPACK_ALIGNMENT, 1); break; case 2: glPixelStorei(GL_UNPACK_ALIGNMENT, 2); break; case 3: glPixelStorei(GL_UNPACK_ALIGNMENT, 1); break; } // bind to correct texture id glBindTexture(GL_TEXTURE_2D, tex_id); // set texture parameters glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); // upload texture to graphics card glTexImage2D(GL_TEXTURE_2D, 0, pixelData.internalformat, textureWidth, textureHeight, 0, pixelData.format, pixelData.type, NULL); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pixelData.width, pixelData.height, pixelData.format, pixelData.type, pixelData.pixels); if (glGetError() != GL_NO_ERROR) { char msg[256]; sprintf(msg, "OpenGL error number %d", glGetError()); ScThrowErr(msg); } // adjust for border useBorder = border != 0; if (useBorder) { tileWidth -= 2; tileHeight -= 2; invTileWidth = 1.0 / tileWidth; invTileHeight = 1.0 / tileHeight; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } }