/* * File: Ocean.cpp * Author: Tomas Goldmann * Date: 2025-03-23 * Description: This class defines ocean (surface). The core functions of this class are optimized by the IPA Project 2025. * * Copyright (c) 2025, Brno University of Technology. All rights reserved. * Licensed under the MIT. */ #include "Ocean.h" #include #include // For pi Ocean::Ocean(int gridSize) : time(0.0f),gridSize(gridSize), gridSpacing(1.0f), amplitude(0.8f), wavelength(10.0f), frequency(1.0f), // Adjusted amplitude slightly direction(glm::vec2(1.0f, 0.0f)), phase(0.0f), seaLevelOffset(-2.0f) { //gerstnerWaves.push_back({1.0f, 10.0f, 1.0f, glm::normalize(glm::vec2(1.0f, 0.0f)), 0.0f}); //gerstnerWaves.push_back({0.3f, 5.0f, 2.0f, glm::normalize(glm::vec2(1.0f, 1.0f)), 0.0f}); //gerstnerWaves.push_back({1.0f, 3.0f, 1.0f, glm::normalize(glm::vec2(1.0f, 0.5f)), 0.0f}); //gerstnerWaves.push_back({0.3f, 5.0f, 2.0f, glm::normalize(glm::vec2(0.5f, 0.5f)), 0.0f}); //gerstnerWaves.push_back({1.0f, 10.0f, 1.0f, glm::normalize(glm::vec2(1.0f, 0.0f)), 0.0f}); //gerstnerWaves.push_back({0.5f, 2.0f, 3.0f, glm::normalize(glm::vec2(1.0f, 1.0f)), 0.0f}); //gerstnerWaves.push_back({1.0f, 1.0f, 0.2f, glm::normalize(glm::vec2(0.7f, 0.2f)), 0.0f}); //gerstnerWaves.push_back({0.5f, 1.2f, 2.0f, glm::normalize(glm::vec2(0.9f, 0.8f)), 0.0f}); #if (0) gerstnerWaves.push_back({0.12f, 6.5f, 1.2f, glm::normalize(glm::vec2(1.0f, 0.8f)), 0.0f}); gerstnerWaves.push_back({0.10f, 9.0f, 1.0f, glm::normalize(glm::vec2(-1.0f, 0.4f)), 1.0f}); gerstnerWaves.push_back({0.08f, 13.0f, 0.8f, glm::normalize(glm::vec2(0.2f, -1.0f)), 2.2f}); gerstnerWaves.push_back({0.07f, 16.0f, 0.6f, glm::normalize(glm::vec2(-0.6f, -1.0f)), 3.0f}); gerstnerWaves.push_back({0.11f, 7.5f, 1.1f, glm::normalize(glm::vec2(0.0f, 1.0f)), 0.5f}); #endif #if (0) gerstnerWaves.push_back({0.18f, 7.0f, 0.9f, glm::normalize(glm::vec2(0.8f, 0.6f)), 0.0f}); gerstnerWaves.push_back({0.15f, 10.0f, 0.8f, glm::normalize(glm::vec2(-0.9f, 0.4f)), 1.1f}); gerstnerWaves.push_back({0.13f, 14.0f, 0.7f, glm::normalize(glm::vec2(0.3f, -1.0f)), 2.3f}); gerstnerWaves.push_back({0.12f, 18.0f, 0.6f, glm::normalize(glm::vec2(-0.6f, -1.0f)), 3.0f}); gerstnerWaves.push_back({0.16f, 8.5f, 0.85f, glm::normalize(glm::vec2(0.0f, 1.0f)), 0.7f}); #endif #if (1) gerstnerWaves.push_back({0.3f, 5.0f, 2.0f, glm::normalize(glm::vec2(1.0f, 1.0f)), 0.0f}); gerstnerWaves.push_back({0.2f, 8.0f, 1.5f, glm::normalize(glm::vec2(-1.0f, 0.5f)), 1.0f}); gerstnerWaves.push_back({0.15f, 12.0f, 1.0f, glm::normalize(glm::vec2(0.3f, -1.0f)), 2.5f}); gerstnerWaves.push_back({0.1f, 15.0f, 0.8f, glm::normalize(glm::vec2(-0.7f, -0.7f)), 3.7f}); gerstnerWaves.push_back({0.25f, 7.0f, 1.8f, glm::normalize(glm::vec2(0.0f, 1.0f)), 0.8f}); #endif } bool Ocean::init() { generateGrid(); createBuffers(); // Create VBOs and IBO return true; } void Ocean::update(float deltaTime) { time += deltaTime; std::vector updatedVertices_vec = vertices; std::vector updatedNormals_vec(vertices.size()); updateVertices(&updatedVertices_vec, &updatedNormals_vec, originalWorldX.data(), originalWorldZ.data(), gridSize, time); updateBuffers(updatedVertices_vec, updatedNormals_vec); } void Ocean::generateGrid() { //std::cout << "Ocean::generateGrid - gridSize: " << gridSize << " " << vertices.size()<< std::endl; vertices.resize(gridSize * gridSize); // Resize original coordinate vectors originalWorldX.resize(gridSize * gridSize); originalWorldZ.resize(gridSize * gridSize); 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; vertices[x * gridSize + z] = glm::vec3(worldX, 0.0f, worldZ); // Store original world coordinates originalWorldX[x * gridSize + z] = worldX; originalWorldZ[x * gridSize + z] = worldZ; } } } void Ocean::updateVertices(std::vector * updatedVertices, std::vector * updatedNormals, float * originalWorldX_, float * originalWorldZ_, int _grid_size, float time) { for (int x = 0; x < _grid_size; ++x) { for (int z = 0; z < _grid_size; ++z) { glm::vec3 &vertex = (*updatedVertices)[x * _grid_size + z]; // Use reference to modify directly float originalX = originalWorldX_[x * _grid_size + z]; float originalZ = originalWorldZ_[x * _grid_size + z]; vertex.y = getWaveHeight(vertex.x, vertex.z, time); (*updatedNormals)[x * gridSize + z] = getWaveNormal(originalX, originalZ, time); // Calculate normal using original coords } } } float Ocean::getWaveHeight(float x, float z, float time) const { float totalHeight = 0.0f; for (const auto& wave : gerstnerWaves) { totalHeight += getGerstnerWaveHeight(wave, x, z, time); } return totalHeight + seaLevelOffset; } float Ocean::getGerstnerWaveHeight(const GerstnerWave& wave, float x, float z, float time) const { float k = 2.0f * glm::pi() / wave.wavelength; float w = wave.speed * k; float dotProduct = glm::dot(wave.direction, glm::vec2(x, z)); float periodicAmplitude = wave.amplitude * 0.5f * (1.0f + sin(2.0f * glm::pi() * time / wave.wavelength)); float waveHeightValue = periodicAmplitude * sin(k * dotProduct - w * time + wave.phase); if (fabs(x) < 0.5f && fabs(z) < 0.5f) { //std::cout << " getGerstnerWaveHeight - time: " << time << ", periodicAmplitude: " << periodicAmplitude << ", waveHeightValue: " << waveHeightValue << std::endl; } /* if(c<8){ float a = 2.0f * glm::pi() * time / wave.wavelength; puts(""); printf("2*pi*time/wave.wavelength: %f \n",a); printf("sin(2*pi*time/wave.wavelength): %f \n",sin(a)); printf("k*dot: %f \n",k * dotProduct); printf("w*time: %f \n",w * time); printf("wave phase: %f \n",wave.phase); printf("k*dot - w*time + phase: %f \n",k * dotProduct-w*time+wave.phase); printf("sin(k*dot - w*time + phase: %f )\n",sin(k * dotProduct-w*time+wave.phase)); printf("res: %f pariodic: %f dot: %f time: %f wave_x x: %f %f wave_y z: %f %f\n",waveHeightValue,periodicAmplitude,dotProduct,time,wave.direction.x,x,wave.direction.y,z); puts(""); c++; if(c==7){ puts(""); } } */ return waveHeightValue; } glm::vec3 Ocean::getGerstnerWaveDisplacement(const GerstnerWave& wave, float x, float z, float time) const { float k = 2.0f * glm::pi() / wave.wavelength; float w = wave.speed * k; float dotProduct = glm::dot(wave.direction, glm::vec2(x, z)); float cosTerm = cos(k * dotProduct - w * time + wave.phase); float periodicAmplitude = wave.amplitude * 0.5f * (1.0f + sin(2.0f * glm::pi() * time / wave.wavelength)); return glm::vec3(wave.direction.x * periodicAmplitude * cosTerm, 0.0f, wave.direction.y * periodicAmplitude * cosTerm); } glm::vec3 Ocean::getWaveNormal(float x, float z, float time) const { glm::vec3 tangentX = glm::vec3(1.0f, 0.0f, 0.0f); glm::vec3 tangentZ = glm::vec3(0.0f, 0.0f, 1.0f); for (const auto& wave : gerstnerWaves) { float k = 2.0f * glm::pi() / wave.wavelength; float w = wave.speed * k; float dotProduct = glm::dot(wave.direction, glm::vec2(x, z)); float sinTerm = sin(k * dotProduct - w * time + wave.phase); float cosTerm = cos(k * dotProduct - w * time + wave.phase); float periodicAmplitude = wave.amplitude * 0.5f * (1.0f + sin(2.0f * glm::pi() * time / wave.wavelength)); float modulatedAmplitude = periodicAmplitude; // Calculate tangent vectors for EACH wave component and ACCUMULATE them directly tangentX += glm::vec3( -modulatedAmplitude * wave.direction.x * wave.direction.x * k * sinTerm, // dx_dx modulatedAmplitude * wave.direction.x * k * cosTerm, // dy_dx -modulatedAmplitude * wave.direction.x * wave.direction.y * k * sinTerm // dz_dx ); tangentZ += glm::vec3( -modulatedAmplitude * wave.direction.x * wave.direction.y * k * sinTerm, // dx_dz modulatedAmplitude * wave.direction.y * k * cosTerm, // dy_dz -modulatedAmplitude * wave.direction.y * wave.direction.y * k * sinTerm // dz_dz ); } /* auto cros = glm::cross(tangentZ, tangentX); if(c==0&&time!=0){ puts("tangent:"); c++; std::cout< &updatedVertices, const std::vector &updatedNormals) { glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glBufferSubData(GL_ARRAY_BUFFER, 0, updatedVertices.size() * sizeof(glm::vec3), updatedVertices.data()); // Update vertex positions glBindBuffer(GL_ARRAY_BUFFER, normalBufferID); glBufferSubData(GL_ARRAY_BUFFER, 0, updatedNormals.size() * sizeof(glm::vec3), updatedNormals.data()); // Update normals } glm::vec3 Ocean::getVertex(int x, int z) const { return vertices[x * gridSize + z]; } void Ocean::setGridSize(int newGridSize) { gridSize = newGridSize; generateGrid(); // Re-generate the grid with the new size std::vector updatedVertices = vertices; // Create a copy to update std::vector updatedNormals(vertices.size()); // Vector to store updated normals updateVertices(&updatedVertices, &updatedNormals, originalWorldX.data(), originalWorldZ.data(), gridSize, time); updateBuffers(updatedVertices, updatedNormals); //updateVertices(); // Re-update vertices based on waves (optional, if needed immediately) } GLuint Ocean::getVAO() const { return vaoID; } GLuint Ocean::getIndexCount() const { return indexCount; } int Ocean::getGridIndex(int x, int z) const { return x * gridSize + z; } void Ocean::createBuffers() { glGenVertexArrays(1, &vaoID); checkGLError("glGenVertexArrays"); // Check after glGenVertexArrays glBindVertexArray(vaoID); checkGLError("glBindVertexArray"); // Check after glBindVertexArray // Generate VBOs glGenBuffers(1, &vertexBufferID); checkGLError("glGenBuffers - vertexBufferID"); // Check after glGenBuffers glGenBuffers(1, &normalBufferID); checkGLError("glGenBuffers - normalBufferID"); // Check after glGenBuffers glGenBuffers(1, &texCoordBufferID); checkGLError("glGenBuffers - texCoordBufferID"); // Check after glGenBuffers glGenBuffers(1, &indexBufferID); checkGLError("glGenBuffers - indexBufferID"); // Check after glGenBuffers // 1. Vertex Positions VBO glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); checkGLError("glBindBuffer - vertexBufferID"); // Check after glBindBuffer glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), vertices.data(), GL_DYNAMIC_DRAW); checkGLError("glBufferData - vertexBufferID"); // Check after glBufferData glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); checkGLError("glVertexAttribPointer - vertexBufferID"); // Check after glVertexAttribPointer glEnableVertexAttribArray(0); checkGLError("glEnableVertexAttribArray - location 0"); // Check after glEnableVertexAttribArray // 2. Vertex Normals VBO (initially flat normals - updated in updateVertices/updateBuffers) std::vector normals(vertices.size(), glm::vec3(0.0f, 1.0f, 0.0f)); // Initialize with flat normals glBindBuffer(GL_ARRAY_BUFFER, normalBufferID); checkGLError("glBindBuffer - normalBufferID"); // Check after glBindBuffer glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(glm::vec3), normals.data(), GL_DYNAMIC_DRAW); checkGLError("glBufferData - normalBufferID"); // Check after glBufferData glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); checkGLError("glVertexAttribPointer - normalBufferID"); // Check after glVertexAttribPointer glEnableVertexAttribArray(1); checkGLError("glEnableVertexAttribArray - location 1"); // Check after glEnableVertexAttribArray // 3. Texture Coordinates VBO std::vector texCoords(vertices.size()); for (int x = 0; x < gridSize; ++x) { for (int z = 0; z < gridSize; ++z) { float texU = static_cast(x) / 70.0f; float texV = static_cast(z) / 70.0f; texCoords[x * gridSize + z] = glm::vec2(texU, texV); } } glBindBuffer(GL_ARRAY_BUFFER, texCoordBufferID); checkGLError("glBindBuffer - texCoordBufferID"); // Check after glBindBuffer glBufferData(GL_ARRAY_BUFFER, texCoords.size() * sizeof(glm::vec2), texCoords.data(), GL_STATIC_DRAW); checkGLError("glBufferData - texCoordBufferID"); // Check after glBufferData glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0); checkGLError("glVertexAttribPointer - texCoordBufferID"); // Check after glVertexAttribPointer glEnableVertexAttribArray(2); checkGLError("glEnableVertexAttribArray - location 2"); // Check after glEnableVertexAttribArray // 4. Index Buffer Object (IBO) for Quads 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}); // Quad indices (counter-clockwise) } } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID); checkGLError("glBindBuffer - indexBufferID"); // Check after glBindBuffer glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW); checkGLError("glBufferData - indexBufferID"); // Check after glBufferData glBindVertexArray(0); // Unbind VAO checkGLError("glBindVertexArray(0)"); // Check after unbinding VAO glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind VBO - Optional, VAO unbinding often unbinds VBOs checkGLError("glBindBuffer(0) - ARRAY_BUFFER"); // Check after unbinding ARRAY_BUFFER glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // Unbind IBO - Optional, VAO unbinding often unbinds IBO checkGLError("glBindBuffer(0) - ELEMENT_ARRAY_BUFFER"); // Check after unbinding ELEMENT_ARRAY_BUFFER indexCount = indices.size(); // Store index count for rendering } Ocean::~Ocean() { cleanup(); // Call cleanup to release OpenGL resources } void Ocean::cleanup() { // No dynamic memory allocation in this simple version // Release OpenGL resources (VBOs, IBO, VAO) glDeleteBuffers(1, &vertexBufferID); glDeleteBuffers(1, &normalBufferID); glDeleteBuffers(1, &texCoordBufferID); glDeleteBuffers(1, &indexBufferID); glDeleteVertexArrays(1, &vaoID); vertexBufferID = 0; normalBufferID = 0; texCoordBufferID = 0; indexBufferID = 0; vaoID = 0; }