/* Initialise the Trig tables */ bool trigInitialise(void) { uint64_t test; uint32_t crc; uint32_t i; // Generate tables. STATIC_ASSERT(sizeof(trigSinTable)/sizeof(*trigSinTable) == 0x4001); for (i = 0; i != 0x4001; ++i) { trigSinTable[i] = (int)(0x10000*sin(i*(M_PI/0x8000)) + 0.5) - !!i; // -!!i = subtract 1, unless i == 0. } STATIC_ASSERT(sizeof(trigAtanTable)/sizeof(*trigAtanTable) == 0x2001); for (i = 0; i != 0x2001; ++i) { trigAtanTable[i] = (int)(0x8000/M_PI*atan((double)i/0x2000) + 0.5); } // Check tables are correct. crc = ~crcSumU16(0, trigSinTable, sizeof(trigSinTable)/sizeof(*trigSinTable)); ASSERT(crc == 0x6D3C8DB5, "Bad trigSinTable CRC = 0x%08X, sin function is broken.", crc); crc = ~crcSumU16(0, trigAtanTable, sizeof(trigAtanTable)/sizeof(*trigAtanTable)); ASSERT(crc == 0xD2797F85, "Bad trigAtanTable CRC = 0x%08X, atan function is broken.", crc); // Test large and small square roots. for (test = 0x0000; test != 0x10000; ++test) { uint64_t lower = test*test; uint64_t upper = (test + 1)*(test + 1) - 1; ASSERT((uint32_t)iSqrt(lower) == test, "Sanity check failed, sqrt(%"PRIu64") gave %"PRIu32" instead of %"PRIu64"!", lower, i64Sqrt(lower), test); ASSERT((uint32_t)iSqrt(upper) == test, "Sanity check failed, sqrt(%"PRIu64") gave %"PRIu32" instead of %"PRIu64"!", upper, i64Sqrt(upper), test); } for (test = 0xFFFE0000; test != 0x00020000; test = (test + 1)&0xFFFFFFFF) { uint64_t lower = test*test; uint64_t upper = (test + 1)*(test + 1) - 1; ASSERT((uint32_t)i64Sqrt(lower) == test, "Sanity check failed, sqrt(%"PRIu64") gave %"PRIu32" instead of %"PRIu64"!", lower, i64Sqrt(lower), test); ASSERT((uint32_t)i64Sqrt(upper) == test, "Sanity check failed, sqrt(%"PRIu64") gave %"PRIu32" instead of %"PRIu64"!", upper, i64Sqrt(upper), test); } return 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; // call VBO support hack before using it screen_EnableVBO(); // 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); glError(); glBindBuffer(GL_ARRAY_BUFFER, geometryVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(RenderVertex)*geometrySize, geometry, GL_DYNAMIC_DRAW); glError(); free(geometry); glGenBuffers(1, &geometryIndexVBO); glError(); glBindBuffer(GL_ARRAY_BUFFER, geometryIndexVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(GLuint)*geometryIndexSize, geometryIndex, GL_STATIC_DRAW); glError(); free(geometryIndex); glGenBuffers(1, &waterVBO); glError(); glBindBuffer(GL_ARRAY_BUFFER, waterVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(RenderVertex)*waterSize, water, GL_DYNAMIC_DRAW); glError(); free(water); glGenBuffers(1, &waterIndexVBO); glError(); glBindBuffer(GL_ARRAY_BUFFER, waterIndexVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(GLuint)*waterIndexSize, waterIndex, GL_STATIC_DRAW); glError(); 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); glError(); glBindBuffer(GL_ARRAY_BUFFER, textureVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(PIELIGHT)*xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2*numGroundTypes, texture, GL_STATIC_DRAW); glError(); free(texture); glGenBuffers(1, &textureIndexVBO); glError(); glBindBuffer(GL_ARRAY_BUFFER, textureIndexVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(GLuint)*textureIndexSize, textureIndex, GL_STATIC_DRAW); glError(); 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); glError(); glBindBuffer(GL_ARRAY_BUFFER, decalVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(DecalVertex)*decalSize, decaldata, GL_STATIC_DRAW); glError(); 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); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); 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; }