/* * File: Terran.cpp * Author: Tomas Goldmann * Date: 2025-03-23 * Description: This class defines terrain (surface). * * Copyright (c) 2025, Brno University of Technology. All rights reserved. * Licensed under the MIT. */ #include "Terrain.h" #include #include #include #include "utils.h" // Include utils.h for checkGLError #include Terrain::Terrain(int gridSize, float gridSpacing) : gridSize(gridSize), gridSpacing(gridSpacing), vertices(gridSize * gridSize), normals(gridSize * gridSize), texCoords(gridSize * gridSize), vertexBufferID(0), normalBufferID(0), texCoordBufferID(0), indexBufferID(0), vaoID(0), indexCount(0) {} Terrain::~Terrain() { cleanup(); } bool Terrain::init(GLuint heightMapTextureID) { generateGrid(heightMapTextureID); createBuffers(); return true; } void Terrain::cleanup() { glDeleteBuffers(1, &vertexBufferID); glDeleteBuffers(1, &normalBufferID); glDeleteBuffers(1, &texCoordBufferID); glDeleteBuffers(1, &indexBufferID); glDeleteVertexArrays(1, &vaoID); } void Terrain::generateGrid(GLuint heightMapTextureID) { std::cout << "Terrain::generateGrid - gridSize: " << gridSize << std::endl; vertices.resize(gridSize * gridSize); normals.resize(gridSize * gridSize); texCoords.resize(gridSize * gridSize); // **Access Heightmap Texture Data** glBindTexture(GL_TEXTURE_2D, heightMapTextureID); // Bind heightmap texture GLint textureWidth, textureHeight; glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &textureWidth); // Get texture width glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &textureHeight); // Get texture height std::vector heightmapData(textureWidth * textureHeight); // Assuming 8-bit grayscale heightmap glPixelStorei(GL_PACK_ALIGNMENT, 1); glGetTexImage(GL_TEXTURE_2D, 0, GL_RED, GL_UNSIGNED_BYTE, heightmapData.data()); // Get texture pixel data (Red channel = grayscale height) glBindTexture(GL_TEXTURE_2D, 0); // Unbind texture float heightScale = 40.0f; // Adjust this to control terrain height scale for (int x = 0; x < gridSize; ++x) { for (int z = 0; z < gridSize; ++z) { float worldX = (x - gridSize / 2.0f) * gridSpacing; float worldZ = (z - gridSize / 2.0f) * gridSpacing; // **Sample height from heightmap texture** float height = 0.0f; if (x < textureWidth && z < textureHeight && x >= 0 && z >= 0) { // Bounds check - important! height = static_cast(heightmapData[z * textureWidth + x]) / 255.0f * heightScale-15.0; // Normalize to [0, 1], scale by heightScale } else { // Handle out-of-bounds access (e.g., set height to 0 or a default value) height = 0.0f; } //vertices[x * gridSize + z] = glm::vec3(worldX, height, worldZ); // Set vertex position with height from heightmap vertices[x * gridSize + z] = glm::vec3(worldX, height, worldZ); // Flat terrain at y=0 normals[x * gridSize + z] = glm::vec3(0.0f, 1.0f, 0.0f); // Upward normals for flat terrain texCoords[x * gridSize + z] = glm::vec2(static_cast(x) / 50.0f, static_cast(z) / 50.0f); // Example texture tiling //originalWorldX[x * gridSize + z] = worldX; //originalWorldZ[x * gridSize + z] = worldZ; } } std::cout << "Width: " << textureWidth << "Geight: " << textureHeight << std::endl; } void Terrain::createBuffers() { glGenVertexArrays(1, &vaoID); checkGLError("1"); // Check after drawMeshVBO call glBindVertexArray(vaoID); checkGLError("2"); // Check after drawMeshVBO call glGenBuffers(1, &vertexBufferID); checkGLError("3"); // Check after drawMeshVBO call glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); checkGLError("4"); // Check after drawMeshVBO call glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), vertices.data(), GL_STATIC_DRAW); checkGLError("5"); // Check after drawMeshVBO call glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); checkGLError("6"); // Check after drawMeshVBO call glEnableVertexAttribArray(0); checkGLError("7"); // Check after drawMeshVBO call glGenBuffers(1, &normalBufferID); checkGLError("8"); // Check after drawMeshVBO call glBindBuffer(GL_ARRAY_BUFFER, normalBufferID); checkGLError("9"); // Check after drawMeshVBO call glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(glm::vec3), normals.data(), GL_STATIC_DRAW); checkGLError("10"); // Check after drawMeshVBO call glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0); checkGLError("11"); // Check after drawMeshVBO call glEnableVertexAttribArray(1); checkGLError("12"); // Check after drawMeshVBO call glGenBuffers(1, &texCoordBufferID); checkGLError("13"); // Check after drawMeshVBO call glBindBuffer(GL_ARRAY_BUFFER, texCoordBufferID); checkGLError("14"); // Check after drawMeshVBO call glBufferData(GL_ARRAY_BUFFER, texCoords.size() * sizeof(glm::vec2), texCoords.data(), GL_STATIC_DRAW); checkGLError("15"); // Check after drawMeshVBO call glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, 0); checkGLError("16"); // Check after drawMeshVBO call glEnableVertexAttribArray(2); checkGLError("17"); // Check after drawMeshVBO call std::vector indices; for (int x = 0; x < gridSize - 1; ++x) { for (int z = 0; z < gridSize - 1; ++z) { unsigned int v00 = x * gridSize + z; unsigned int v10 = (x + 1) * gridSize + z; unsigned int v11 = (x + 1) * gridSize + (z + 1); unsigned int v01 = x * gridSize + (z + 1); indices.insert(indices.end(), {v00, v10, v11, v01}); } } glGenBuffers(1, &indexBufferID); checkGLError("glGenBuffers - indexBufferID"); // Check after glGenBuffers glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID); checkGLError("18"); // Check after drawMeshVBO call glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW); checkGLError("19"); // Check after drawMeshVBO call glBindVertexArray(0); checkGLError("20"); // Check after drawMeshVBO call indexCount = indices.size(); } void Terrain::updateBuffers() { // Not needed for static terrain in this basic example } glm::vec3 Terrain::getVertex(int x, int z) const { assert(x >= 0 && x < gridSize); assert(z >= 0 && z < gridSize); return vertices[x * gridSize + z]; } glm::vec3 Terrain::getNormal(float x, float z) const { return normals[x * gridSize + z]; }