1494 lines
49 KiB
C++
1494 lines
49 KiB
C++
/*
|
|
This file is part of Warzone 2100.
|
|
Copyright (C) 1999-2004 Eidos Interactive
|
|
Copyright (C) 2005-2013 Warzone 2100 Project
|
|
|
|
Warzone 2100 is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Warzone 2100 is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Warzone 2100; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/**
|
|
* @file terrain.c
|
|
* Draws the terrain.
|
|
* It uses the ground property of every MAPTILE to divide the terrain into different layers.
|
|
* For every layer the GROUND_TYPE (from psGroundTypes) determines the texture and size.
|
|
* The technique used it called "texture splatting".
|
|
* Every layer only draws the spots where that terrain is, and additive blending is used to make transitions smooth.
|
|
* Decals are a kind of hack now, as for some tiles (where decal == true) the old tile is just drawn.
|
|
* The water is drawn using the hardcoded page-80 and page-81 textures.
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "lib/framework/frame.h"
|
|
#include "lib/framework/opengl.h"
|
|
#include "lib/ivis_opengl/ivisdef.h"
|
|
#include "lib/ivis_opengl/imd.h"
|
|
#include "lib/ivis_opengl/piefunc.h"
|
|
#include "lib/ivis_opengl/tex.h"
|
|
#include "lib/ivis_opengl/piedef.h"
|
|
#include "lib/ivis_opengl/piestate.h"
|
|
#include "lib/ivis_opengl/pieclip.h"
|
|
#include "lib/ivis_opengl/piestate.h"
|
|
#include "lib/ivis_opengl/screen.h"
|
|
|
|
#include "terrain.h"
|
|
#include "map.h"
|
|
#include "texture.h"
|
|
#include "display3d.h"
|
|
#include "hci.h"
|
|
#include "loop.h"
|
|
|
|
/**
|
|
* A sector contains all information to draw a square piece of the map.
|
|
* The actual geometry and texture data is not stored in here but in large VBO's.
|
|
* The sector only stores the index and length of the pieces it's going to use.
|
|
*/
|
|
struct Sector
|
|
{
|
|
int geometryOffset; ///< The point in the geometry VBO where our geometry starts
|
|
int geometrySize; ///< The size of our geometry
|
|
int geometryIndexOffset; ///< The point in the index VBO where our triangles start
|
|
int geometryIndexSize; ///< The size of our indices
|
|
int waterOffset; ///< The point in the water VBO where the water geometry starts
|
|
int waterSize; ///< The size of the water geometry
|
|
int waterIndexOffset; ///< The point in the water index VBO where the water triangles start
|
|
int waterIndexSize; ///< The size of our water triangles
|
|
int *textureOffset; ///< An array containing the offsets into the texture VBO for each terrain layer
|
|
int *textureSize; ///< The size of the geometry for this layer for each layer
|
|
int *textureIndexOffset; ///< The offset into the index VBO for the texture for each layer
|
|
int *textureIndexSize; ///< The size of the indices for each layer
|
|
int decalOffset; ///< Index into the decal VBO
|
|
int decalSize; ///< Size of the part of the decal VBO we are going to use
|
|
bool draw; ///< Do we draw this sector this frame?
|
|
bool dirty; ///< Do we need to update the geometry for this sector?
|
|
};
|
|
|
|
/// A vertex with just a position
|
|
struct RenderVertex
|
|
{
|
|
GLfloat x, y, z; // Vertex
|
|
};
|
|
|
|
/// A vertex with a position and texture coordinates
|
|
struct DecalVertex
|
|
{
|
|
GLfloat x, y, z;
|
|
GLfloat u, v; // uv
|
|
};
|
|
|
|
/// The lightmap texture
|
|
static GLuint lightmap_tex_num;
|
|
/// When are we going to update the lightmap next?
|
|
static unsigned int lightmapLastUpdate;
|
|
/// How big is the lightmap?
|
|
static int lightmapWidth;
|
|
static int lightmapHeight;
|
|
/// Lightmap image
|
|
static GLubyte *lightmapPixmap;
|
|
/// Ticks per lightmap refresh
|
|
static const unsigned int LIGHTMAP_REFRESH = 80;
|
|
|
|
/// VBOs
|
|
static GLuint geometryVBO, geometryIndexVBO, textureVBO, textureIndexVBO, decalVBO;
|
|
/// VBOs
|
|
static GLuint waterVBO, waterIndexVBO;
|
|
/// The amount we shift the water textures so the waves appear to be moving
|
|
static float waterOffset;
|
|
|
|
/// These are properties of your videocard and hardware
|
|
static GLint GLmaxElementsVertices, GLmaxElementsIndices;
|
|
|
|
/// The sectors are stored here
|
|
static Sector *sectors;
|
|
/// The default sector size (a sector is sectorSize x sectorSize)
|
|
static int sectorSize = 15;
|
|
/// What is the distance we can see
|
|
static int terrainDistance;
|
|
/// How many sectors have we actually got?
|
|
static int xSectors, ySectors;
|
|
|
|
/// Did we initialise the terrain renderer yet?
|
|
static bool terrainInitalised = false;
|
|
|
|
/// Helper to specify the offset in a VBO
|
|
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
|
|
|
|
/// Helper variables for the DrawRangeElements functions
|
|
GLuint dreStart, dreEnd, dreOffset;
|
|
GLsizei dreCount;
|
|
/// Are we actually drawing something using the DrawRangeElements functions?
|
|
bool drawRangeElementsStarted = false;
|
|
|
|
/// Pass all remaining triangles to OpenGL
|
|
static void finishDrawRangeElements(void)
|
|
{
|
|
if (drawRangeElementsStarted && dreCount > 0)
|
|
{
|
|
ASSERT(dreEnd - dreStart + 1 <= GLmaxElementsVertices, "too many vertices (%i)", (int)(dreEnd - dreStart + 1));
|
|
ASSERT(dreCount <= GLmaxElementsIndices, "too many indices (%i)", (int)dreCount);
|
|
glDrawRangeElements(GL_TRIANGLES,
|
|
dreStart,
|
|
dreEnd,
|
|
dreCount,
|
|
GL_UNSIGNED_INT,
|
|
BUFFER_OFFSET(sizeof(GLuint)*dreOffset));
|
|
}
|
|
drawRangeElementsStarted = false;
|
|
}
|
|
|
|
/**
|
|
* Either draw the elements or batch them to be sent to OpenGL later
|
|
* This improves performance by reducing the amount of OpenGL calls.
|
|
*/
|
|
static void addDrawRangeElements(GLenum mode,
|
|
GLuint start,
|
|
GLuint end,
|
|
GLsizei count,
|
|
GLenum type,
|
|
GLuint offset)
|
|
{
|
|
ASSERT(mode == GL_TRIANGLES, "not supported");
|
|
ASSERT(type == GL_UNSIGNED_INT, "not supported");
|
|
|
|
if (end - start + 1 > GLmaxElementsVertices)
|
|
{
|
|
debug(LOG_WARNING, "A single call provided too much vertices, will operate at reduced performance or crash. Decrease the sector size to fix this.");
|
|
}
|
|
if (count > GLmaxElementsIndices)
|
|
{
|
|
debug(LOG_WARNING, "A single call provided too much indices, will operate at reduced performance or crash. Decrease the sector size to fix this.");
|
|
}
|
|
|
|
if (!drawRangeElementsStarted)
|
|
{
|
|
dreStart = start;
|
|
dreEnd = end;
|
|
dreCount = count;
|
|
dreOffset = offset;
|
|
drawRangeElementsStarted = true;
|
|
return;
|
|
}
|
|
|
|
// check if we can append theoretically and
|
|
// check if this will not go over the bounds advised by the opengl implementation
|
|
if (dreOffset + dreCount != offset ||
|
|
dreCount + count > GLmaxElementsIndices ||
|
|
end - dreStart + 1 > GLmaxElementsVertices)
|
|
{
|
|
finishDrawRangeElements();
|
|
// start anew
|
|
addDrawRangeElements(mode, start, end, count, type, offset);
|
|
}
|
|
else
|
|
{
|
|
// OK to append
|
|
dreCount += count;
|
|
dreEnd = end;
|
|
}
|
|
// make sure we did everything right
|
|
ASSERT(dreEnd - dreStart + 1 <= GLmaxElementsVertices, "too many vertices (%i)", (int)(dreEnd - dreStart + 1));
|
|
ASSERT(dreCount <= GLmaxElementsIndices, "too many indices (%i)", (int)(dreCount));
|
|
}
|
|
|
|
/// Get the colour of the terrain tile at the specified position
|
|
PIELIGHT getTileColour(int x, int y)
|
|
{
|
|
return mapTile(x, y)->colour;
|
|
}
|
|
|
|
/// Set the colour of the tile at the specified position
|
|
void setTileColour(int x, int y, PIELIGHT colour)
|
|
{
|
|
MAPTILE *psTile = mapTile(x, y);
|
|
|
|
psTile->colour = colour;
|
|
}
|
|
|
|
// NOTE: The current (max) texture size of a tile is 128x128. We allow up to a user defined texture size
|
|
// of 2048. This will cause ugly seams for the decals, if user picks a texture size bigger than the tile!
|
|
#define MAX_TILE_TEXTURE_SIZE 128.0f
|
|
/// Set up the texture coordinates for a tile
|
|
static void getTileTexCoords(Vector2f *uv, unsigned int tileNumber)
|
|
{
|
|
/* unmask proper values from compressed data */
|
|
const unsigned short texture = TileNumber_texture(tileNumber);
|
|
const unsigned short tile = TileNumber_tile(tileNumber);
|
|
|
|
/* Used to calculate texture coordinates */
|
|
const float xMult = 1.0f / TILES_IN_PAGE_COLUMN;
|
|
const float yMult = 1.0f / TILES_IN_PAGE_ROW;
|
|
float texsize = (float)getTextureSize();
|
|
float centertile, shiftamount, one;
|
|
Vector2f sP1, sP2, sP3, sP4, sPTemp;
|
|
|
|
// the decals are 128x128 (at this time), we should not go above this value. See note above
|
|
if (texsize > MAX_TILE_TEXTURE_SIZE)
|
|
{
|
|
texsize = MAX_TILE_TEXTURE_SIZE;
|
|
}
|
|
centertile = 0.5f / texsize; //compute center of tile
|
|
shiftamount = (texsize -1.0) / texsize; // 1 pixel border
|
|
one = 1.0f / (TILES_IN_PAGE_COLUMN * texsize);
|
|
|
|
// bump the texture coords, for 1 pixel border, so our range is [.5,(texsize - .5)]
|
|
one += centertile * shiftamount;
|
|
/*
|
|
* Points for flipping the texture around if the tile is flipped or rotated
|
|
* Store the source rect as four points
|
|
*/
|
|
sP1.x = one;
|
|
sP1.y = one;
|
|
sP2.x = xMult - one;
|
|
sP2.y = one;
|
|
sP3.x = xMult - one;
|
|
sP3.y = yMult - one;
|
|
sP4.x = one;
|
|
sP4.y = yMult - one;
|
|
|
|
if (texture & TILE_XFLIP)
|
|
{
|
|
sPTemp = sP1;
|
|
sP1 = sP2;
|
|
sP2 = sPTemp;
|
|
|
|
sPTemp = sP3;
|
|
sP3 = sP4;
|
|
sP4 = sPTemp;
|
|
}
|
|
if (texture & TILE_YFLIP)
|
|
{
|
|
sPTemp = sP1;
|
|
sP1 = sP4;
|
|
sP4 = sPTemp;
|
|
sPTemp = sP2;
|
|
sP2 = sP3;
|
|
sP3 = sPTemp;
|
|
}
|
|
|
|
switch ((texture & TILE_ROTMASK) >> TILE_ROTSHIFT)
|
|
{
|
|
case 1:
|
|
sPTemp = sP1;
|
|
sP1 = sP4;
|
|
sP4 = sP3;
|
|
sP3 = sP2;
|
|
sP2 = sPTemp;
|
|
break;
|
|
case 2:
|
|
sPTemp = sP1;
|
|
sP1 = sP3;
|
|
sP3 = sPTemp;
|
|
sPTemp = sP4;
|
|
sP4 = sP2;
|
|
sP2 = sPTemp;
|
|
break;
|
|
case 3:
|
|
sPTemp = sP1;
|
|
sP1 = sP2;
|
|
sP2 = sP3;
|
|
sP3 = sP4;
|
|
sP4 = sPTemp;
|
|
break;
|
|
}
|
|
uv[0 + 0].x = tileTexInfo[tile].uOffset + sP1.x;
|
|
uv[0 + 0].y = tileTexInfo[tile].vOffset + sP1.y;
|
|
|
|
uv[0 + 2].x = tileTexInfo[tile].uOffset + sP2.x;
|
|
uv[0 + 2].y = tileTexInfo[tile].vOffset + sP2.y;
|
|
|
|
uv[1 + 2].x = tileTexInfo[tile].uOffset + sP3.x;
|
|
uv[1 + 2].y = tileTexInfo[tile].vOffset + sP3.y;
|
|
|
|
uv[1 + 0].x = tileTexInfo[tile].uOffset + sP4.x;
|
|
uv[1 + 0].y = tileTexInfo[tile].vOffset + sP4.y;
|
|
}
|
|
|
|
/// Average the four positions to get the center
|
|
static void averagePos(Vector3i *center, Vector3i *a, Vector3i *b, Vector3i *c, Vector3i *d)
|
|
{
|
|
center->x = (a->x + b->x + c->x + d->x)/4;
|
|
center->y = (a->y + b->y + c->y + d->y)/4;
|
|
center->z = (a->z + b->z + c->z + d->z)/4;
|
|
}
|
|
|
|
/// Is this position next to a water tile?
|
|
static bool isWater(int x, int y)
|
|
{
|
|
bool result = false;
|
|
result = result || (tileOnMap(x ,y ) && terrainType(mapTile(x ,y )) == TER_WATER);
|
|
result = result || (tileOnMap(x-1,y ) && terrainType(mapTile(x-1,y )) == TER_WATER);
|
|
result = result || (tileOnMap(x ,y-1) && terrainType(mapTile(x ,y-1)) == TER_WATER);
|
|
result = result || (tileOnMap(x-1,y-1) && terrainType(mapTile(x-1,y-1)) == TER_WATER);
|
|
return result;
|
|
}
|
|
|
|
/// Get the position of a grid point
|
|
static void getGridPos(Vector3i *result, int x, int y, bool center, bool water)
|
|
{
|
|
if (center)
|
|
{
|
|
Vector3i a,b,c,d;
|
|
getGridPos(&a, x , y , false, water);
|
|
getGridPos(&b, x+1, y , false, water);
|
|
getGridPos(&c, x , y+1, false, water);
|
|
getGridPos(&d, x+1, y+1, false, water);
|
|
averagePos(result, &a, &b, &c, &d);
|
|
return;
|
|
}
|
|
result->x = world_coord(x);
|
|
result->z = world_coord(-y);
|
|
|
|
if (x <= 0 || y <= 0 || x >= mapWidth || y >= mapHeight)
|
|
{
|
|
result->y = 0;
|
|
}
|
|
else
|
|
{
|
|
result->y = map_TileHeight(x, y);
|
|
if (water)
|
|
{
|
|
result->y = map_WaterHeight(x, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Calculate the average texture coordinates of 4 points
|
|
static inline void averageUV(Vector2f *center, Vector2f* uv)
|
|
{
|
|
center->x = (uv[0].x+uv[1].x+uv[2].x+uv[3].x)/4;
|
|
center->y = (uv[0].y+uv[1].y+uv[2].y+uv[3].y)/4;
|
|
}
|
|
|
|
/// Get the texture coordinates for the map position
|
|
static inline void getTexCoords(Vector2f *uv, float x, float y, int groundType)
|
|
{
|
|
uv->x = (x/psGroundTypes[groundType].textureSize);
|
|
uv->y = (y/psGroundTypes[groundType].textureSize);
|
|
}
|
|
|
|
/// Calculate the average colour of 4 points
|
|
static inline void averageColour(PIELIGHT *average, PIELIGHT a, PIELIGHT b,
|
|
PIELIGHT c, PIELIGHT d)
|
|
{
|
|
average->byte.a = (a.byte.a + b.byte.a + c.byte.a + d.byte.a)/4;
|
|
average->byte.r = (a.byte.r + b.byte.r + c.byte.r + d.byte.r)/4;
|
|
average->byte.g = (a.byte.g + b.byte.g + c.byte.g + d.byte.g)/4;
|
|
average->byte.b = (a.byte.b + b.byte.b + c.byte.b + d.byte.b)/4;
|
|
}
|
|
|
|
/**
|
|
* Set the terrain and water geometry for the specified sector
|
|
*/
|
|
static void setSectorGeometry(int x, int y,
|
|
RenderVertex *geometry, RenderVertex *water,
|
|
int *geometrySize, int *waterSize)
|
|
{
|
|
Vector3i pos;
|
|
int i,j;
|
|
for (i = 0; i < sectorSize+1; i++)
|
|
{
|
|
for (j = 0; j < sectorSize+1; j++)
|
|
{
|
|
// set up geometry
|
|
getGridPos(&pos, i+x*sectorSize, j+y*sectorSize, false, false);
|
|
geometry[*geometrySize].x = pos.x;
|
|
geometry[*geometrySize].y = pos.y;
|
|
geometry[*geometrySize].z = pos.z;
|
|
(*geometrySize)++;
|
|
|
|
getGridPos(&pos, i+x*sectorSize, j+y*sectorSize, true, false);
|
|
geometry[*geometrySize].x = pos.x;
|
|
geometry[*geometrySize].y = pos.y;
|
|
geometry[*geometrySize].z = pos.z;
|
|
(*geometrySize)++;
|
|
|
|
getGridPos(&pos, i+x*sectorSize, j+y*sectorSize, false, true);
|
|
water[*waterSize].x = pos.x;
|
|
water[*waterSize].y = pos.y;
|
|
water[*waterSize].z = pos.z;
|
|
(*waterSize)++;
|
|
|
|
getGridPos(&pos, i+x*sectorSize, j+y*sectorSize, true, true);
|
|
water[*waterSize].x = pos.x;
|
|
water[*waterSize].y = pos.y;
|
|
water[*waterSize].z = pos.z;
|
|
(*waterSize)++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the decals for a sector. This takes care of both the geometry and the texture part.
|
|
*/
|
|
static void setSectorDecals(int x, int y,
|
|
DecalVertex *decaldata,
|
|
int *decalSize)
|
|
{
|
|
Vector3i pos;
|
|
Vector2f uv[2][2], center;
|
|
int a,b;
|
|
int i,j;
|
|
|
|
for (i = x*sectorSize; i < x*sectorSize+sectorSize; i++)
|
|
{
|
|
for (j = y*sectorSize; j < y*sectorSize+sectorSize; j++)
|
|
{
|
|
if (i < 0 || j < 0 || i >= mapWidth || j >= mapHeight)
|
|
{
|
|
continue;
|
|
}
|
|
if (TILE_HAS_DECAL(mapTile(i, j)))
|
|
{
|
|
getTileTexCoords(*uv, mapTile(i,j)->texture);
|
|
averageUV(¢er, *uv);
|
|
|
|
getGridPos(&pos, i, j, true, false);
|
|
decaldata[*decalSize].x = pos.x;
|
|
decaldata[*decalSize].y = pos.y;
|
|
decaldata[*decalSize].z = pos.z;
|
|
decaldata[*decalSize].u = center.x;
|
|
decaldata[*decalSize].v = center.y;
|
|
(*decalSize)++;
|
|
a = 0; b = 1;
|
|
getGridPos(&pos, i+a, j+b, false, false);
|
|
decaldata[*decalSize].x = pos.x;
|
|
decaldata[*decalSize].y = pos.y;
|
|
decaldata[*decalSize].z = pos.z;
|
|
decaldata[*decalSize].u = uv[a][b].x;
|
|
decaldata[*decalSize].v = uv[a][b].y;
|
|
(*decalSize)++;
|
|
a = 0; b = 0;
|
|
getGridPos(&pos, i+a, j+b, false, false);
|
|
decaldata[*decalSize].x = pos.x;
|
|
decaldata[*decalSize].y = pos.y;
|
|
decaldata[*decalSize].z = pos.z;
|
|
decaldata[*decalSize].u = uv[a][b].x;
|
|
decaldata[*decalSize].v = uv[a][b].y;
|
|
(*decalSize)++;
|
|
|
|
getGridPos(&pos, i, j, true, false);
|
|
decaldata[*decalSize].x = pos.x;
|
|
decaldata[*decalSize].y = pos.y;
|
|
decaldata[*decalSize].z = pos.z;
|
|
decaldata[*decalSize].u = center.x;
|
|
decaldata[*decalSize].v = center.y;
|
|
(*decalSize)++;
|
|
a = 1; b = 1;
|
|
getGridPos(&pos, i+a, j+b, false, false);
|
|
decaldata[*decalSize].x = pos.x;
|
|
decaldata[*decalSize].y = pos.y;
|
|
decaldata[*decalSize].z = pos.z;
|
|
decaldata[*decalSize].u = uv[a][b].x;
|
|
decaldata[*decalSize].v = uv[a][b].y;
|
|
(*decalSize)++;
|
|
a = 0; b = 1;
|
|
getGridPos(&pos, i+a, j+b, false, false);
|
|
decaldata[*decalSize].x = pos.x;
|
|
decaldata[*decalSize].y = pos.y;
|
|
decaldata[*decalSize].z = pos.z;
|
|
decaldata[*decalSize].u = uv[a][b].x;
|
|
decaldata[*decalSize].v = uv[a][b].y;
|
|
(*decalSize)++;
|
|
|
|
getGridPos(&pos, i, j, true, false);
|
|
decaldata[*decalSize].x = pos.x;
|
|
decaldata[*decalSize].y = pos.y;
|
|
decaldata[*decalSize].z = pos.z;
|
|
decaldata[*decalSize].u = center.x;
|
|
decaldata[*decalSize].v = center.y;
|
|
(*decalSize)++;
|
|
a = 1; b = 0;
|
|
getGridPos(&pos, i+a, j+b, false, false);
|
|
decaldata[*decalSize].x = pos.x;
|
|
decaldata[*decalSize].y = pos.y;
|
|
decaldata[*decalSize].z = pos.z;
|
|
decaldata[*decalSize].u = uv[a][b].x;
|
|
decaldata[*decalSize].v = uv[a][b].y;
|
|
(*decalSize)++;
|
|
a = 1; b = 1;
|
|
getGridPos(&pos, i+a, j+b, false, false);
|
|
decaldata[*decalSize].x = pos.x;
|
|
decaldata[*decalSize].y = pos.y;
|
|
decaldata[*decalSize].z = pos.z;
|
|
decaldata[*decalSize].u = uv[a][b].x;
|
|
decaldata[*decalSize].v = uv[a][b].y;
|
|
(*decalSize)++;
|
|
|
|
getGridPos(&pos, i, j, true, false);
|
|
decaldata[*decalSize].x = pos.x;
|
|
decaldata[*decalSize].y = pos.y;
|
|
decaldata[*decalSize].z = pos.z;
|
|
decaldata[*decalSize].u = center.x;
|
|
decaldata[*decalSize].v = center.y;
|
|
(*decalSize)++;
|
|
a = 0; b = 0;
|
|
getGridPos(&pos, i+a, j+b, false, false);
|
|
decaldata[*decalSize].x = pos.x;
|
|
decaldata[*decalSize].y = pos.y;
|
|
decaldata[*decalSize].z = pos.z;
|
|
decaldata[*decalSize].u = uv[a][b].x;
|
|
decaldata[*decalSize].v = uv[a][b].y;
|
|
(*decalSize)++;
|
|
a = 1; b = 0;
|
|
getGridPos(&pos, i+a, j+b, false, false);
|
|
decaldata[*decalSize].x = pos.x;
|
|
decaldata[*decalSize].y = pos.y;
|
|
decaldata[*decalSize].z = pos.z;
|
|
decaldata[*decalSize].u = uv[a][b].x;
|
|
decaldata[*decalSize].v = uv[a][b].y;
|
|
(*decalSize)++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the sector for when the terrain is changed.
|
|
*/
|
|
static void updateSectorGeometry(int x, int y)
|
|
{
|
|
RenderVertex *geometry;
|
|
RenderVertex *water;
|
|
DecalVertex *decaldata;
|
|
int geometrySize = 0;
|
|
int waterSize = 0;
|
|
int decalSize = 0;
|
|
|
|
geometry = (RenderVertex *)malloc(sizeof(RenderVertex)*sectors[x*ySectors + y].geometrySize);
|
|
water = (RenderVertex *)malloc(sizeof(RenderVertex)*sectors[x*ySectors + y].waterSize);
|
|
|
|
setSectorGeometry(x, y, geometry, water, &geometrySize, &waterSize);
|
|
ASSERT(geometrySize == sectors[x*ySectors + y].geometrySize, "something went seriously wrong updating the terrain");
|
|
ASSERT(waterSize == sectors[x*ySectors + y].waterSize , "something went seriously wrong updating the terrain");
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, geometryVBO); glErrors();
|
|
glBufferSubData(GL_ARRAY_BUFFER, sizeof(RenderVertex)*sectors[x*ySectors + y].geometryOffset,
|
|
sizeof(RenderVertex)*sectors[x*ySectors + y].geometrySize, geometry); glErrors();
|
|
glBindBuffer(GL_ARRAY_BUFFER, waterVBO); glErrors();
|
|
glBufferSubData(GL_ARRAY_BUFFER, sizeof(RenderVertex)*sectors[x*ySectors + y].waterOffset,
|
|
sizeof(RenderVertex)*sectors[x*ySectors + y].waterSize, water); glErrors();
|
|
|
|
free(geometry);
|
|
free(water);
|
|
|
|
if (sectors[x*ySectors + y].decalSize <= 0)
|
|
{
|
|
// Nothing to do here, and glBufferSubData(GL_ARRAY_BUFFER, 0, 0, *) crashes in my graphics driver. Probably shouldn't crash...
|
|
return;
|
|
}
|
|
|
|
decaldata = (DecalVertex *)malloc(sizeof(DecalVertex)*sectors[x*ySectors + y].decalSize);
|
|
setSectorDecals(x, y, decaldata, &decalSize);
|
|
ASSERT(decalSize == sectors[x*ySectors + y].decalSize , "the amount of decals has changed");
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, decalVBO); glErrors();
|
|
glBufferSubData(GL_ARRAY_BUFFER, sizeof(DecalVertex)*sectors[x*ySectors + y].decalOffset,
|
|
sizeof(DecalVertex)*sectors[x*ySectors + y].decalSize, decaldata); glErrors();
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
free (decaldata);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0); // HACK Must unbind GL_ARRAY_BUFFER (don't know if it has to be unbound everywhere), otherwise text rendering may mysteriously crash.
|
|
}
|
|
|
|
/**
|
|
* Mark all tiles that are influenced by this grid point as dirty.
|
|
* Dirty sectors will later get updated by updateSectorGeometry.
|
|
*/
|
|
void markTileDirty(int i, int j)
|
|
{
|
|
int x, y;
|
|
|
|
if (!terrainInitalised)
|
|
{
|
|
return; // will be updated anyway
|
|
}
|
|
|
|
x = i/sectorSize;
|
|
y = j/sectorSize;
|
|
if (x < xSectors && y < ySectors) // could be on the lower or left edge of the map
|
|
{
|
|
sectors[x*ySectors + y].dirty = true;
|
|
}
|
|
|
|
// it could be on an edge, so update for all sectors it is in
|
|
if (x*sectorSize == i && x > 0)
|
|
{
|
|
if (x-1 < xSectors && y < ySectors)
|
|
{
|
|
sectors[(x-1)*ySectors + y].dirty = true;
|
|
}
|
|
}
|
|
if (y*sectorSize == j && y > 0)
|
|
{
|
|
if (x < xSectors && y-1 < ySectors)
|
|
{
|
|
sectors[x*ySectors + (y-1)].dirty = true;
|
|
}
|
|
}
|
|
if (x*sectorSize == i && x > 0 && y*sectorSize == j && y > 0)
|
|
{
|
|
if (x-1 < xSectors && y-1 < ySectors)
|
|
{
|
|
sectors[(x-1)*ySectors + (y-1)].dirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check what the videocard + drivers support and divide the loaded map into sectors that can be drawn.
|
|
* It also determines the lightmap size.
|
|
*/
|
|
bool initTerrain(void)
|
|
{
|
|
int i, j, x, y, a, b, absX, absY;
|
|
PIELIGHT colour[2][2], centerColour;
|
|
int layer = 0;
|
|
|
|
RenderVertex *geometry;
|
|
RenderVertex *water;
|
|
DecalVertex *decaldata;
|
|
int geometrySize, geometryIndexSize;
|
|
int waterSize, waterIndexSize;
|
|
int textureSize, textureIndexSize;
|
|
GLuint *geometryIndex;
|
|
GLuint *waterIndex;
|
|
GLuint *textureIndex;
|
|
PIELIGHT *texture;
|
|
int decalSize;
|
|
int maxSectorSizeIndices, maxSectorSizeVertices;
|
|
bool decreasedSize = false;
|
|
|
|
// this information is useful to prevent crashes with buggy opengl implementations
|
|
glGetIntegerv(GL_MAX_ELEMENTS_VERTICES, &GLmaxElementsVertices);
|
|
glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &GLmaxElementsIndices);
|
|
|
|
// testing for crappy cards
|
|
debug(LOG_TERRAIN, "GL_MAX_ELEMENTS_VERTICES: %i", (int)GLmaxElementsVertices);
|
|
debug(LOG_TERRAIN, "GL_MAX_ELEMENTS_INDICES: %i", (int)GLmaxElementsIndices);
|
|
|
|
// now we know these values, determine the maximum sector size achievable
|
|
maxSectorSizeVertices = iSqrt(GLmaxElementsVertices/2)-1;
|
|
maxSectorSizeIndices = iSqrt(GLmaxElementsIndices/12);
|
|
|
|
debug(LOG_TERRAIN, "preferred sector size: %i", sectorSize);
|
|
debug(LOG_TERRAIN, "maximum sector size due to vertices: %i", maxSectorSizeVertices);
|
|
debug(LOG_TERRAIN, "maximum sector size due to indices: %i", maxSectorSizeIndices);
|
|
|
|
if (sectorSize > maxSectorSizeVertices)
|
|
{
|
|
sectorSize = maxSectorSizeVertices;
|
|
decreasedSize = true;
|
|
}
|
|
if (sectorSize > maxSectorSizeIndices)
|
|
{
|
|
sectorSize = maxSectorSizeIndices;
|
|
decreasedSize = true;
|
|
}
|
|
if (decreasedSize)
|
|
{
|
|
if (sectorSize < 1)
|
|
{
|
|
debug(LOG_WARNING, "GL_MAX_ELEMENTS_VERTICES: %i", (int)GLmaxElementsVertices);
|
|
debug(LOG_WARNING, "GL_MAX_ELEMENTS_INDICES: %i", (int)GLmaxElementsIndices);
|
|
debug(LOG_WARNING, "maximum sector size due to vertices: %i", maxSectorSizeVertices);
|
|
debug(LOG_WARNING, "maximum sector size due to indices: %i", maxSectorSizeIndices);
|
|
debug(LOG_ERROR, "Your graphics card and/or drivers do not seem to support glDrawRangeElements, needed for the terrain renderer.");
|
|
debug(LOG_ERROR, "- Do other 3D games work?");
|
|
debug(LOG_ERROR, "- Did you install the latest drivers correctly?");
|
|
debug(LOG_ERROR, "- Do you have a 3D window manager (Aero/Compiz) running?");
|
|
return false;
|
|
}
|
|
debug(LOG_WARNING, "decreasing sector size to %i to fit graphics card constraints", sectorSize);
|
|
}
|
|
|
|
// +4 = +1 for iHypot rounding, +1 for sector size rounding, +2 for edge of visibility
|
|
terrainDistance = iHypot(visibleTiles.x/2, visibleTiles.y/2)+4+sectorSize/2;
|
|
debug(LOG_TERRAIN, "visible tiles x:%i y: %i", visibleTiles.x, visibleTiles.y);
|
|
debug(LOG_TERRAIN, "terrain view distance: %i", terrainDistance);
|
|
|
|
/////////////////////
|
|
// Create the sectors
|
|
xSectors = (mapWidth +sectorSize-1)/sectorSize;
|
|
ySectors = (mapHeight+sectorSize-1)/sectorSize;
|
|
sectors = (Sector *)malloc(sizeof(Sector)*xSectors*ySectors);
|
|
|
|
////////////////////
|
|
// fill the geometry part of the sectors
|
|
geometry = (RenderVertex *)malloc(sizeof(RenderVertex)*xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2);
|
|
geometryIndex = (GLuint *)malloc(sizeof(GLuint)*xSectors*ySectors*sectorSize*sectorSize*12);
|
|
geometrySize = 0;
|
|
geometryIndexSize = 0;
|
|
|
|
water = (RenderVertex *)malloc(sizeof(RenderVertex)*xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2);
|
|
waterIndex = (GLuint *)malloc(sizeof(GLuint)*xSectors*ySectors*sectorSize*sectorSize*12);
|
|
waterSize = 0;
|
|
waterIndexSize = 0;
|
|
for (x = 0; x < xSectors; x++)
|
|
{
|
|
for (y = 0; y < ySectors; y++)
|
|
{
|
|
sectors[x*ySectors + y].dirty = false;
|
|
sectors[x*ySectors + y].geometryOffset = geometrySize;
|
|
sectors[x*ySectors + y].geometrySize = 0;
|
|
sectors[x*ySectors + y].waterOffset = waterSize;
|
|
sectors[x*ySectors + y].waterSize = 0;
|
|
|
|
setSectorGeometry(x, y, geometry, water, &geometrySize, &waterSize);
|
|
|
|
sectors[x*ySectors + y].geometrySize = geometrySize - sectors[x*ySectors + y].geometryOffset;
|
|
sectors[x*ySectors + y].waterSize = waterSize - sectors[x*ySectors + y].waterOffset;
|
|
// and do the index buffers
|
|
sectors[x*ySectors + y].geometryIndexOffset = geometryIndexSize;
|
|
sectors[x*ySectors + y].geometryIndexSize = 0;
|
|
sectors[x*ySectors + y].waterIndexOffset = waterIndexSize;
|
|
sectors[x*ySectors + y].waterIndexSize = 0;
|
|
|
|
for (i = 0; i < sectorSize; i++)
|
|
{
|
|
for (j = 0; j < sectorSize; j++)
|
|
{
|
|
if (x*sectorSize+i >= mapWidth || y*sectorSize+j >= mapHeight)
|
|
{
|
|
continue; // off map, so skip
|
|
}
|
|
|
|
/* One tile is composed of 4 triangles,
|
|
* we need _2_ vertices per tile (1)
|
|
* e.g. center and bottom left
|
|
* the other 3 vertices are from the adjacent tiles
|
|
* on their top and right.
|
|
* (1) The top row and right column of tiles need 4 vertices per tile
|
|
* because they do not have adjacent tiles on their top and right,
|
|
* that is why we add _1_ row and _1_ column to provide the geometry
|
|
* for these tiles.
|
|
* This is the source of the '*2' and '+1' in the index math below.
|
|
*/
|
|
#define q(i,j,center) ((x*ySectors+y)*(sectorSize+1)*(sectorSize+1)*2 + ((i)*(sectorSize+1)+(j))*2+(center))
|
|
// First triangle
|
|
geometryIndex[geometryIndexSize+0] = q(i ,j ,1); // Center vertex
|
|
geometryIndex[geometryIndexSize+1] = q(i ,j ,0); // Bottom left
|
|
geometryIndex[geometryIndexSize+2] = q(i+1,j ,0); // Bottom right
|
|
// Second triangle
|
|
geometryIndex[geometryIndexSize+3] = q(i ,j ,1); // Center vertex
|
|
geometryIndex[geometryIndexSize+4] = q(i ,j+1,0); // Top left
|
|
geometryIndex[geometryIndexSize+5] = q(i ,j ,0); // Bottom left
|
|
// Third triangle
|
|
geometryIndex[geometryIndexSize+6] = q(i ,j ,1); // Center vertex
|
|
geometryIndex[geometryIndexSize+7] = q(i+1,j+1,0); // Top right
|
|
geometryIndex[geometryIndexSize+8] = q(i ,j+1,0); // Top left
|
|
// Fourth triangle
|
|
geometryIndex[geometryIndexSize+9] = q(i ,j ,1); // Center vertex
|
|
geometryIndex[geometryIndexSize+10] = q(i+1,j ,0); // Bottom right
|
|
geometryIndex[geometryIndexSize+11] = q(i+1,j+1,0); // Top right
|
|
geometryIndexSize += 12;
|
|
if (isWater(i+x*sectorSize,j+y*sectorSize))
|
|
{
|
|
waterIndex[waterIndexSize+0] = q(i ,j ,1);
|
|
waterIndex[waterIndexSize+1] = q(i ,j ,0);
|
|
waterIndex[waterIndexSize+2] = q(i+1,j ,0);
|
|
|
|
waterIndex[waterIndexSize+3] = q(i ,j ,1);
|
|
waterIndex[waterIndexSize+4] = q(i ,j+1,0);
|
|
waterIndex[waterIndexSize+5] = q(i ,j ,0);
|
|
|
|
waterIndex[waterIndexSize+6] = q(i ,j ,1);
|
|
waterIndex[waterIndexSize+7] = q(i+1,j+1,0);
|
|
waterIndex[waterIndexSize+8] = q(i ,j+1,0);
|
|
|
|
waterIndex[waterIndexSize+9] = q(i ,j ,1);
|
|
waterIndex[waterIndexSize+10] = q(i+1,j ,0);
|
|
waterIndex[waterIndexSize+11] = q(i+1,j+1,0);
|
|
waterIndexSize += 12;
|
|
}
|
|
}
|
|
}
|
|
sectors[x*ySectors + y].geometryIndexSize = geometryIndexSize - sectors[x*ySectors + y].geometryIndexOffset;
|
|
sectors[x*ySectors + y].waterIndexSize = waterIndexSize - sectors[x*ySectors + y].waterIndexOffset;
|
|
}
|
|
}
|
|
glGenBuffers(1, &geometryVBO); glErrors();
|
|
glBindBuffer(GL_ARRAY_BUFFER, geometryVBO); glErrors();
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(RenderVertex)*geometrySize, geometry, GL_DYNAMIC_DRAW); glErrors();
|
|
free(geometry);
|
|
|
|
glGenBuffers(1, &geometryIndexVBO); glErrors();
|
|
glBindBuffer(GL_ARRAY_BUFFER, geometryIndexVBO); glErrors();
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(GLuint)*geometryIndexSize, geometryIndex, GL_STATIC_DRAW); glErrors();
|
|
free(geometryIndex);
|
|
|
|
glGenBuffers(1, &waterVBO); glErrors();
|
|
glBindBuffer(GL_ARRAY_BUFFER, waterVBO); glErrors();
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(RenderVertex)*waterSize, water, GL_DYNAMIC_DRAW); glErrors();
|
|
free(water);
|
|
|
|
glGenBuffers(1, &waterIndexVBO); glErrors();
|
|
glBindBuffer(GL_ARRAY_BUFFER, waterIndexVBO); glErrors();
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(GLuint)*waterIndexSize, waterIndex, GL_STATIC_DRAW); glErrors();
|
|
free(waterIndex);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
|
|
////////////////////
|
|
// fill the texture part of the sectors
|
|
texture = (PIELIGHT *)malloc(sizeof(PIELIGHT)*xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2*numGroundTypes);
|
|
textureIndex = (GLuint *)malloc(sizeof(GLuint)*xSectors*ySectors*sectorSize*sectorSize*12*numGroundTypes);
|
|
textureSize = 0;
|
|
textureIndexSize = 0;
|
|
for (layer = 0; layer < numGroundTypes; layer++)
|
|
{
|
|
for (x = 0; x < xSectors; x++)
|
|
{
|
|
for (y = 0; y < ySectors; y++)
|
|
{
|
|
if (layer == 0)
|
|
{
|
|
sectors[x*ySectors + y].textureOffset = (int *)malloc(sizeof(int)*numGroundTypes);
|
|
sectors[x*ySectors + y].textureSize = (int *)malloc(sizeof(int)*numGroundTypes);
|
|
sectors[x*ySectors + y].textureIndexOffset = (int *)malloc(sizeof(int)*numGroundTypes);
|
|
sectors[x*ySectors + y].textureIndexSize = (int *)malloc(sizeof(int)*numGroundTypes);
|
|
}
|
|
|
|
sectors[x*ySectors + y].textureOffset[layer] = textureSize;
|
|
sectors[x*ySectors + y].textureSize[layer] = 0;
|
|
sectors[x*ySectors + y].textureIndexOffset[layer] = textureIndexSize;
|
|
sectors[x*ySectors + y].textureIndexSize[layer] = 0;
|
|
//debug(LOG_WARNING, "offset when filling %i: %i", layer, xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2*layer);
|
|
for (i = 0; i < sectorSize+1; i++)
|
|
{
|
|
for (j = 0; j < sectorSize+1; j++)
|
|
{
|
|
bool draw = false;
|
|
bool off_map;
|
|
|
|
// set transparency
|
|
for (a=0;a<2;a++)
|
|
{
|
|
for(b=0;b<2;b++)
|
|
{
|
|
absX = x*sectorSize+i+a;
|
|
absY = y*sectorSize+j+b;
|
|
colour[a][b].rgba = 0x00FFFFFF; // transparent
|
|
|
|
// extend the terrain type for the bottom and left edges of the map
|
|
off_map = false;
|
|
if (absX == mapWidth)
|
|
{
|
|
off_map = true;
|
|
absX--;
|
|
}
|
|
if (absY == mapHeight)
|
|
{
|
|
off_map = true;
|
|
absY--;
|
|
}
|
|
|
|
if (absX < 0 || absY < 0 || absX >= mapWidth || absY >= mapHeight)
|
|
{
|
|
// not on the map, so don't draw
|
|
continue;
|
|
}
|
|
if (mapTile(absX,absY)->ground == layer)
|
|
{
|
|
colour[a][b].rgba = 0xFFFFFFFF;
|
|
if (!off_map)
|
|
{
|
|
// if this point lies on the edge is may not force this tile to be drawn
|
|
// otherwise this will give a bright line when fog is enabled
|
|
draw = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
texture[xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2*layer+((x*ySectors+y)*(sectorSize+1)*(sectorSize+1)*2 + (i*(sectorSize+1)+j)*2)].rgba = colour[0][0].rgba;
|
|
averageColour(¢erColour, colour[0][0], colour[0][1], colour[1][0], colour[1][1]);
|
|
texture[xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2*layer+((x*ySectors+y)*(sectorSize+1)*(sectorSize+1)*2 + (i*(sectorSize+1)+j)*2+1)].rgba = centerColour.rgba;
|
|
textureSize += 2;
|
|
if ((draw) && i < sectorSize && j < sectorSize)
|
|
{
|
|
textureIndex[textureIndexSize+0] = q(i ,j ,1);
|
|
textureIndex[textureIndexSize+1] = q(i ,j ,0);
|
|
textureIndex[textureIndexSize+2] = q(i+1,j ,0);
|
|
|
|
textureIndex[textureIndexSize+3] = q(i ,j ,1);
|
|
textureIndex[textureIndexSize+4] = q(i ,j+1,0);
|
|
textureIndex[textureIndexSize+5] = q(i ,j ,0);
|
|
|
|
textureIndex[textureIndexSize+6] = q(i ,j ,1);
|
|
textureIndex[textureIndexSize+7] = q(i+1,j+1,0);
|
|
textureIndex[textureIndexSize+8] = q(i ,j+1,0);
|
|
|
|
textureIndex[textureIndexSize+9] = q(i ,j ,1);
|
|
textureIndex[textureIndexSize+10] = q(i+1,j ,0);
|
|
textureIndex[textureIndexSize+11] = q(i+1,j+1,0);
|
|
textureIndexSize += 12;
|
|
}
|
|
|
|
}
|
|
}
|
|
sectors[x*ySectors + y].textureSize[layer] = textureSize - sectors[x*ySectors + y].textureOffset[layer];
|
|
sectors[x*ySectors + y].textureIndexSize[layer] = textureIndexSize - sectors[x*ySectors + y].textureIndexOffset[layer];
|
|
}
|
|
}
|
|
}
|
|
glGenBuffers(1, &textureVBO); glErrors();
|
|
glBindBuffer(GL_ARRAY_BUFFER, textureVBO); glErrors();
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(PIELIGHT)*xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2*numGroundTypes, texture, GL_STATIC_DRAW); glErrors();
|
|
free(texture);
|
|
|
|
glGenBuffers(1, &textureIndexVBO); glErrors();
|
|
glBindBuffer(GL_ARRAY_BUFFER, textureIndexVBO); glErrors();
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(GLuint)*textureIndexSize, textureIndex, GL_STATIC_DRAW); glErrors();
|
|
free(textureIndex);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
// and finally the decals
|
|
decaldata = (DecalVertex *)malloc(sizeof(DecalVertex)*mapWidth*mapHeight*12);
|
|
decalSize = 0;
|
|
for (x = 0; x < xSectors; x++)
|
|
{
|
|
for (y = 0; y < ySectors; y++)
|
|
{
|
|
sectors[x*ySectors + y].decalOffset = decalSize;
|
|
sectors[x*ySectors + y].decalSize = 0;
|
|
setSectorDecals(x, y, decaldata, &decalSize);
|
|
sectors[x*ySectors + y].decalSize = decalSize - sectors[x*ySectors + y].decalOffset;
|
|
}
|
|
}
|
|
debug(LOG_TERRAIN, "%i decals found", decalSize/12);
|
|
glGenBuffers(1, &decalVBO); glErrors();
|
|
glBindBuffer(GL_ARRAY_BUFFER, decalVBO); glErrors();
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(DecalVertex)*decalSize, decaldata, GL_STATIC_DRAW); glErrors();
|
|
free(decaldata);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
lightmap_tex_num = 0;
|
|
lightmapLastUpdate = 0;
|
|
lightmapWidth = 1;
|
|
lightmapHeight = 1;
|
|
// determine the smallest power-of-two size we can use for the lightmap
|
|
while (mapWidth > (lightmapWidth <<= 1)) {}
|
|
while (mapHeight > (lightmapHeight <<= 1)) {}
|
|
debug(LOG_TERRAIN, "the size of the map is %ix%i", mapWidth, mapHeight);
|
|
debug(LOG_TERRAIN, "lightmap texture size is %ix%i", lightmapWidth, lightmapHeight);
|
|
|
|
// Prepare the lightmap pixmap and texture
|
|
lightmapPixmap = (GLubyte *)calloc(lightmapWidth * lightmapHeight, 3 * sizeof(GLubyte));
|
|
if (lightmapPixmap == NULL)
|
|
{
|
|
debug(LOG_FATAL, "Out of memory!");
|
|
abort();
|
|
return false;
|
|
}
|
|
|
|
glGenTextures(1, &lightmap_tex_num);
|
|
glBindTexture(GL_TEXTURE_2D, lightmap_tex_num);
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, lightmapWidth, lightmapHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, lightmapPixmap);
|
|
|
|
terrainInitalised = true;
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0); // HACK Must unbind GL_ARRAY_BUFFER (in this function, at least), otherwise text rendering may mysteriously crash.
|
|
|
|
return true;
|
|
}
|
|
|
|
/// free all memory and opengl buffers used by the terrain renderer
|
|
void shutdownTerrain(void)
|
|
{
|
|
ASSERT_OR_RETURN( ,sectors, "trying to shutdown terrain when it didn't need it!");
|
|
glDeleteBuffers(1, &geometryVBO);
|
|
glDeleteBuffers(1, &geometryIndexVBO);
|
|
glDeleteBuffers(1, &waterVBO);
|
|
glDeleteBuffers(1, &waterIndexVBO);
|
|
glDeleteBuffers(1, &textureVBO);
|
|
glDeleteBuffers(1, &textureIndexVBO);
|
|
glDeleteBuffers(1, &decalVBO);
|
|
|
|
for (int x = 0; x < xSectors; x++)
|
|
{
|
|
for (int y = 0; y < ySectors; y++)
|
|
{
|
|
free(sectors[x*ySectors + y].textureOffset);
|
|
free(sectors[x*ySectors + y].textureSize);
|
|
free(sectors[x*ySectors + y].textureIndexOffset);
|
|
free(sectors[x*ySectors + y].textureIndexSize);
|
|
}
|
|
}
|
|
free(sectors);
|
|
sectors = NULL;
|
|
|
|
glDeleteTextures(1, &lightmap_tex_num);
|
|
free(lightmapPixmap);
|
|
lightmapPixmap = NULL;
|
|
|
|
terrainInitalised = false;
|
|
}
|
|
|
|
/**
|
|
* Update the lightmap and draw the terrain and decals.
|
|
* This function first draws the terrain in black, and then uses additive blending to put the terrain layers
|
|
* on it one by one. Finally the decals are drawn.
|
|
*/
|
|
void drawTerrain(void)
|
|
{
|
|
int i, j, x, y;
|
|
int texPage;
|
|
int layer;
|
|
int offset, size;
|
|
float xPos, yPos, distance;
|
|
const GLfloat paramsX[4] = {1.0f/world_coord(mapWidth)*((float)mapWidth/lightmapWidth), 0, 0, 0};
|
|
const GLfloat paramsY[4] = {0, 0, -1.0f/world_coord(mapHeight)*((float)mapHeight/lightmapHeight), 0};
|
|
|
|
///////////////////////////////////
|
|
glErrors(); // clear error codes
|
|
// set up the lightmap texture
|
|
glActiveTexture(GL_TEXTURE1);
|
|
// bind the texture
|
|
glBindTexture(GL_TEXTURE_2D, lightmap_tex_num);
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
// we limit the framerate of the lightmap, because updating a texture is an expensive operation
|
|
if (realTime - lightmapLastUpdate >= LIGHTMAP_REFRESH)
|
|
{
|
|
lightmapLastUpdate = realTime;
|
|
|
|
for (j = 0; j < mapHeight; ++j)
|
|
{
|
|
for (i = 0; i < mapWidth; ++i)
|
|
{
|
|
MAPTILE *psTile = mapTile(i, j);
|
|
PIELIGHT colour = psTile->colour;
|
|
|
|
if (psTile->tileInfoBits & BITS_GATEWAY && showGateways)
|
|
{
|
|
colour.byte.g = 255;
|
|
}
|
|
if (psTile->tileInfoBits & BITS_MARKED)
|
|
{
|
|
int m = getModularScaledGraphicsTime(2048, 255);
|
|
colour.byte.r = MAX(m, 255 - m);
|
|
}
|
|
|
|
lightmapPixmap[(i + j * lightmapWidth) * 3 + 0] = colour.byte.r;
|
|
lightmapPixmap[(i + j * lightmapWidth) * 3 + 1] = colour.byte.g;
|
|
lightmapPixmap[(i + j * lightmapWidth) * 3 + 2] = colour.byte.b;
|
|
|
|
if (!pie_GetFogStatus())
|
|
{
|
|
// fade to black at the edges of the visible terrain area
|
|
const float playerX = map_coordf(player.p.x);
|
|
const float playerY = map_coordf(player.p.z);
|
|
|
|
const float distA = i-(playerX-visibleTiles.x/2);
|
|
const float distB = (playerX+visibleTiles.x/2)-i;
|
|
const float distC = j-(playerY-visibleTiles.y/2);
|
|
const float distD = (playerY+visibleTiles.y/2)-j;
|
|
float darken, distToEdge;
|
|
|
|
// calculate the distance to the closest edge of the visible map
|
|
// determine the smallest distance
|
|
distToEdge = distA;
|
|
if (distB < distToEdge) distToEdge = distB;
|
|
if (distC < distToEdge) distToEdge = distC;
|
|
if (distD < distToEdge) distToEdge = distD;
|
|
|
|
darken = (distToEdge)/2.0f;
|
|
if (darken <= 0)
|
|
{
|
|
lightmapPixmap[(i + j * lightmapWidth) * 3 + 0] = 0;
|
|
lightmapPixmap[(i + j * lightmapWidth) * 3 + 1] = 0;
|
|
lightmapPixmap[(i + j * lightmapWidth) * 3 + 2] = 0;
|
|
}
|
|
else if (darken < 1)
|
|
{
|
|
lightmapPixmap[(i + j * lightmapWidth) * 3 + 0] *= darken;
|
|
lightmapPixmap[(i + j * lightmapWidth) * 3 + 1] *= darken;
|
|
lightmapPixmap[(i + j * lightmapWidth) * 3 + 2] *= darken;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, lightmapWidth, lightmapHeight, GL_RGB, GL_UNSIGNED_BYTE, lightmapPixmap);
|
|
}
|
|
|
|
///////////////////////////////////
|
|
// terrain culling
|
|
for (x = 0; x < xSectors; x++)
|
|
{
|
|
for (y = 0; y < ySectors; y++)
|
|
{
|
|
xPos = world_coord(x*sectorSize+sectorSize/2);
|
|
yPos = world_coord(y*sectorSize+sectorSize/2);
|
|
distance = pow(player.p.x - xPos, 2) + pow(player.p.z - yPos, 2);
|
|
|
|
if (distance > pow((double)world_coord(terrainDistance), 2))
|
|
{
|
|
sectors[x*ySectors + y].draw = false;
|
|
}
|
|
else
|
|
{
|
|
sectors[x*ySectors + y].draw = true;
|
|
if (sectors[x*ySectors + y].dirty)
|
|
{
|
|
updateSectorGeometry(x,y);
|
|
sectors[x*ySectors + y].dirty = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// enable texture coord generation
|
|
glEnable(GL_TEXTURE_GEN_S); glErrors();
|
|
glEnable(GL_TEXTURE_GEN_T); glErrors();
|
|
|
|
// set the parameters for the texture coord generation
|
|
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
|
|
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
|
|
glTexGenfv(GL_S, GL_OBJECT_PLANE, paramsX);
|
|
glTexGenfv(GL_T, GL_OBJECT_PLANE, paramsY);
|
|
|
|
// shift the lightmap half a tile as lights are supposed to be placed at the center of a tile
|
|
glMatrixMode(GL_TEXTURE);
|
|
glLoadIdentity();
|
|
glTranslatef(1.0/lightmapWidth/2, 1.0/lightmapHeight/2, 0);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
//////////////////////////////////////
|
|
// canvas to draw on
|
|
pie_SetTexturePage(TEXPAGE_NONE);
|
|
pie_SetRendMode(REND_OPAQUE);
|
|
|
|
// we only draw in the depth buffer of using fog of war, as the clear color is black then
|
|
if (pie_GetFogStatus())
|
|
{
|
|
// FIXME: with fog enabled we draw this also to the color buffer. A fogbox might be faster.
|
|
glDisable(GL_FOG);
|
|
glColor4ub(0x00, 0x00, 0x00, 0xFF);
|
|
}
|
|
else
|
|
{
|
|
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
|
}
|
|
|
|
// draw slightly higher distance than it actually is so it will not
|
|
// by accident obscure the actual terrain
|
|
glEnable(GL_POLYGON_OFFSET_FILL);
|
|
glPolygonOffset(0.1f, 1.0f);
|
|
|
|
// bind the vertex buffer
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geometryIndexVBO); glErrors();
|
|
glBindBuffer(GL_ARRAY_BUFFER, geometryVBO); glErrors();
|
|
|
|
glVertexPointer(3, GL_FLOAT, sizeof(RenderVertex), BUFFER_OFFSET(0)); glErrors();
|
|
|
|
for (x = 0; x < xSectors; x++)
|
|
{
|
|
for (y = 0; y < ySectors; y++)
|
|
{
|
|
if (sectors[x*ySectors + y].draw)
|
|
{
|
|
addDrawRangeElements(GL_TRIANGLES,
|
|
sectors[x*ySectors + y].geometryOffset,
|
|
sectors[x*ySectors + y].geometryOffset+sectors[x*ySectors + y].geometrySize,
|
|
sectors[x*ySectors + y].geometryIndexSize,
|
|
GL_UNSIGNED_INT,
|
|
sectors[x*ySectors + y].geometryIndexOffset);
|
|
}
|
|
}
|
|
}
|
|
finishDrawRangeElements();
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
|
|
if (pie_GetFogStatus())
|
|
{
|
|
glEnable(GL_FOG); // resync fog state
|
|
glColor4ub(0xFF, 0xFF, 0xFF, 0xFF);
|
|
}
|
|
else
|
|
{
|
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
}
|
|
|
|
// disable the depth offset
|
|
glDisable(GL_POLYGON_OFFSET_FILL);
|
|
|
|
///////////////////////////////////
|
|
// terrain
|
|
|
|
glEnableClientState(GL_COLOR_ARRAY);
|
|
|
|
// set up for texture coord generation
|
|
glEnable(GL_TEXTURE_GEN_S);
|
|
glEnable(GL_TEXTURE_GEN_T);
|
|
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
|
|
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
|
|
|
|
// additive blending
|
|
pie_SetRendMode(REND_ADDITIVE);
|
|
|
|
// only draw colors
|
|
glDepthMask(GL_FALSE);
|
|
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, textureIndexVBO); glErrors();
|
|
|
|
// load the vertex (geometry) buffer
|
|
glBindBuffer(GL_ARRAY_BUFFER, geometryVBO);
|
|
glVertexPointer(3, GL_FLOAT, sizeof(RenderVertex), BUFFER_OFFSET(0)); glErrors();
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, textureVBO);
|
|
|
|
ASSERT_OR_RETURN( , psGroundTypes, "Ground type was not set, no textures will be seen.");
|
|
|
|
// draw each layer separately
|
|
for (layer = 0; layer < numGroundTypes; layer++)
|
|
{
|
|
const GLfloat paramsX[4] = {0, 0, -1.0f/world_coord(psGroundTypes[layer].textureSize), 0};
|
|
const GLfloat paramsY[4] = {1.0f/world_coord(psGroundTypes[layer].textureSize), 0, 0, 0};
|
|
|
|
// load the texture
|
|
texPage = iV_GetTexture(psGroundTypes[layer].textureName);
|
|
pie_SetTexturePage(texPage);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
|
|
// set the texture generation parameters
|
|
glTexGenfv(GL_S, GL_OBJECT_PLANE, paramsX);
|
|
glTexGenfv(GL_T, GL_OBJECT_PLANE, paramsY);
|
|
|
|
// load the color buffer
|
|
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PIELIGHT), BUFFER_OFFSET(sizeof(PIELIGHT)*xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2*layer));
|
|
|
|
for (x = 0; x < xSectors; x++)
|
|
{
|
|
for (y = 0; y < ySectors; y++)
|
|
{
|
|
if (sectors[x*ySectors + y].draw)
|
|
{
|
|
addDrawRangeElements(GL_TRIANGLES,
|
|
sectors[x*ySectors + y].geometryOffset,
|
|
sectors[x*ySectors + y].geometryOffset+sectors[x*ySectors + y].geometrySize,
|
|
sectors[x*ySectors + y].textureIndexSize[layer],
|
|
GL_UNSIGNED_INT,
|
|
sectors[x*ySectors + y].textureIndexOffset[layer]);
|
|
}
|
|
}
|
|
}
|
|
finishDrawRangeElements();
|
|
}
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
// we don't need this one anymore
|
|
glDisableClientState(GL_COLOR_ARRAY);
|
|
|
|
// no more texture generation
|
|
glDisable(GL_TEXTURE_GEN_S); glErrors();
|
|
glDisable(GL_TEXTURE_GEN_T); glErrors();
|
|
|
|
//////////////////////////////////
|
|
// decals
|
|
|
|
// select the terrain texture page
|
|
pie_SetTexturePage(terrainPage); glErrors();
|
|
|
|
// use the alpha to blend
|
|
pie_SetRendMode(REND_ALPHA);
|
|
// don't blend decals with another color
|
|
glColor3f( 1.f, 1.f, 1.f);
|
|
|
|
// and the texture coordinates buffer
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY); glErrors();
|
|
glEnableClientState(GL_VERTEX_ARRAY); glErrors();
|
|
glBindBuffer(GL_ARRAY_BUFFER, decalVBO); glErrors();
|
|
glVertexPointer(3, GL_FLOAT, sizeof(DecalVertex), BUFFER_OFFSET(0)); glErrors();
|
|
glTexCoordPointer(2, GL_FLOAT, sizeof(DecalVertex), BUFFER_OFFSET(12)); glErrors();
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
size = 0;
|
|
offset = 0;
|
|
for (x = 0; x < xSectors; x++)
|
|
{
|
|
for (y = 0; y < ySectors + 1; y++)
|
|
{
|
|
if (y < ySectors && offset + size == sectors[x*ySectors + y].decalOffset && sectors[x*ySectors + y].draw)
|
|
{
|
|
// append
|
|
size += sectors[x*ySectors + y].decalSize;
|
|
continue;
|
|
}
|
|
// can't append, so draw what we have and start anew
|
|
if (size > 0)
|
|
{
|
|
glDrawArrays(GL_TRIANGLES, offset, size); glErrors();
|
|
}
|
|
size = 0;
|
|
if (y < ySectors && sectors[x*ySectors + y].draw)
|
|
{
|
|
offset = sectors[x*ySectors + y].decalOffset;
|
|
size = sectors[x*ySectors + y].decalSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////
|
|
// disable the lightmap texture
|
|
glActiveTexture(GL_TEXTURE1);
|
|
glDisable(GL_TEXTURE_2D);
|
|
glDisable(GL_TEXTURE_GEN_S);
|
|
glDisable(GL_TEXTURE_GEN_T);
|
|
glMatrixMode(GL_TEXTURE);
|
|
glLoadIdentity();
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glLoadIdentity();
|
|
glMatrixMode(GL_MODELVIEW);
|
|
|
|
// leave everything in a sane state so it won't mess up somewhere else
|
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
|
|
glDepthMask(GL_TRUE);
|
|
|
|
//glBindBuffer(GL_ARRAY_BUFFER, 0); // HACK Must unbind GL_ARRAY_BUFFER (don't know if it has to be unbound everywhere), otherwise text rendering may mysteriously crash.
|
|
}
|
|
|
|
/**
|
|
* Draw the water.
|
|
*/
|
|
void drawWater(void)
|
|
{
|
|
int x, y;
|
|
const GLfloat paramsX[4] = {0, 0, -1.0f/world_coord(4), 0};
|
|
const GLfloat paramsY[4] = {1.0f/world_coord(4), 0, 0, 0};
|
|
const GLfloat paramsX2[4] = {0, 0, -1.0f/world_coord(5), 0};
|
|
const GLfloat paramsY2[4] = {1.0f/world_coord(5), 0, 0, 0};
|
|
const GLfloat white[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
|
const GLfloat fogColour[4] = {pie_GetFogColour().byte.r/255.f,
|
|
pie_GetFogColour().byte.g/255.f,
|
|
pie_GetFogColour().byte.b/255.f,
|
|
pie_GetFogColour().byte.a/255.f};
|
|
|
|
glEnable(GL_TEXTURE_GEN_S); glErrors();
|
|
glEnable(GL_TEXTURE_GEN_T); glErrors();
|
|
|
|
glColor4ub(0xFF, 0xFF, 0xFF, 0xFF);
|
|
glDepthMask(GL_FALSE);
|
|
|
|
if (pie_GetFogStatus())
|
|
{
|
|
glFogfv(GL_FOG_COLOR, white);
|
|
}
|
|
|
|
// first texture unit
|
|
pie_SetTexturePage(iV_GetTexture("page-80-water-1.png")); glErrors();
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
|
|
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
|
|
glTexGenfv(GL_S, GL_OBJECT_PLANE, paramsX);
|
|
glTexGenfv(GL_T, GL_OBJECT_PLANE, paramsY);
|
|
glMatrixMode(GL_TEXTURE);
|
|
glLoadIdentity();
|
|
glTranslatef(waterOffset, 0, 0);
|
|
|
|
// multiplicative blending
|
|
pie_SetRendMode(REND_MULTIPLICATIVE);
|
|
|
|
// second texture unit
|
|
glActiveTexture(GL_TEXTURE1);
|
|
glBindTexture(GL_TEXTURE_2D, pie_Texture(iV_GetTexture("page-81-water-2.png")));
|
|
glErrors();
|
|
glEnable(GL_TEXTURE_2D);
|
|
glEnable(GL_TEXTURE_GEN_S); glErrors();
|
|
glEnable(GL_TEXTURE_GEN_T); glErrors();
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
|
|
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
|
|
glTexGenfv(GL_S, GL_OBJECT_PLANE, paramsX2);
|
|
glTexGenfv(GL_T, GL_OBJECT_PLANE, paramsY2);
|
|
glLoadIdentity();
|
|
glTranslatef(-waterOffset, 0, 0);
|
|
|
|
// bind the vertex buffer
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, waterIndexVBO); glErrors();
|
|
glBindBuffer(GL_ARRAY_BUFFER, waterVBO); glErrors();
|
|
|
|
glVertexPointer(3, GL_FLOAT, sizeof(RenderVertex), BUFFER_OFFSET(0)); glErrors();
|
|
|
|
for (x = 0; x < xSectors; x++)
|
|
{
|
|
for (y = 0; y < ySectors; y++)
|
|
{
|
|
if (sectors[x*ySectors + y].draw)
|
|
{
|
|
addDrawRangeElements(GL_TRIANGLES,
|
|
sectors[x*ySectors + y].geometryOffset,
|
|
sectors[x*ySectors + y].geometryOffset+sectors[x*ySectors + y].geometrySize,
|
|
sectors[x*ySectors + y].waterIndexSize,
|
|
GL_UNSIGNED_INT,
|
|
sectors[x*ySectors + y].waterIndexOffset);
|
|
}
|
|
}
|
|
}
|
|
finishDrawRangeElements();
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
|
|
// move the water
|
|
if(!gamePaused())
|
|
{
|
|
waterOffset += graphicsTimeAdjustedIncrement(0.1f);
|
|
}
|
|
|
|
// disable second texture
|
|
glLoadIdentity();
|
|
glDisable(GL_TEXTURE_2D);
|
|
glDisable(GL_TEXTURE_GEN_S);
|
|
glDisable(GL_TEXTURE_GEN_T);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
if (pie_GetFogStatus())
|
|
{
|
|
glFogfv(GL_FOG_COLOR, fogColour); // resync fog state
|
|
}
|
|
|
|
// clean up
|
|
glDepthMask(GL_TRUE);
|
|
glLoadIdentity();
|
|
glDisable(GL_TEXTURE_GEN_S);
|
|
glDisable(GL_TEXTURE_GEN_T);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glDisable(GL_TEXTURE_GEN_S);
|
|
glDisable(GL_TEXTURE_GEN_T);
|
|
|
|
//glBindBuffer(GL_ARRAY_BUFFER, 0); // HACK Must unbind GL_ARRAY_BUFFER (don't know if it has to be unbound everywhere), otherwise text rendering may mysteriously crash.
|
|
}
|