// Demeter Terrain Visualization Library by Clay Fowler // Copyright (C) 2002 Clay Fowler // $ID$ /* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "Demeter/Terrain.h" #include "Demeter/Brush.h" #include #include #ifndef _WIN32 #include #undef GLX_GLXEXT_LEGACY #include #endif using namespace Demeter; using namespace std; float numBlocks = 0.0f; float numLevels = 0.0f; float hashDelta = 0.0f; map < string, TextureGenerator * >Terrain::m_TextureGenerators; Brush brush(20); // Diagnostics vector < GLuint > AllocatedTextures; PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB_ptr; PFNGLACTIVETEXTUREARBPROC glActiveTextureARB_ptr; PFNGLLOCKARRAYSEXTPROC glLockArraysEXT_ptr; PFNGLUNLOCKARRAYSEXTPROC glUnlockArraysEXT_ptr; PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB_ptr; PFNGLXGETPROCADDRESSARBPROC glXGetProcAddressARB_ptr; extern void SafelyFillString(char *szSource, char *szTarget, int targetSize); bool IsPowerOf2(double number) { const int MAX_POWER = 1024; bool isPowerOf2 = false; for (int i = 0; i < MAX_POWER && !isPowerOf2; i++) { if (pow(2.0, i) == number) isPowerOf2 = true; } return isPowerOf2; } bool IsPowerOf2Plus1(double number) { const int MAX_POWER = 1024; bool isPowerOf2Plus1 = false; for (int i = 0; i < MAX_POWER && !isPowerOf2Plus1; i++) { if (pow(2.0, i) + 1 == number) isPowerOf2Plus1 = true; } return isPowerOf2Plus1; } void DimensionPowerOf2(int origX, int origY, int &newX, int &newY) { register int temp = 1; while (origX > temp) { temp = temp * 2; } // origX <= temp newX = temp; temp = 1; while (origY > temp) { temp = temp * 2; } // origY <= temp newY = temp; } void LoadGLExtensions() { void* h; #ifdef _WIN32 glMultiTexCoord2fARB_ptr = (PFNGLMULTITEXCOORD2FARBPROC) wglGetProcAddress("glMultiTexCoord2fARB"); glActiveTextureARB_ptr = (PFNGLACTIVETEXTUREARBPROC) wglGetProcAddress("glActiveTextureARB"); glLockArraysEXT_ptr = (PFNGLLOCKARRAYSEXTPROC) wglGetProcAddress("glLockArraysEXT"); glUnlockArraysEXT_ptr = (PFNGLUNLOCKARRAYSEXTPROC) wglGetProcAddress("glUnlockArraysEXT"); glClientActiveTextureARB_ptr = (PFNGLCLIENTACTIVETEXTUREARBPROC) wglGetProcAddress("glClientActiveTextureARB"); #else if ((h = dlopen(NULL, RTLD_LAZY)) == NULL) { string msg("Unable to find symbol list for the main program."); throw new DemeterException(msg); } glXGetProcAddressARB_ptr = (PFNGLXGETPROCADDRESSARBPROC) dlsym(h, "glXGetProcAddressARB"); if (glXGetProcAddressARB_ptr == NULL) { string msg("Unable to find symbol \"glXGetProcAddressARB\"."); throw new DemeterException(msg); } glMultiTexCoord2fARB_ptr = (PFNGLMULTITEXCOORD2FARBPROC) glXGetProcAddressARB_ptr((GLubyte*)"glMultiTexCoord2fARB"); glActiveTextureARB_ptr = (PFNGLACTIVETEXTUREARBPROC) glXGetProcAddressARB_ptr((GLubyte*)"glActiveTextureARB"); glLockArraysEXT_ptr = (PFNGLLOCKARRAYSEXTPROC) glXGetProcAddressARB_ptr((GLubyte*)"glLockArraysEXT"); glUnlockArraysEXT_ptr = (PFNGLUNLOCKARRAYSEXTPROC) glXGetProcAddressARB_ptr((GLubyte*)"glUnlockArraysEXT"); glClientActiveTextureARB_ptr = (PFNGLCLIENTACTIVETEXTUREARBPROC) glXGetProcAddressARB_ptr((GLubyte*)"glClientActiveTextureARB"); dlclose(h); #endif } Terrain::Terrain() { Init(50000, 0, 0); } Terrain::Terrain(int maxNumTriangles, float offsetX, float offsetY) { Init(maxNumTriangles, offsetX, offsetY); } Terrain::Terrain(int widthVertices, int heightVertices, float vertexSpacing, int maxNumTriangles) { Init(maxNumTriangles, 0.0, 0.0); BuildVertices(widthVertices, heightVertices, vertexSpacing); } Terrain::Terrain(const float *pElevations, int elevWidth, int elevHeight, const Uint8 * pTextureImage, int textureWidth, int textureHeight, const Uint8 * pDetailTextureImage, int detailWidth, int detailHeight, float vertexSpacing, float elevationScale, int maxNumTriangles, float offsetX, float offsetY) { Init(maxNumTriangles, offsetX, offsetY); SetAllElevations(pElevations, elevWidth, elevHeight, vertexSpacing, elevationScale); Init(pTextureImage, textureWidth, textureHeight, pDetailTextureImage, detailWidth, detailHeight, offsetX, offsetY); } Terrain::~Terrain() { for (unsigned int i = 0; i < m_TextureCells.size(); i++) { delete m_TextureCells[i]; m_TextureCells[i] = NULL; } m_TextureCells.clear(); if (m_pCommonTexture) delete m_pCommonTexture; if (m_pTriangleStrips) { delete[]m_pTriangleStrips; m_pTriangleStrips = NULL; } if (m_pTriangleFans) { delete[]m_pTriangleFans; m_pTriangleFans = NULL; } if (m_pVertices) delete[]m_pVertices; if (m_pVertexStatus) delete m_pVertexStatus; if (m_pRootBlock) delete m_pRootBlock; if (m_pTextureMain) delete[]m_pTextureMain; if (m_pTextureDetail) delete[]m_pTextureDetail; if (m_pNormals) delete[]m_pNormals; if (m_pTextureSet) delete m_pTextureSet; if (Settings::GetInstance()->IsDiagnostic()) { if (AllocatedTextures.size() != 0) cout << "TERRAIN: Leaking " << AllocatedTextures.size() << " textures!" << endl; else cout << "TERRAIN: No texture leaks" << endl; } } void Terrain::Init(int maxNumTriangles, float offsetX, float offsetY) { m_NumberOfTextureTilesWidth = m_NumberOfTextureTilesHeight = m_NumberOfTextureTiles = 0; m_refCount = 0; m_pCommonTexture = NULL; m_pTriangleStrips = NULL; m_pTriangleFans = NULL; m_pVertices = NULL; m_pVertexStatus = NULL; m_pRootBlock = NULL; m_pTextureMain = NULL; m_pTextureDetail = NULL; m_pNormals = NULL; m_pTextureFactory = NULL; m_pTextureSet = NULL; m_MaxNumberOfPrimitives = maxNumTriangles / 4; m_MaximumVisibleBlockSize = 8; m_OffsetX = offsetX; m_OffsetY = offsetY; Init(NULL, 0, 0, NULL, 0, 0, 0, 0); } void Terrain::Init(const Uint8 * pTextureImage, int textureWidth, int textureHeight, const Uint8 * pDetailTextureImage, int detailWidth, int detailHeight, float offsetX, float offsetY) { m_pTextureSet = new TextureSet; m_DetailThreshold = 10.0f; if (pTextureImage != NULL) SetTexture(pTextureImage, textureWidth, textureHeight); if (pDetailTextureImage != NULL) SetCommonTexture(pDetailTextureImage, detailWidth, detailHeight); if (glActiveTextureARB_ptr == NULL && !Settings::GetInstance()->IsHeadless()) LoadGLExtensions(); } void Terrain::SetAllElevations(const float *pElevations, int elevWidth, int elevHeight, float vertexSpacing, float elevationScale) { // It should be safe to delete[] on NULL pointers, but MSVC used to choke, so we still check... if (m_pVertices) delete[]m_pVertices; if (m_pVertexStatus) delete m_pVertexStatus; if (m_pRootBlock) delete m_pRootBlock; if (m_pNormals) delete[]m_pNormals; m_VertexSpacing = vertexSpacing; bool isQuadSized = IsPowerOf2Plus1(elevWidth) && IsPowerOf2Plus1(elevHeight); if (!isQuadSized && (!IsPowerOf2(elevWidth) || !IsPowerOf2(elevHeight))) { string msg("The elevation data is NOT a power of 2 in both width and height. Elevation data must be a power of 2 in both width and height."); throw new DemeterException(msg); m_pTriangleStrips = NULL; m_pTriangleFans = NULL; m_pVertices = NULL; } m_WidthVertices = elevWidth; m_HeightVertices = elevHeight; if (!isQuadSized) { m_WidthVertices++; // Add 1 dummy pixel line to edges for block strides m_HeightVertices++; } m_NumberOfVertices = m_WidthVertices * m_HeightVertices; m_pVertices = new Vector[m_WidthVertices * m_HeightVertices]; int i, j; float x, y; y = 0.0f; m_MaxElevation = 0.0f; int end = elevWidth * elevHeight; for (i = 0, j = 0; i < end; i += elevWidth, y += m_VertexSpacing) { const float *pImageRow = pElevations + i; x = 0.0f; for (const float *pImagePixel = pImageRow; pImagePixel < pImageRow + elevWidth; pImagePixel++, j++, x += m_VertexSpacing) { m_pVertices[j].x = x + m_OffsetX; m_pVertices[j].y = y + m_OffsetY; m_pVertices[j].z = *pImagePixel * elevationScale; if (m_MaxElevation < m_pVertices[j].z) m_MaxElevation = m_pVertices[j].z; } if (!isQuadSized) { // Account for dummy column on right edge m_pVertices[j].x = (m_WidthVertices - 1) * m_VertexSpacing + m_OffsetX; m_pVertices[j].y = y + m_OffsetY; m_pVertices[j].z = m_pVertices[j - 1].z; if (m_MaxElevation < m_pVertices[j].z) m_MaxElevation = m_pVertices[j].z; j++; } x += m_VertexSpacing; } if (!isQuadSized) { x = 0.0f; for (i = m_NumberOfVertices - m_WidthVertices; i < m_NumberOfVertices; i++, x += m_VertexSpacing) { m_pVertices[i].x = x + m_OffsetX; m_pVertices[i].y = (m_HeightVertices - 1) * m_VertexSpacing + m_OffsetY; m_pVertices[i].z = m_pVertices[i - m_WidthVertices].z; } } BuildBlocks(); // Generate vertex normal arrays if (Settings::GetInstance()->UseNormals()) { m_pNormals = new Vector[m_NumberOfVertices]; for (i = 0; i < m_NumberOfVertices; i++) RecalcNormal(i); } } void Terrain::RecalcNormal(int vertexIndex) { const float delta = (M_PI * 2.0f) / 8.0f; int indexX, indexY; indexX = vertexIndex % m_WidthVertices; indexY = vertexIndex / m_WidthVertices; float vertexX, vertexY; vertexX = indexX * m_VertexSpacing; vertexY = indexY * m_VertexSpacing; Vector avgNormal; avgNormal.x = avgNormal.y = avgNormal.z = 0.0f; for (float theta = -0.5f * delta; theta < (M_PI * 2.0f); theta++) { Vector v; v.x = 1.0f; v.y = 0.0f; v.z = 0.0f; v.RotateZ(theta); v.x += vertexX; v.y += vertexY; float nx, ny, nz; GetNormal(v.x, v.y, nx, ny, nz); avgNormal.x += nx; avgNormal.y += ny; avgNormal.z += nz; } m_pNormals[vertexIndex].x = avgNormal.x / 8.0f; m_pNormals[vertexIndex].y = avgNormal.y / 8.0f; m_pNormals[vertexIndex].z = avgNormal.z / 8.0f; } void Terrain::RecalcNormal(float x, float y) { int index = GetVertex(x, y); RecalcNormal(index); } void Terrain::BuildVertices(int widthVertices, int heightVertices, float vertexSpacing) { float *pElevations = new float[widthVertices * heightVertices]; for (int i = 0; i < widthVertices * heightVertices; i++) pElevations[i] = 0.0f; SetAllElevations(pElevations, widthVertices, heightVertices, vertexSpacing); delete[]pElevations; } void Terrain::SetOffset(float x, float y) { float deltaX = x - m_OffsetX; float deltaY = y - m_OffsetY; m_OffsetX = x; m_OffsetY = y; for (int i = 0; i < m_NumberOfVertices; i++) { m_pVertices[i].x += deltaX; m_pVertices[i].y += deltaY; } } float Terrain::GetOffsetX() const { return m_OffsetX; } float Terrain::GetOffsetY() const { return m_OffsetY; } bool Terrain::IsMultiTextureSupported() const { return true; // Legacy } int Terrain::GetNumberOfVertices() const { return m_NumberOfVertices; } void Terrain::UpdateNeighbor(Terrain * pTerrain, Demeter::DIRECTION direction) { int thisVertex, otherVertex; if (direction == Demeter::DIR_SOUTH) { for (thisVertex = 0, otherVertex = m_NumberOfVertices - m_WidthVertices; thisVertex < m_WidthVertices; thisVertex++, otherVertex++) { if (GetVertexStatus(thisVertex)) pTerrain->SetVertexStatus(otherVertex, true); } } else if (direction == Demeter::DIR_NORTH) { for (thisVertex = m_NumberOfVertices - m_WidthVertices, otherVertex = 0; thisVertex < m_NumberOfVertices; thisVertex++, otherVertex++) { if (GetVertexStatus(thisVertex)) pTerrain->SetVertexStatus(otherVertex, true); } } else if (direction == Demeter::DIR_WEST) { for (thisVertex = 0, otherVertex = m_WidthVertices - 1; thisVertex < m_NumberOfVertices; thisVertex += m_WidthVertices, otherVertex += m_WidthVertices) { if (GetVertexStatus(thisVertex)) pTerrain->SetVertexStatus(otherVertex, true); } } else if (direction == Demeter::DIR_EAST) { for (thisVertex = m_WidthVertices - 1, otherVertex = 0; thisVertex < m_NumberOfVertices; thisVertex += m_WidthVertices, otherVertex += m_WidthVertices) { if (GetVertexStatus(thisVertex)) pTerrain->SetVertexStatus(otherVertex, true); } } else if (direction == Demeter::DIR_NORTHWEST) { if (GetVertexStatus(m_NumberOfVertices - m_WidthVertices)) pTerrain->SetVertexStatus(m_WidthVertices - 1, true); } else if (direction == Demeter::DIR_NORTHEAST) { if (GetVertexStatus(m_NumberOfVertices - 1)) pTerrain->SetVertexStatus(0, true); } else if (direction == Demeter::DIR_SOUTHEAST) { if (GetVertexStatus(m_WidthVertices - 1)) pTerrain->SetVertexStatus(m_NumberOfVertices - m_WidthVertices, true); } else if (direction == Demeter::DIR_SOUTHWEST) { if (GetVertexStatus(0)) pTerrain->SetVertexStatus(m_NumberOfVertices - 1, true); } } float Terrain::GetElevation(int index) const { return m_pVertices[index].z; } float Terrain::GetElevation(float x, float y) const { Plane plane; int vertexID; float elevation; x -= m_OffsetX; y -= m_OffsetY; if (x < 0.0f || y < 0.0f || GetWidth() < x || GetHeight() < y) { elevation = 0.0f; } else { vertexID = ((int)(y / m_VertexSpacing)) * m_WidthVertices + ((int)(x / m_VertexSpacing)); if ((fmod(y, m_VertexSpacing) + fmod(x, m_VertexSpacing)) <= m_VertexSpacing) plane.DefineFromPoints(m_pVertices[vertexID], m_pVertices[vertexID + m_WidthVertices], m_pVertices[vertexID + 1]); else plane.DefineFromPoints(m_pVertices[vertexID + 1], m_pVertices[vertexID + 1 + m_WidthVertices], m_pVertices[vertexID + m_WidthVertices]); elevation = -1.0f * ((plane.a * (x + m_OffsetX) + plane.b * (y + m_OffsetY) + plane.d) / plane.c); } return elevation; } void Terrain::GetNormal(float x, float y, float &normalX, float &normalY, float &normalZ) const { Plane plane; int vertexID; x -= m_OffsetX; y -= m_OffsetY; if (x < 0.0f || y < 0.0f || GetWidth() < x || GetHeight() < y) { normalX = normalY = 0.0f; normalZ = 1.0f; } else { vertexID = ((int)(y / m_VertexSpacing)) * m_WidthVertices + ((int)(x / m_VertexSpacing)); if ((fmod(y, m_VertexSpacing) + fmod(x, m_VertexSpacing)) <= m_VertexSpacing) plane.DefineFromPoints(m_pVertices[vertexID], m_pVertices[vertexID + 1], m_pVertices[vertexID + m_WidthVertices]); else plane.DefineFromPoints(m_pVertices[vertexID + 1], m_pVertices[vertexID + 1 + m_WidthVertices], m_pVertices[vertexID + m_WidthVertices]); normalX = plane.a; normalY = plane.b; normalZ = plane.c; } } void Terrain::SetDetailThreshold(float threshold) { m_DetailThreshold = threshold; } float Terrain::GetDetailThreshold() const { return m_DetailThreshold; } int Terrain::GetWidthVertices() const { return m_WidthVertices; } int Terrain::GetHeightVertices() const { return m_HeightVertices; } float Terrain::GetWidth() const { return (float)(m_WidthVertices - 1) * m_VertexSpacing; } float Terrain::GetHeight() const { return (float)(m_HeightVertices - 1) * m_VertexSpacing; } int Terrain::GetBaseTextureWidth() { return m_BaseTextureWidth; } int Terrain::GetBaseTextureHeight() { return m_BaseTextureHeight; } float Terrain::GetMaxElevation() const { return m_MaxElevation; } void Terrain::SetTextureFactory(TextureFactory * pFactory) { m_pTextureFactory = pFactory; } float Terrain::GetVertexElevation(int index) const { if (index < 0 || m_NumberOfVertices <= index) return 0.0f; else return m_pVertices[index].z; } int Terrain::GetVertex(float x, float y) const { return ((int)(y / m_VertexSpacing)) * m_WidthVertices + ((int)(x / m_VertexSpacing)); /* if ((fmod(y,m_VertexSpacing) + fmod(x,m_VertexSpacing)) <= m_VertexSpacing) plane.DefineFromPoints(m_pVertices[vertexID],m_pVertices[vertexID + m_WidthVertices],m_pVertices[vertexID + 1]); else plane.DefineFromPoints(m_pVertices[vertexID + 1],m_pVertices[vertexID + 1 + m_WidthVertices],m_pVertices[vertexID + m_WidthVertices]); float vx,vy; vx = fmod(x,m_VertexSpacing); vy = y / m_VertexSpacing; int index; if ((fmod(y,m_VertexSpacing) + fmod(x,m_VertexSpacing)) <= m_VertexSpacing) index = (int)vy * m_WidthVertices + (int)vx; else index = ((int)vy + 1) * m_WidthVertices + ((int)vx + 1); return index;*/ } void Terrain::SetVertexElevation(int index, float newElevation, bool recalculate_geometry) { if (0 <= index && index < m_NumberOfVertices) m_pVertices[index].z = newElevation; if (recalculate_geometry) m_pRootBlock->VertexChanged(this, index); } void Terrain::RecalcGeometry(int index1, int index2) { m_pRootBlock->VertexChanged(this, index1, index2); } void Terrain::SetVertexElevation(float x, float y, float newElevation) { SetVertexElevation(GetVertex(x, y), newElevation); } float Terrain::GetVertexSpacing() const { return m_VertexSpacing; } void Terrain::BuildBlocks() { #if _USE_RAYTRACING_SUPPORT_ == 0 if (Settings::GetInstance()->IsHeadless()) return; #endif numLevels = 0.0f; numBlocks = 0.0f; for (int i = m_WidthVertices - 1; 2 <= i; i /= 2) numLevels += 1.0f; for (double j = 0.0f; j < numLevels; j += 1.0f) numBlocks += (float)pow(4.0, j); if (Settings::GetInstance()->IsVerbose()) { cout << "TERRAIN: Building " << numBlocks << " blocks; please wait..." << endl; #if _USE_RAYTRACING_SUPPORT_ > 0 cout << "TERRAIN: Memory required at runtime for blocks = " << numBlocks * (sizeof(TerrainBlock) + 8 * sizeof(Triangle)) << " bytes" << endl; #else cout << "TERRAIN: Memory required at runtime for blocks = " << numBlocks * sizeof(TerrainBlock) << " bytes" << endl; #endif cout << ".............................." << endl; hashDelta = (float)numBlocks / 30.0f; cout << "#" << flush; } m_pVertexStatus = new BitArray(m_WidthVertices * m_HeightVertices); // We assume that the terrain's width is always a power of 2 + 1! m_pRootBlock = new TerrainBlock(0, m_WidthVertices - 1, this, NULL); if (Settings::GetInstance()->IsVerbose()) cout << endl; } void Terrain::SetVertexStatus(int vertexIndex, bool status) { if (status) m_pVertexStatus->SetBit(vertexIndex); else m_pVertexStatus->ClearBit(vertexIndex); } bool Terrain::GetVertexStatus(int vertexIndex) { return m_pVertexStatus->IsBitSet(vertexIndex); } int Terrain::Tessellate() { if (m_pTriangleStrips == NULL) { unsigned long int maxNumStrips = (GetWidthVertices() - 1) * (GetHeightVertices() - 1); try { if (m_MaxNumberOfPrimitives < maxNumStrips) maxNumStrips = m_MaxNumberOfPrimitives; if (Settings::GetInstance()->IsVerbose()) cout << "TERRAIN: Allocating " << maxNumStrips << " triangle strips and fans (" << maxNumStrips * sizeof(TriangleStrip) + maxNumStrips * sizeof(TriangleFan) << " bytes)\n" << endl; m_pTriangleStrips = new TriangleStrip[maxNumStrips]; m_pTriangleFans = new TriangleFan[maxNumStrips]; if (m_pTriangleStrips == NULL || m_pTriangleFans == NULL) { cout << "TERRAIN: " << "Not enough memory to build terrain triangles" << endl; exit(1); } } catch(...) { cout << "TERRAIN: " << "Not enough memory to build terrain triangles" << endl; exit(1); } } double matModelview[16]; double matProjection[16]; GLint viewport[4]; glGetDoublev(GL_MODELVIEW_MATRIX, matModelview); glGetDoublev(GL_PROJECTION_MATRIX, matProjection); glGetIntegerv(GL_VIEWPORT, viewport); ExtractFrustum(); m_pVertexStatus->Clear(); m_CountStrips = m_CountFans = 0; m_pRootBlock->Tessellate((double *)matModelview, (double *)matProjection, (int *)viewport, (TriangleStrip *) m_pTriangleStrips, &m_CountStrips, this); return m_CountStrips * 2 + m_CountFans * 6; } Texture *Terrain::GetCommonTexture() const { return m_pCommonTexture; } void Terrain::GetCommonTextureFilename(char *szFilename, int bufferSize) { char szTmp[256]; sprintf(szTmp, "common.raw"); SafelyFillString(szTmp, szFilename, bufferSize); } void Terrain::SetCommonTexture(Texture * pTexture) { m_pCommonTexture = pTexture; } bool Terrain::SetCommonTexture(const Uint8 * pBuffer, int width, int height) { bool bSuccess = false; // Test to see if the image is a power of 2 in both width and height. if (!IsPowerOf2(width) || !IsPowerOf2(height)) { string msg("The detail texture image file is NOT a power of 2 in both width and height.\nTexture files must be a power of 2 in both width and height."); throw new DemeterException(msg); } m_pCommonTexture = new Texture(pBuffer, width, height, width, 0, false, Settings::GetInstance()->IsTextureCompression()); bSuccess = true; if (Settings::GetInstance()->IsVerbose()) cout << "TERRAIN: Common texture set successfully" << endl; return bSuccess; } bool Terrain::SetTexture(const Uint8 * pBuffer, int width, int height) { if (width <= 256 || height <= 256) throw new DemeterException("The overall terrain texture must be > 256 in both width and height"); for (unsigned int i = 0; i < m_TextureCells.size(); i++) { delete m_TextureCells[i]; m_TextureCells[i] = NULL; } m_TextureCells.clear(); m_BaseTextureWidth = width; m_BaseTextureHeight = height; if (Settings::GetInstance()->IsHeadless()) return true; bool bSuccess = false; if (!Settings::GetInstance()->UseBorders()) { // Test to see if the image is a power of 2 in both width and height. if (!IsPowerOf2(width) || !IsPowerOf2(height)) { string msg("The texture is NOT a power of 2 in both width and height.\nTextures must be a power of 2 in both width and height."); throw new DemeterException(msg); } } ChopTexture(pBuffer, width, height, 256); bSuccess = true; if (Settings::GetInstance()->IsVerbose()) cout << "TERRAIN: Texture set successfully" << endl; FlipTexturesForMapping(); if (Settings::GetInstance()->GetPreloadTextures()) PreloadTextures(); return bSuccess; } void Terrain::GenerateTextureCoordinates() { if (m_pTextureMain) delete[]m_pTextureMain; if (m_pTextureDetail) delete[]m_pTextureDetail; m_pTextureMain = new float[m_NumberOfVertices * 2]; m_pTextureDetail = new float[m_NumberOfVertices * 2]; float u; float v = 0.0f; float uDetail = 0.0f; float vDetail = 0.0f; float verticesPerTileWidth = (float)(m_WidthVertices + m_NumberOfTextureTilesWidth - 1) / (float)m_NumberOfTextureTilesWidth; float verticesPerTileHeight = (float)(m_HeightVertices + m_NumberOfTextureTilesHeight - 1) / (float)m_NumberOfTextureTilesHeight; float deltaU = 1.0f / (verticesPerTileWidth - 1.0f); float deltaV = 1.0f / (verticesPerTileHeight - 1.0f); float deltaUDetail = deltaU * Settings::GetInstance()->GetDetailTextureRepeats(); float deltaVDetail = deltaV * Settings::GetInstance()->GetDetailTextureRepeats(); const float epsilon = 0.0000001f; int k = 0; int i; for (i = 0; i < m_NumberOfVertices; i += m_WidthVertices) { u = 0.0f; uDetail = 0.0f; for (int j = i; j < i + m_WidthVertices; j++) { m_pTextureMain[k] = u; m_pTextureMain[k + 1] = v; m_pTextureDetail[k] = uDetail; m_pTextureDetail[k + 1] = vDetail; k += 2; u += deltaU; uDetail += deltaUDetail; if (fabs(u - 1.0f) < epsilon) { u = 1.0f; deltaU *= -1.0f; } else if (fabs(u) < epsilon) { u = 0.0f; deltaU *= -1.0f; } } v += deltaV; vDetail += deltaVDetail; if (fabs(v - 1.0f) < epsilon) { v = 1.0f; deltaV *= -1.0f; } else if (fabs(v) < epsilon) { v = 0.0f; deltaV *= -1.0f; } } } void Terrain::ChopTexture(const Uint8 * pImage, int width, int height, int tileSize) { // It is assumed that the image is in a 3-byte per pixel, RGB format, with no padding on the pixel rows if (Settings::GetInstance()->UseBorders()) { m_NumberOfTextureTilesWidth = (width - 1) / (tileSize + 1); m_NumberOfTextureTilesHeight = (height - 1) / (tileSize + 1); } else { m_NumberOfTextureTilesWidth = width / tileSize; m_NumberOfTextureTilesHeight = height / tileSize; } m_NumberOfTextureTiles = m_NumberOfTextureTilesWidth * m_NumberOfTextureTilesHeight; float verticesPerTileWidth = (float)(m_WidthVertices + m_NumberOfTextureTilesWidth - 1) / (float)m_NumberOfTextureTilesWidth; float verticesPerTileHeight = (float)(m_HeightVertices + m_NumberOfTextureTilesHeight - 1) / (float)m_NumberOfTextureTilesHeight; m_TextureTileWidth = (verticesPerTileWidth - 1.0f) * m_VertexSpacing; m_TextureTileHeight = (verticesPerTileHeight - 1.0f) * m_VertexSpacing; m_TileSize = tileSize; GenerateTextureCoordinates(); int cellIndex = 0; if (Settings::GetInstance()->UseBorders()) { int i; // Create texture tiles by roaming across the bordered image. for (i = 0; i < height - 1; i += m_TileSize + 1) { for (int j = 0; j < width - 1; j += m_TileSize + 1) { const Uint8 *pTile = pImage + i * width * 3 + j * 3; Texture *pTexture = new Texture(pTile, m_TileSize + 2, m_TileSize + 2, width, 1, true, Settings::GetInstance()->IsTextureCompression()); TextureCell *pCell = new TextureCell(cellIndex++); pCell->SetTexture(pTexture); m_TextureCells.push_back(pCell); } } } else { // Create texture tiles by roaming across the image. for (int i = 0; i < height; i += tileSize) { for (int j = 0; j < width; j += tileSize) { const Uint8 *pTile = pImage + i * width * 3 + j * 3; Texture *pTexture = new Texture(pTile, tileSize, tileSize, width, 0, true, Settings::GetInstance()->IsTextureCompression()); TextureCell *pCell = new TextureCell(cellIndex++); pCell->SetTexture(pTexture); m_TextureCells.push_back(pCell); } } } } void Terrain::GenerateTexture(int widthTexels, int heightTexels, const char *szGeneratorName) { string name(szGeneratorName); TextureGenerator *pGenerator = m_TextureGenerators[name]; if (pGenerator == NULL) throw new DemeterException("The specified texture generator does not exist"); GenerateTexture(widthTexels, heightTexels, pGenerator); } void Terrain::GenerateTexture(int widthTexels, int heightTexels, TextureGenerator * pGenerator) { pGenerator->Init(this, widthTexels, heightTexels); pGenerator->Generate(); } void Terrain::RegisterTextureGenerator(string name, TextureGenerator * pGenerator) { Terrain::m_TextureGenerators[name] = pGenerator; } void Terrain::RegisterDefaultGenerators() { // Terrain::RegisterTextureGenerator("White",new WhiteTextureGenerator); // Terrain::RegisterTextureGenerator("GrassyFlats",new GrassyFlatsTextureGenerator); } void Terrain::SetMaximumVisibleBlockSize(int stride) { m_MaximumVisibleBlockSize = stride; } int Terrain::ModelViewMatrixChanged() { int count = Tessellate(); m_pRootBlock->RepairCracks(this, m_pTriangleFans, &m_CountFans); return count; } void Terrain::Paint(int detailTextureIndex, int brushWidth, float brushIntensity, float maxIntensity, bool erase, float x, float y) { brush.SetWidth(brushWidth); brush.SetIntensity(brushIntensity); brush.SetMaxIntensity(maxIntensity); brush.SetErase(erase); brush.Paint(this, detailTextureIndex, x, y); } Uint8 *Terrain::GetMaskBits(int textureCellX, int textureCellY, int detailIndex, int &maskWidth, int &maskHeight) { TextureCell *pCell = GetTextureCell(textureCellX, textureCellY); DetailTexture *pDet = pCell->GetDetail(GetTextureSet()->GetTexture(detailIndex)); if (pDet == NULL) { pDet = new DetailTexture(GetTextureSet()->GetTexture(detailIndex)); pCell->AddDetail(pDet); } Texture *pMask = pDet->GetMask(); // Mask sizes are actually fixed right now, but this will allow flexibility in the future. maskWidth = pCell->GetDetailMaskImageWidth(detailIndex); maskHeight = pCell->GetDetailMaskImageHeight(detailIndex); return pMask->GetBuffer(); } void Terrain::ReloadMask(int textureCellX, int textureCellY, int detailIndex) { if (0 <= textureCellX && (unsigned int)textureCellX < m_NumberOfTextureTilesWidth && 0 <= (unsigned int)textureCellY && (unsigned int)textureCellY < m_NumberOfTextureTilesHeight) { if (Settings::GetInstance()->IsMaskReloadQueueActive()) { char szTmp[128]; sprintf(szTmp, "%d|%d|%d", textureCellX, textureCellY, detailIndex); string key(szTmp); map < string, ReloadMaskRequest * >::iterator foundIter = m_ReloadMaskRequests.find(key); if (foundIter == m_ReloadMaskRequests.end()) { m_ReloadMaskRequests[key] = new ReloadMaskRequest(textureCellX, textureCellY, detailIndex); } } else { ReloadMaskImmediate(textureCellX, textureCellY, detailIndex); } } } void Terrain::ReloadMaskImmediate(int textureCellX, int textureCellY, int detailIndex) { TextureCell *pCell = GetTextureCell(textureCellX, textureCellY); DetailTexture *pDet = pCell->GetDetail(GetTextureSet()->GetTexture(detailIndex)); if (pDet != NULL) { Texture *pMask = pDet->GetMask(); pMask->UnloadTexture(); pMask->UploadTexture(); } } void Terrain::FinishPaints() { map < string, ReloadMaskRequest * >::iterator iter = m_ReloadMaskRequests.begin(); while (iter != m_ReloadMaskRequests.end()) { ReloadMaskRequest *pRequest = iter->second; ReloadMaskImmediate(pRequest->m_TextureCellX, pRequest->m_TextureCellY, pRequest->m_DetailIndex); delete pRequest; iter++; } m_ReloadMaskRequests.clear(); } void Terrain::Render() { unsigned int i, j; // Force different rendering contexts to share all terrain textures. #ifdef _WIN32 HGLRC currentContext = wglGetCurrentContext(); bool found = false; for (i = 0; i < m_SharedContexts.size() && !found; i++) found = (m_SharedContexts[i] == currentContext); if (!found) { m_SharedContexts.push_back(currentContext); if (1 < m_SharedContexts.size()) wglShareLists(m_SharedContexts[0], m_SharedContexts[m_SharedContexts.size() - 1]); } #endif if (!glMultiTexCoord2fARB_ptr) LoadGLExtensions(); glDepthMask(GL_TRUE); glDepthFunc(GL_LESS); glDisable(GL_BLEND); glFrontFace(GL_CW); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); for (i = 0; i < m_CountStrips; i++) m_pTriangleStrips[i].Setup(this); for (i = 0; i < m_CountFans; i++) m_pTriangleFans[i].Setup(this); if (m_pTextureMain != NULL) { glClientActiveTextureARB_ptr(GL_TEXTURE0_ARB); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_FLOAT, 0, m_pTextureMain); glClientActiveTextureARB_ptr(GL_TEXTURE1_ARB); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_FLOAT, 0, m_pTextureDetail); } glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, 0, m_pVertices); if (Settings::GetInstance()->UseNormals()) { glEnableClientState(GL_NORMAL_ARRAY); glNormalPointer(GL_FLOAT, 0, m_pNormals); //float matDiff[4] = {1.0f,1.0f,1.0f,1.0f}; //glMaterialfv(GL_FRONT,GL_DIFFUSE,matDiff); } else glNormal3f(0.0f, 0.0f, 1.0f); if (Settings::GetInstance()->UseVertexLocking() && glLockArraysEXT_ptr != NULL) glLockArraysEXT_ptr(0, m_NumberOfVertices); if (0 < m_NumberOfTextureTiles) { // Prevent state thrashing by rendering one texture at a time... for (i = 0; i < m_NumberOfTextureTiles; i++) { bool anyRendered = false; bool firstTime = true; TextureCell *pCell; if (m_pTextureFactory == NULL) pCell = m_TextureCells[i]; // First pass - overall texture for (j = 0; j < m_CountStrips; j++) { if (m_pTriangleStrips[j].textureId == i) { if (firstTime) { if (Settings::GetInstance()->UseNormals()) glColor4f(1.0f, 1.0f, 1.0f, 1.0f); else glColor4f(0.0f, 0.0f, 0.0f, 1.0f); glActiveTextureARB_ptr(GL_TEXTURE1_ARB); if (m_pCommonTexture != NULL) { glEnable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD); if (Settings::GetInstance()->IsBaseTextureEnabled()) { GLuint cTexId = m_pCommonTexture->UploadTexture(); glBindTexture(GL_TEXTURE_2D, cTexId); } } else glDisable(GL_TEXTURE_2D); glActiveTextureARB_ptr(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); if (Settings::GetInstance()->UseNormals()) glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); else glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); if (Settings::GetInstance()->IsBaseTextureEnabled()) { GLuint texId; if (m_pTextureFactory != NULL) { int tileX = i % (int)m_NumberOfTextureTilesWidth; int tileY = i / (int)m_NumberOfTextureTilesHeight; float originX = tileX * m_TextureTileWidth; float originY = tileY * m_TextureTileHeight; Texture *pTexture = m_pTextureFactory->GetTexture(i, originX, originY, m_TextureTileWidth, m_TextureTileHeight); if (!pTexture->IsBound()) { if (tileX % 2 != 0) pTexture->FlipHorizontal(); if (tileY % 2 != 0) pTexture->FlipVertical(); } texId = pTexture->UploadTexture(); } else { texId = pCell->BindTexture(); } glBindTexture(GL_TEXTURE_2D, texId); } firstTime = false; anyRendered = true; } m_pTriangleStrips[j].Render(this); } } if (m_pTextureFactory == NULL && !firstTime && 0 < pCell->GetNumberOfDetails()) { // Second and subsequent passes - multitexture of masks and details glDepthMask(GL_FALSE); glDepthFunc(GL_EQUAL); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); for (int k = 0; k < pCell->GetNumberOfDetails(); k++) { glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glActiveTextureARB_ptr(GL_TEXTURE0_ARB); glBindTexture(GL_TEXTURE_2D, pCell->BindMask(k)); glActiveTextureARB_ptr(GL_TEXTURE1_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, pCell->BindDetail(k)); for (j = 0; j < m_CountStrips; j++) { if (m_pTriangleStrips[j].textureId == i) { m_pTriangleStrips[j].Render(this); } } } glDepthMask(GL_TRUE); glDepthFunc(GL_LESS); glDisable(GL_BLEND); } firstTime = true; for (j = 0; j < m_CountFans; j++) { if (m_pTriangleFans[j].textureId == i) { if (firstTime) { if (Settings::GetInstance()->UseNormals()) glColor4f(1.0f, 1.0f, 1.0f, 1.0f); else glColor4f(0.0f, 0.0f, 0.0f, 1.0f); glActiveTextureARB_ptr(GL_TEXTURE1_ARB); if (m_pCommonTexture != NULL) { glEnable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD); if (Settings::GetInstance()->IsBaseTextureEnabled()) { GLuint cTexId = m_pCommonTexture->UploadTexture(); glBindTexture(GL_TEXTURE_2D, cTexId); } } else glDisable(GL_TEXTURE_2D); glActiveTextureARB_ptr(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); if (Settings::GetInstance()->UseNormals()) glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); else glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); if (Settings::GetInstance()->IsBaseTextureEnabled()) { GLuint texId; if (m_pTextureFactory != NULL) { int tileX = i % (int)m_NumberOfTextureTilesWidth; int tileY = i / (int)m_NumberOfTextureTilesHeight; float originX = tileX * m_TextureTileWidth; float originY = tileY * m_TextureTileHeight; Texture *pTexture = m_pTextureFactory->GetTexture(i, originX, originY, m_TextureTileWidth, m_TextureTileHeight); if (!pTexture->IsBound()) { if (tileX % 2 != 0) pTexture->FlipHorizontal(); if (tileY % 2 != 0) pTexture->FlipVertical(); } texId = pTexture->UploadTexture(); } else texId = pCell->BindTexture(); glBindTexture(GL_TEXTURE_2D, texId); } firstTime = false; anyRendered = true; } m_pTriangleFans[j].Render(this); } } if (m_pTextureFactory == NULL && !firstTime && 0 < pCell->GetNumberOfDetails()) { glDepthMask(GL_FALSE); glDepthFunc(GL_EQUAL); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // Second and subsequent passes - multitexture of masks and details for (int k = 0; k < pCell->GetNumberOfDetails(); k++) { glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glActiveTextureARB_ptr(GL_TEXTURE0_ARB); glBindTexture(GL_TEXTURE_2D, pCell->BindMask(k)); glActiveTextureARB_ptr(GL_TEXTURE1_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, pCell->BindDetail(k)); for (j = 0; j < m_CountFans; j++) { if (m_pTriangleFans[j].textureId == i) { m_pTriangleFans[j].Render(this); } } } glDepthMask(GL_TRUE); glDepthFunc(GL_LESS); glDisable(GL_BLEND); } if (Settings::GetInstance()->UseDynamicTextures() && !anyRendered) { if (m_pTextureFactory == NULL) pCell->UnbindAll(); else m_pTextureFactory->UnloadTexture(i); } } } else { glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glActiveTextureARB_ptr(GL_TEXTURE0_ARB); glDisable(GL_TEXTURE_2D); glActiveTextureARB_ptr(GL_TEXTURE1_ARB); glDisable(GL_TEXTURE_2D); for (j = 0; j < m_CountStrips; j++) m_pTriangleStrips[j].Render(this); for (j = 0; j < m_CountFans; j++) m_pTriangleFans[j].Render(this); } if (Settings::GetInstance()->UseVertexLocking() && glUnlockArraysEXT_ptr != NULL) glUnlockArraysEXT_ptr(); glDisableClientState(GL_VERTEX_ARRAY); // Turn multi-texture back off again so the client application doesn't end up in an unexpected state. glClientActiveTextureARB_ptr(GL_TEXTURE1_ARB); glActiveTextureARB_ptr(GL_TEXTURE1_ARB); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glDisable(GL_TEXTURE_2D); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB_ptr(GL_TEXTURE0_ARB); glActiveTextureARB_ptr(GL_TEXTURE0_ARB); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glDisable(GL_TEXTURE_2D); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glFrontFace(GL_CCW); } float Terrain::GetTextureTileWidth() const { return m_TextureTileWidth; } float Terrain::GetTextureTileHeight() const { return m_TextureTileHeight; } int Terrain::GetNumberOfTextureTilesWidth() const { return m_NumberOfTextureTilesWidth; } int Terrain::GetNumberOfTextureTilesHeight() const { return m_NumberOfTextureTilesHeight; } void Terrain::Write(char *szFilename) { char szFullFilename[MAX_FILENAME_LENGTH]; Settings::GetInstance()->PrependMediaPath(szFilename, szFullFilename); FILE *file = fopen(szFullFilename, "wb"); char szDemeter[] = "Demeter"; fwrite(szDemeter, 1, 7, file); m_pTextureSet->Write(file, this); for (unsigned int i = 0; i < m_TextureCells.size(); i++) m_TextureCells[i]->Write(file, this); fclose(file); } void Terrain::Read(char *szFilename) { char szFullFilename[MAX_FILENAME_LENGTH]; Settings::GetInstance()->PrependMediaPath(szFilename, szFullFilename); FILE *file = fopen(szFullFilename, "rb"); if (file) { char szDemeter[8]; fread(szDemeter, 1, 7, file); szDemeter[7] = '\0'; if (strcmp(szDemeter, "Demeter") != 0) { fclose(file); throw new DemeterException("The specified file is not a Demeter surface file"); } if (m_pTextureSet) delete m_pTextureSet; m_pTextureSet = new TextureSet; m_pTextureSet->Read(file, this); for (unsigned int i = 0; i < m_NumberOfTextureTiles; i++) m_TextureCells[i]->Read(file, this); if (Settings::GetInstance()->GetPreloadTextures()) PreloadTextures(); fclose(file); } else throw new DemeterException("The specified file could not be opened"); } void Terrain::FlipTexturesForMapping() { // Every odd texture has to be flipped because our texture coordinates vary from 0 to 1 back to 0 and so forth across the entire terrain. This eliminates seams between texture tiles. for (unsigned int textureY = 0; textureY < m_NumberOfTextureTilesHeight; textureY++) { for (unsigned int textureX = 0; textureX < m_NumberOfTextureTilesWidth; textureX++) { TextureCell *pTextureCell = m_TextureCells[textureY * m_NumberOfTextureTilesWidth + textureX]; if (textureX % 2 != 0) pTextureCell->FlipHorizontal(); if (textureY % 2 != 0) pTextureCell->FlipVertical(); } } } void Terrain::PreloadTextures() { for (unsigned int i = 0; i < m_TextureCells.size(); i++) { m_TextureCells[i]->BindTexture(); for (unsigned int j = 0; j < (unsigned int)m_TextureCells[i]->GetNumberOfDetails(); j++) { m_TextureCells[i]->BindMask(j); m_TextureCells[i]->BindDetail(j); } } } TextureCell *Terrain::GetTextureCell(int index) { return m_TextureCells[index]; } TextureCell *Terrain::GetTextureCell(int textureCellX, int textureCellY) { return m_TextureCells[textureCellY * m_NumberOfTextureTilesWidth + textureCellX]; } bool Terrain::Pick(int mouseX, int mouseY, float &pickedX, float &pickedY, float &pickedZ) const { // Thanks to Tero Kontkanen for providing this brilliant picking technique. bool bPickedTerrain = false; float depth[1]; GLdouble modelm[16], projm[16], pos[3]; int view[4]; glGetDoublev(GL_MODELVIEW_MATRIX, modelm); glGetDoublev(GL_PROJECTION_MATRIX, projm); glGetIntegerv(GL_VIEWPORT, (GLint *) view); glReadPixels(mouseX, mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, depth); gluUnProject(mouseX, mouseY, depth[0], modelm, projm, (GLint *) view, &pos[0], &pos[1], &pos[2]); pickedX = (float)pos[0]; pickedY = (float)pos[1]; pickedZ = (float)pos[2]; float elev = GetElevation(pickedX, pickedY); bPickedTerrain = Settings::GetInstance()->GetPickThreshold() < 0.0f || (pickedZ - elev < Settings::GetInstance()->GetPickThreshold()); // Look for a "fudge factor" difference between the unprojected point and the actual terrain elevation to see if some application object was picked instead of the terrain if (!bPickedTerrain) cout << "TERRAIN: Picked point is not on terrain *" << (pickedZ - elev) << ")" << endl; return bPickedTerrain; } bool Terrain::Pick(int mouseX, int mouseY, float &pickedX, float &pickedY, float &pickedZ, int &textureCellX, int &textureCellY, float &texU, float &texV) const { bool bPickedTerrain = Pick(mouseX, mouseY, pickedX, pickedY, pickedZ); if (bPickedTerrain) GetTextureCoordinates(pickedX, pickedY, textureCellX, textureCellY, texU, texV); return bPickedTerrain; } void Terrain::GetTextureCoordinates(float x, float y, int &textureCellX, int &textureCellY, float &texU, float &texV) const { textureCellX = (int)(x / m_TextureTileWidth); textureCellY = (int)(y / m_TextureTileHeight); texU = (float)fmod(x, m_TextureTileWidth) / m_TextureTileWidth; texV = (float)fmod(y, m_TextureTileHeight) / m_TextureTileHeight; if (textureCellX % 2 != 0) texU = 1.0f - texU; if (textureCellY % 2 != 0) texV = 1.0f - texV; } void Terrain::ExtractFrustum() { // Thanks to Mark Morley for this OpenGL frustum testing code. // See all of it at http://www.markmorley.com/opengl/frustumculling.html. float proj[16]; float modl[16]; float clip[16]; float t; /* Get the current PROJECTION matrix from OpenGL */ glGetFloatv(GL_PROJECTION_MATRIX, proj); /* Get the current MODELVIEW matrix from OpenGL */ glGetFloatv(GL_MODELVIEW_MATRIX, modl); /* Combine the two matrices (multiply projection by modelview) */ clip[0] = modl[0] * proj[0] + modl[1] * proj[4] + modl[2] * proj[8] + modl[3] * proj[12]; clip[1] = modl[0] * proj[1] + modl[1] * proj[5] + modl[2] * proj[9] + modl[3] * proj[13]; clip[2] = modl[0] * proj[2] + modl[1] * proj[6] + modl[2] * proj[10] + modl[3] * proj[14]; clip[3] = modl[0] * proj[3] + modl[1] * proj[7] + modl[2] * proj[11] + modl[3] * proj[15]; clip[4] = modl[4] * proj[0] + modl[5] * proj[4] + modl[6] * proj[8] + modl[7] * proj[12]; clip[5] = modl[4] * proj[1] + modl[5] * proj[5] + modl[6] * proj[9] + modl[7] * proj[13]; clip[6] = modl[4] * proj[2] + modl[5] * proj[6] + modl[6] * proj[10] + modl[7] * proj[14]; clip[7] = modl[4] * proj[3] + modl[5] * proj[7] + modl[6] * proj[11] + modl[7] * proj[15]; clip[8] = modl[8] * proj[0] + modl[9] * proj[4] + modl[10] * proj[8] + modl[11] * proj[12]; clip[9] = modl[8] * proj[1] + modl[9] * proj[5] + modl[10] * proj[9] + modl[11] * proj[13]; clip[10] = modl[8] * proj[2] + modl[9] * proj[6] + modl[10] * proj[10] + modl[11] * proj[14]; clip[11] = modl[8] * proj[3] + modl[9] * proj[7] + modl[10] * proj[11] + modl[11] * proj[15]; clip[12] = modl[12] * proj[0] + modl[13] * proj[4] + modl[14] * proj[8] + modl[15] * proj[12]; clip[13] = modl[12] * proj[1] + modl[13] * proj[5] + modl[14] * proj[9] + modl[15] * proj[13]; clip[14] = modl[12] * proj[2] + modl[13] * proj[6] + modl[14] * proj[10] + modl[15] * proj[14]; clip[15] = modl[12] * proj[3] + modl[13] * proj[7] + modl[14] * proj[11] + modl[15] * proj[15]; /* Extract the numbers for the RIGHT plane */ m_Frustum[0][0] = clip[3] - clip[0]; m_Frustum[0][1] = clip[7] - clip[4]; m_Frustum[0][2] = clip[11] - clip[8]; m_Frustum[0][3] = clip[15] - clip[12]; /* Normalize the result */ t = (float)sqrtf(m_Frustum[0][0] * m_Frustum[0][0] + m_Frustum[0][1] * m_Frustum[0][1] + m_Frustum[0][2] * m_Frustum[0][2]); m_Frustum[0][0] /= t; m_Frustum[0][1] /= t; m_Frustum[0][2] /= t; m_Frustum[0][3] /= t; /* Extract the numbers for the LEFT plane */ m_Frustum[1][0] = clip[3] + clip[0]; m_Frustum[1][1] = clip[7] + clip[4]; m_Frustum[1][2] = clip[11] + clip[8]; m_Frustum[1][3] = clip[15] + clip[12]; /* Normalize the result */ t = (float)sqrtf(m_Frustum[1][0] * m_Frustum[1][0] + m_Frustum[1][1] * m_Frustum[1][1] + m_Frustum[1][2] * m_Frustum[1][2]); m_Frustum[1][0] /= t; m_Frustum[1][1] /= t; m_Frustum[1][2] /= t; m_Frustum[1][3] /= t; /* Extract the BOTTOM plane */ m_Frustum[2][0] = clip[3] + clip[1]; m_Frustum[2][1] = clip[7] + clip[5]; m_Frustum[2][2] = clip[11] + clip[9]; m_Frustum[2][3] = clip[15] + clip[13]; /* Normalize the result */ t = (float)sqrtf(m_Frustum[2][0] * m_Frustum[2][0] + m_Frustum[2][1] * m_Frustum[2][1] + m_Frustum[2][2] * m_Frustum[2][2]); m_Frustum[2][0] /= t; m_Frustum[2][1] /= t; m_Frustum[2][2] /= t; m_Frustum[2][3] /= t; /* Extract the TOP plane */ m_Frustum[3][0] = clip[3] - clip[1]; m_Frustum[3][1] = clip[7] - clip[5]; m_Frustum[3][2] = clip[11] - clip[9]; m_Frustum[3][3] = clip[15] - clip[13]; /* Normalize the result */ t = (float)sqrtf(m_Frustum[3][0] * m_Frustum[3][0] + m_Frustum[3][1] * m_Frustum[3][1] + m_Frustum[3][2] * m_Frustum[3][2]); m_Frustum[3][0] /= t; m_Frustum[3][1] /= t; m_Frustum[3][2] /= t; m_Frustum[3][3] /= t; /* Extract the FAR plane */ m_Frustum[4][0] = clip[3] - clip[2]; m_Frustum[4][1] = clip[7] - clip[6]; m_Frustum[4][2] = clip[11] - clip[10]; m_Frustum[4][3] = clip[15] - clip[14]; /* Normalize the result */ t = (float)sqrtf(m_Frustum[4][0] * m_Frustum[4][0] + m_Frustum[4][1] * m_Frustum[4][1] + m_Frustum[4][2] * m_Frustum[4][2]); m_Frustum[4][0] /= t; m_Frustum[4][1] /= t; m_Frustum[4][2] /= t; m_Frustum[4][3] /= t; /* Extract the NEAR plane */ m_Frustum[5][0] = clip[3] + clip[2]; m_Frustum[5][1] = clip[7] + clip[6]; m_Frustum[5][2] = clip[11] + clip[10]; m_Frustum[5][3] = clip[15] + clip[14]; /* Normalize the result */ t = (float)sqrtf(m_Frustum[5][0] * m_Frustum[5][0] + m_Frustum[5][1] * m_Frustum[5][1] + m_Frustum[5][2] * m_Frustum[5][2]); m_Frustum[5][0] /= t; m_Frustum[5][1] /= t; m_Frustum[5][2] /= t; m_Frustum[5][3] /= t; } bool Terrain::CuboidInFrustum(const Box & cuboid) const { // Thanks to Mark Morley for this OpenGL frustum testing code. // See all of it at http://www.markmorley.com/opengl/frustumculling.html. for (int p = 0; p < 6; p++) { if (m_Frustum[p][0] * cuboid.m_Min.x + m_Frustum[p][1] * cuboid.m_Min.y + m_Frustum[p][2] * cuboid.m_Min.z + m_Frustum[p][3] > 0) continue; if (m_Frustum[p][0] * cuboid.m_Max.x + m_Frustum[p][1] * cuboid.m_Min.y + m_Frustum[p][2] * cuboid.m_Min.z + m_Frustum[p][3] > 0) continue; if (m_Frustum[p][0] * cuboid.m_Min.x + m_Frustum[p][1] * cuboid.m_Max.y + m_Frustum[p][2] * cuboid.m_Min.z + m_Frustum[p][3] > 0) continue; if (m_Frustum[p][0] * cuboid.m_Max.x + m_Frustum[p][1] * cuboid.m_Max.y + m_Frustum[p][2] * cuboid.m_Min.z + m_Frustum[p][3] > 0) continue; if (m_Frustum[p][0] * cuboid.m_Min.x + m_Frustum[p][1] * cuboid.m_Min.y + m_Frustum[p][2] * cuboid.m_Max.z + m_Frustum[p][3] > 0) continue; if (m_Frustum[p][0] * cuboid.m_Max.x + m_Frustum[p][1] * cuboid.m_Min.y + m_Frustum[p][2] * cuboid.m_Max.z + m_Frustum[p][3] > 0) continue; if (m_Frustum[p][0] * cuboid.m_Min.x + m_Frustum[p][1] * cuboid.m_Max.y + m_Frustum[p][2] * cuboid.m_Max.z + m_Frustum[p][3] > 0) continue; if (m_Frustum[p][0] * cuboid.m_Max.x + m_Frustum[p][1] * cuboid.m_Max.y + m_Frustum[p][2] * cuboid.m_Max.z + m_Frustum[p][3] > 0) continue; return false; } return true; } #if _USE_RAYTRACING_SUPPORT_ > 0 float Terrain::IntersectRay(float startX, float startY, float startZ, float dirX, float dirY, float dirZ, float &intersectX, float &intersectY, float &intersectZ) { Ray ray; float distance = INFINITY; Vector point; point.x = point.y = point.z = -1.0f; ray.m_Origin.x = startX; ray.m_Origin.y = startY; ray.m_Origin.z = startZ; ray.m_Direction.x = dirX; ray.m_Direction.y = dirY; ray.m_Direction.z = dirZ; ray.m_Direction.Normalize(); m_pRootBlock->IntersectRay(ray, point, distance, this); intersectX = point.x; intersectY = point.y; intersectZ = point.z; return distance; } float Terrain::IntersectRay(float startX, float startY, float startZ, float dirX, float dirY, float dirZ, float &intersectX, float &intersectY, float &intersectZ, int &textureCellX, int &textureCellY, float &texU, float &texV) { float dist = IntersectRay(startX, startY, startZ, dirX, dirY, dirZ, intersectX, intersectY, intersectZ); if (0.0f <= intersectX) GetTextureCoordinates(intersectX, intersectY, textureCellX, textureCellY, texU, texV); return dist; } #endif void Terrain::SetTextureCell(int index, TextureCell * pCell) { m_TextureCells[index] = pCell; } TextureSet *Terrain::GetTextureSet() { return m_pTextureSet; } void Terrain::SetTextureSet(TextureSet * pSet) { if (m_pTextureSet) delete m_pTextureSet; m_pTextureSet = pSet; } void Terrain::SetLatticePosition(int x, int y) { m_LatticePositionX = x; m_LatticePositionY = y; } void Terrain::GetLatticePosition(int &x, int &y) { x = m_LatticePositionX; y = m_LatticePositionY; } void Terrain::WriteRawTextures(const char *szName) { char szRealFilename[1024]; char szFullFilename[1024]; char szFilename[1024]; unsigned int i; for (i = 0; i < (unsigned int)m_pTextureSet->GetNumTextures(); i++) { Texture *pTex = m_pTextureSet->GetTexture(i); m_pTextureSet->GetSharedTextureFilename(i, szFilename, 1024); sprintf(szRealFilename, "%s.%s", szName, szFilename); Settings::GetInstance()->PrependMediaPath(szRealFilename, szFullFilename); FILE *file = fopen(szFullFilename, "wb"); if (file == NULL) throw new DemeterException("Unable to open texture file for writing: disk error"); pTex->WriteRaw(file); fclose(file); } for (i = 0; i < m_NumberOfTextureTiles; i++) { TextureCell *pCell = m_TextureCells[i]; Texture *pTex = pCell->GetTexture(); pCell->GetBaseTextureImageFilename(szFilename, 1024); sprintf(szRealFilename, "%s.%s", szName, szFilename); Settings::GetInstance()->PrependMediaPath(szRealFilename, szFullFilename); FILE *file = fopen(szFullFilename, "wb"); if (file == NULL) throw new DemeterException("Unable to open texture file for writing: disk error"); pTex->WriteRaw(file); fclose(file); for (int j = 0; j < pCell->GetNumberOfDetails(); j++) { pTex = pCell->GetDetail(j)->GetMask(); pCell->GetDetailMaskFilename(j, szFilename, 1024); sprintf(szRealFilename, "%s.%s", szName, szFilename); Settings::GetInstance()->PrependMediaPath(szRealFilename, szFullFilename); FILE *file = fopen(szFullFilename, "wb"); if (file == NULL) throw new DemeterException("Unable to open texture file for writing: disk error"); pTex->WriteRaw(file); fclose(file); } } if (m_pCommonTexture) { GetCommonTextureFilename(szFilename, 1024); sprintf(szRealFilename, "%s.%s", szName, szFilename); Settings::GetInstance()->PrependMediaPath(szRealFilename, szFullFilename); FILE *file = fopen(szFullFilename, "wb"); m_pCommonTexture->WriteRaw(file); fclose(file); } } void Terrain::WriteRawElevations(const char *szFilename) { char szFullFilename[1024]; Settings::GetInstance()->PrependMediaPath(szFilename, szFullFilename); FILE *file = fopen(szFullFilename, "wb"); if (file == NULL) throw new DemeterException("Unable to open elevations file for writing: disk error"); for (int i = 0; i < m_NumberOfVertices; i++) { if (fwrite(&m_pVertices[i].z, sizeof(float), 1, file) != 1) throw new DemeterException("Unable to write elevations file: disk error"); } fclose(file); } void Terrain::AllocateTextureCells(int numCellsX, int numCellsY) { m_NumberOfTextureTilesWidth = numCellsX; m_NumberOfTextureTilesHeight = numCellsY; m_NumberOfTextureTiles = numCellsX * numCellsY; float verticesPerTileWidth = (float)(m_WidthVertices + m_NumberOfTextureTilesWidth - 1) / (float)m_NumberOfTextureTilesWidth; float verticesPerTileHeight = (float)(m_HeightVertices + m_NumberOfTextureTilesHeight - 1) / (float)m_NumberOfTextureTilesHeight; m_TextureTileWidth = (verticesPerTileWidth - 1.0f) * m_VertexSpacing; m_TextureTileHeight = (verticesPerTileHeight - 1.0f) * m_VertexSpacing; GenerateTextureCoordinates(); for (unsigned int i = 0; i < m_NumberOfTextureTilesWidth * m_NumberOfTextureTilesHeight; i++) m_TextureCells.push_back(NULL); } void Terrain::DigCrater(float centerX, float centerY, float radius, int detailTextureId) { const float depth = radius / 2.0f; vector < TerrainVertex > vertices; GetVertices(centerX, centerY, radius, vertices); vector < TerrainVertex >::iterator iter = vertices.begin(); while (iter != vertices.end()) { TerrainVertex v = *iter; //float craterBottom = sqrt(radius*radius - (sqrt(v.m_X*v.m_X + v.m_Y * v.m_Y) * sqrt(v.m_X*v.m_X + v.m_Y * v.m_Y))); float dx = v.m_X - centerX; float dy = v.m_Y - centerY; float distance = sqrtf(dx * dx + dy * dy); float distanceFactor = distance / (radius * 0.5f); if (distanceFactor < 0.0f) distanceFactor = 0.0f; if (1.0f < distanceFactor) distanceFactor = 1.0f; float craterBottom = GetElevation(v.m_X, v.m_Y) - (1.0f - distanceFactor) * depth; SetVertexElevation(v.m_Index, craterBottom, false); iter++; } RecalcGeometry(vertices[0].m_Index, vertices[vertices.size() - 1].m_Index); if (detailTextureId >= 0) { float detailTexelWidth = GetWidth() / (m_NumberOfTextureTilesWidth * Settings::GetInstance()->GetTextureMaskWidth()); int brushWidth = (int)((radius / detailTexelWidth) * 1.3f); if (brushWidth > Settings::GetInstance()->GetTextureMaskWidth()) brushWidth = Settings::GetInstance()->GetTextureMaskWidth() - 1; Paint(detailTextureId, brushWidth, 1.0f, 1.0f, false, centerX, centerY); } } void Terrain::GetVertices(float centerX, float centerY, float radius, vector < TerrainVertex > &vertices) { int centerIndex = GetVertex(centerX, centerY); int span = (int)(radius / m_VertexSpacing); int topLeftIndex = centerIndex - (span / 2) * m_WidthVertices - (span / 2); int topLeftX = topLeftIndex % m_WidthVertices; int topLeftY = topLeftIndex / m_WidthVertices; for (int indexY = topLeftY; indexY < topLeftY + span; indexY++) { if (0 <= indexY && indexY < m_HeightVertices - 1) { for (int indexX = topLeftX; indexX < topLeftX + span; indexX++) { if (0 <= indexX && indexX < m_WidthVertices - 1) { float x = indexX * m_VertexSpacing; float y = indexY * m_VertexSpacing; TerrainVertex v(indexY * m_WidthVertices + indexX, x, y, GetElevation(x, y)); vertices.push_back(v); } } } } } ReloadMaskRequest::ReloadMaskRequest(int textureCellX, int textureCellY, int detailIndex) { m_TextureCellX = textureCellX; m_TextureCellY = textureCellY; m_DetailIndex = detailIndex; } ReloadMaskRequest::~ReloadMaskRequest() { }