/////////////////////////////////////////////////////////////////// // Create information about the terrain and wave vertices. void WaterManager::CreateSuperfancyInfo(CSimulation2* simulation) { if (m_VBWaves) { g_VBMan.Release(m_VBWaves); m_VBWaves = NULL; } if (m_VBWavesIndices) { g_VBMan.Release(m_VBWavesIndices); m_VBWavesIndices = NULL; } CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); ssize_t mapSize = terrain->GetVerticesPerSide(); CmpPtr<ICmpWaterManager> cmpWaterManager(*simulation, SYSTEM_ENTITY); if (!cmpWaterManager) return; // REALLY shouldn't happen and will most likely crash. // Using this to get some more optimization on circular maps CmpPtr<ICmpRangeManager> cmpRangeManager(*simulation, SYSTEM_ENTITY); if (!cmpRangeManager) return; bool circular = cmpRangeManager->GetLosCircular(); float mSize = mapSize*mapSize; float halfSize = (mapSize/2.0); // Warning: this won't work with multiple water planes m_WaterHeight = cmpWaterManager->GetExactWaterLevel(0,0); // TODO: change this whenever we incrementally update because it's def. not too efficient delete[] m_WaveX; delete[] m_WaveZ; delete[] m_DistanceToShore; delete[] m_FoamFactor; m_WaveX = new float[mapSize*mapSize]; m_WaveZ = new float[mapSize*mapSize]; m_DistanceToShore = new float[mapSize*mapSize]; m_FoamFactor = new float[mapSize*mapSize]; u16* heightmap = terrain->GetHeightMap(); // some temporary stuff for wave intensity // not really used too much right now. u8* waveForceHQ = new u8[mapSize*mapSize]; u16 waterHeightInu16 = m_WaterHeight/HEIGHT_SCALE; // used to cache terrain normals since otherwise we'd recalculate them a lot (I'm blurring the "normal" map). // this might be updated to actually cache in the terrain manager but that's not for now. CVector3D* normals = new CVector3D[mapSize*mapSize]; // calculate wave force (not really used right now) // and puts into "normals" the terrain normal at that point // so as to avoid recalculating terrain normals too often. for (ssize_t i = 0; i < mapSize; ++i) { for (ssize_t j = 0; j < mapSize; ++j) { normals[j*mapSize + i] = terrain->CalcExactNormal(((float)i)*4.0f,((float)j)*4.0f); if (circular && (i-halfSize)*(i-halfSize)+(j-halfSize)*(j-halfSize) > mSize) { waveForceHQ[j*mapSize + i] = 255; continue; } u8 color = 0; for (int v = 0; v <= 18; v += 3){ if (j-v >= 0 && i-v >= 0 && heightmap[(j-v)*mapSize + i-v] > waterHeightInu16) { if (color == 0) color = 5; else color++; } } waveForceHQ[j*mapSize + i] = 255 - color * 40; } } // this creates information for waves and stores it in float arrays. PatchRData then puts it in the vertex info for speed. for (ssize_t i = 0; i < mapSize; ++i) { for (ssize_t j = 0; j < mapSize; ++j) { if (circular && (i-halfSize)*(i-halfSize)+(j-halfSize)*(j-halfSize) > mSize) { m_WaveX[j*mapSize + i] = 0.0f; m_WaveZ[j*mapSize + i] = 0.0f; m_DistanceToShore[j*mapSize + i] = 100; m_FoamFactor[j*mapSize + i] = 0.0f; continue; } float depth = m_WaterHeight - heightmap[j*mapSize + i]*HEIGHT_SCALE; int distanceToShore = 10000; // calculation of the distance to the shore. // TODO: this is fairly dumb, though it returns a good result // Could be sped up a fair bit. if (depth >= 0) { // check in the square around. for (int xx = -5; xx <= 5; ++xx) { for (int yy = -5; yy <= 5; ++yy) { if (i+xx >= 0 && i + xx < mapSize) if (j + yy >= 0 && j + yy < mapSize) { float hereDepth = m_WaterHeight - heightmap[(j+yy)*mapSize + (i+xx)]*HEIGHT_SCALE; if (hereDepth < 0 && xx*xx + yy*yy < distanceToShore) distanceToShore = xx*xx + yy*yy; } } } // refine the calculation if we're close enough if (distanceToShore < 9) { for (float xx = -2.5f; xx <= 2.5f; ++xx) { for (float yy = -2.5f; yy <= 2.5f; ++yy) { float hereDepth = m_WaterHeight - terrain->GetExactGroundLevel( (i+xx)*4, (j+yy)*4 ); if (hereDepth < 0 && xx*xx + yy*yy < distanceToShore) distanceToShore = xx*xx + yy*yy; } } } } else { for (int xx = -2; xx <= 2; ++xx) { for (int yy = -2; yy <= 2; ++yy) { float hereDepth = m_WaterHeight - terrain->GetVertexGroundLevel(i+xx, j+yy); if (hereDepth > 0) distanceToShore = 0; } } } // speedup with default values for land squares if (distanceToShore == 10000) { m_WaveX[j*mapSize + i] = 0.0f; m_WaveZ[j*mapSize + i] = 0.0f; m_DistanceToShore[j*mapSize + i] = 100; m_FoamFactor[j*mapSize + i] = 0.0f; continue; } // We'll compute the normals and the "water raise", to know about foam // Normals are a pretty good calculation but it's slow since we normalize so much. CVector3D normal; int waterRaise = 0; for (int xx = -4; xx <= 4; xx += 2) // every 2 tile is good enough. { for (int yy = -4; yy <= 4; yy += 2) { if (j+yy < mapSize && i+xx < mapSize && i+xx >= 0 && j+yy >= 0) normal += normals[(j+yy)*mapSize + (i+xx)]; if (terrain->GetVertexGroundLevel(i+xx,j+yy) < heightmap[j*mapSize + i]*HEIGHT_SCALE) waterRaise += heightmap[j*mapSize + i]*HEIGHT_SCALE - terrain->GetVertexGroundLevel(i+xx,j+yy); } } // normalizes the terrain info to avoid foam moving at too different speeds. normal *= 0.012345679f; normal[1] = 0.1f; normal = normal.Normalized(); m_WaveX[j*mapSize + i] = normal[0]; m_WaveZ[j*mapSize + i] = normal[2]; // distance is /5.0 to be a [0,1] value. m_DistanceToShore[j*mapSize + i] = sqrtf(distanceToShore)/5.0f; // TODO: this can probably be cached as I'm integer here. // computing the amount of foam I want depth = clamp(depth,0.0f,10.0f); float foamAmount = (waterRaise/255.0f) * (1.0f - depth/10.0f) * (waveForceHQ[j*mapSize+i]/255.0f) * (m_Waviness/8.0f); foamAmount += clamp(m_Waviness/2.0f - distanceToShore,0.0f,m_Waviness/2.0f)/(m_Waviness/2.0f) * clamp(m_Waviness/9.0f,0.3f,1.0f); foamAmount = foamAmount > 1.0f ? 1.0f: foamAmount; m_FoamFactor[j*mapSize + i] = foamAmount; } } delete[] normals; delete[] waveForceHQ; // TODO: The rest should be cleaned up // okay let's create the waves squares. i'll divide the map in arbitrary squares // For each of these squares, check if waves are needed. // If yes, look for the best positionning (in order to have a nice blending with the shore) // Then clean-up: remove squares that are too close to each other std::vector<CVector2D> waveSquares; int size = 8; // I think this is the size of the squares. for (int i = 0; i < mapSize/size; ++i) { for (int j = 0; j < mapSize/size; ++j) { int landTexel = 0; int waterTexel = 0; CVector3D avnormal (0.0f,0.0f,0.0f); CVector2D landPosition(0.0f,0.0f); CVector2D waterPosition(0.0f,0.0f); for (int xx = 0; xx < size; ++xx) { for (int yy = 0; yy < size; ++yy) { if (terrain->GetVertexGroundLevel(i*size+xx,j*size+yy) > m_WaterHeight) { landTexel++; landPosition += CVector2D(i*size+xx,j*size+yy); } else { waterPosition += CVector2D(i*size+xx,j*size+yy); waterTexel++; avnormal += terrain->CalcExactNormal( (i*size+xx)*4.0f,(j*size+yy)*4.0f); } } } if (landTexel < size/2) continue; landPosition /= landTexel; waterPosition /= waterTexel; avnormal[1] = 1.0f; avnormal.Normalize(); avnormal[1] = 0.0f; // this should help ensure that the shore is pretty flat. if (avnormal.Length() <= 0.2f) continue; // To get the best position for squares, I start at the mean "ocean" position // And step by step go to the mean "land" position. I keep the position where I change from water to land. // If this never happens, the square is scrapped. if (terrain->GetExactGroundLevel(waterPosition.X*4.0f,waterPosition.Y*4.0f) > m_WaterHeight) continue; CVector2D squarePos(-1,-1); for (u8 i = 0; i < 40; i++) { squarePos = landPosition * (i/40.0f) + waterPosition * (1.0f-(i/40.0f)); if (terrain->GetExactGroundLevel(squarePos.X*4.0f,squarePos.Y*4.0f) > m_WaterHeight) break; } if (squarePos.X == -1) continue; u8 enter = 1; // okaaaaaay. Got a square. Check for proximity. for (unsigned long i = 0; i < waveSquares.size(); i++) { if ( CVector2D(waveSquares[i]-squarePos).LengthSquared() < 80) { enter = 0; break; } } if (enter == 1) waveSquares.push_back(squarePos); } } // Actually create the waves' meshes. std::vector<SWavesVertex> waves_vertex_data; std::vector<GLushort> waves_indices; // loop through each square point. Look in the square around it, calculate the normal // create the square. for (unsigned long i = 0; i < waveSquares.size(); i++) { CVector2D pos(waveSquares[i]); CVector3D avgnorm(0.0f,0.0f,0.0f); for (int xx = -size/2; xx < size/2; ++xx) { for (int yy = -size/2; yy < size/2; ++yy) { avgnorm += terrain->CalcExactNormal((pos.X+xx)*4.0f,(pos.Y+yy)*4.0f); } } avgnorm[1] = 0.1f; // okay crank out a square. // we have the direction of the square. We'll get the perpendicular vector too CVector2D perp(-avgnorm[2],avgnorm[0]); perp = perp.Normalized(); avgnorm = avgnorm.Normalized(); SWavesVertex vertex[4]; vertex[0].m_Position = CVector3D(pos.X + perp.X*(size/2.2f) - avgnorm[0]*1.0f, 0.0f,pos.Y + perp.Y*(size/2.2f) - avgnorm[2]*1.0f); vertex[0].m_Position *= 4.0f; vertex[0].m_Position.Y = m_WaterHeight + 1.0f; vertex[0].m_UV[1] = 1; vertex[0].m_UV[0] = 0; vertex[1].m_Position = CVector3D(pos.X - perp.X*(size/2.2f) - avgnorm[0]*1.0f, 0.0f,pos.Y - perp.Y*(size/2.2f) - avgnorm[2]*1.0f); vertex[1].m_Position *= 4.0f; vertex[1].m_Position.Y = m_WaterHeight + 1.0f; vertex[1].m_UV[1] = 1; vertex[1].m_UV[0] = 1; vertex[3].m_Position = CVector3D(pos.X + perp.X*(size/2.2f) + avgnorm[0]*(size/1.5f), 0.0f,pos.Y + perp.Y*(size/2.2f) + avgnorm[2]*(size/1.5f)); vertex[3].m_Position *= 4.0f; vertex[3].m_Position.Y = m_WaterHeight + 1.0f; vertex[3].m_UV[1] = 0; vertex[3].m_UV[0] = 0; vertex[2].m_Position = CVector3D(pos.X - perp.X*(size/2.2f) + avgnorm[0]*(size/1.5f), 0.0f,pos.Y - perp.Y*(size/2.2f) + avgnorm[2]*(size/1.5f)); vertex[2].m_Position *= 4.0f; vertex[2].m_Position.Y = m_WaterHeight + 1.0f; vertex[2].m_UV[1] = 0; vertex[2].m_UV[0] = 1; waves_indices.push_back(waves_vertex_data.size()); waves_vertex_data.push_back(vertex[0]); waves_indices.push_back(waves_vertex_data.size()); waves_vertex_data.push_back(vertex[1]); waves_indices.push_back(waves_vertex_data.size()); waves_vertex_data.push_back(vertex[2]); waves_indices.push_back(waves_vertex_data.size()); waves_vertex_data.push_back(vertex[3]); } // no vertex buffers if no data generated if (waves_indices.empty()) return; // waves // allocate vertex buffer m_VBWaves = g_VBMan.Allocate(sizeof(SWavesVertex), waves_vertex_data.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER); m_VBWaves->m_Owner->UpdateChunkVertices(m_VBWaves, &waves_vertex_data[0]); // Construct indices buffer m_VBWavesIndices = g_VBMan.Allocate(sizeof(GLushort), waves_indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER); m_VBWavesIndices->m_Owner->UpdateChunkVertices(m_VBWavesIndices, &waves_indices[0]); }
/////////////////////////////////////////////////////////////////// // Calculate the strength of the wind at a given point on the map. // This is too slow and should support limited recomputation. void WaterManager::RecomputeWindStrength() { if (m_WindStrength == NULL) m_WindStrength = new float[m_MapSize*m_MapSize]; CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); float waterLevel = m_WaterHeight; CVector2D windDir = CVector2D(cos(m_WindAngle),sin(m_WindAngle)); CVector2D perp = CVector2D(-windDir.Y, windDir.X); // Our kernel will sample 5 points going towards the wind (generally). int kernel[5][2] = { {(int)windDir.X*2,(int)windDir.Y*2}, {(int)windDir.X*5,(int)windDir.Y*5}, {(int)windDir.X*9,(int)windDir.Y*9}, {(int)windDir.X*16,(int)windDir.Y*16}, {(int)windDir.X*25,(int)windDir.Y*25} }; float* Temp = new float[m_MapSize*m_MapSize]; std::fill(Temp, Temp + m_MapSize*m_MapSize, 1.0f); for (size_t j = 0; j < m_MapSize; ++j) for (size_t i = 0; i < m_MapSize; ++i) { float curHeight = terrain->GetVertexGroundLevel(i,j); if (curHeight >= waterLevel) { Temp[j*m_MapSize + i] = 0.3f; // blurs too strong otherwise continue; } if (terrain->GetVertexGroundLevel(i + ceil(windDir.X),j + ceil(windDir.Y)) < waterLevel) continue; // Calculate how dampened our waves should be. float tendency = 0.0f; float oldHeight = std::max(waterLevel,terrain->GetVertexGroundLevel(i+kernel[4][0],j+kernel[4][1])); float currentHeight = std::max(waterLevel,terrain->GetVertexGroundLevel(i+kernel[3][0],j+kernel[3][1])); float avgheight = oldHeight + currentHeight; tendency = currentHeight - oldHeight; oldHeight = currentHeight; currentHeight = std::max(waterLevel,terrain->GetVertexGroundLevel(i+kernel[2][0],j+kernel[2][1])); avgheight += currentHeight; tendency += currentHeight - oldHeight; oldHeight = currentHeight; currentHeight = std::max(waterLevel,terrain->GetVertexGroundLevel(i+kernel[1][0],j+kernel[1][1])); avgheight += currentHeight; tendency += currentHeight - oldHeight; oldHeight = currentHeight; currentHeight = std::max(waterLevel,terrain->GetVertexGroundLevel(i+kernel[0][0],j+kernel[0][1])); avgheight += currentHeight; tendency += currentHeight - oldHeight; float baseLevel = std::max(0.0f,1.0f - (avgheight/5.0f-waterLevel)/20.0f); baseLevel *= baseLevel; tendency /= 15.0f; baseLevel -= tendency; // if the terrain was sloping downwards, increase baselevel. Otherwise reduce. baseLevel = clamp(baseLevel,0.0f,1.0f); // Draw on map. This is pretty slow. float length = 35.0f * (1.0f-baseLevel/1.8f); for (float y = 0; y < length; y += 0.6f) { int xx = clamp(i - y * windDir.X,0.0f,(float)(m_MapSize-1)); int yy = clamp(j - y * windDir.Y,0.0f,(float)(m_MapSize-1)); Temp[yy*m_MapSize + xx] = Temp[yy*m_MapSize + xx] < (0.0f+baseLevel/1.5f) * (1.0f-y/length) + y/length * 1.0f ? Temp[yy*m_MapSize + xx] : (0.0f+baseLevel/1.5f) * (1.0f-y/length) + y/length * 1.0f; } } int blurKernel[4][2] = { {(int)ceil(windDir.X),(int)ceil(windDir.Y)}, {(int)windDir.X*3,(int)windDir.Y*3}, {(int)ceil(perp.X),(int)ceil(perp.Y)}, {(int)-ceil(perp.X),(int)-ceil(perp.Y)} }; float blurValue; for (size_t j = 2; j < m_MapSize-2; ++j) for (size_t i = 2; i < m_MapSize-2; ++i) { blurValue = Temp[(j+blurKernel[0][1])*m_MapSize + i+blurKernel[0][0]]; blurValue += Temp[(j+blurKernel[0][1])*m_MapSize + i+blurKernel[0][0]]; blurValue += Temp[(j+blurKernel[0][1])*m_MapSize + i+blurKernel[0][0]]; blurValue += Temp[(j+blurKernel[0][1])*m_MapSize + i+blurKernel[0][0]]; m_WindStrength[j*m_MapSize + i] = blurValue * 0.25f; } delete[] Temp; }
// Build vertex buffer for water vertices over our patch void CPatchRData::BuildWater() { PROFILE3("build water"); // number of vertices in each direction in each patch ENSURE((PATCH_SIZE % water_cell_size) == 0); if (m_VBWater) { g_VBMan.Release(m_VBWater); m_VBWater = 0; } if (m_VBWaterIndices) { g_VBMan.Release(m_VBWaterIndices); m_VBWaterIndices = 0; } m_WaterBounds.SetEmpty(); // We need to use this to access the water manager or we may not have the // actual values but some compiled-in defaults CmpPtr<ICmpWaterManager> cmpWaterManager(*m_Simulation, SYSTEM_ENTITY); if (!cmpWaterManager) return; // Build data for water std::vector<SWaterVertex> water_vertex_data; std::vector<GLushort> water_indices; u16 water_index_map[PATCH_SIZE+1][PATCH_SIZE+1]; memset(water_index_map, 0xFF, sizeof(water_index_map)); // TODO: This is not (yet) exported via the ICmp interface so... we stick to these values which can be compiled in defaults WaterManager* WaterMgr = g_Renderer.GetWaterManager(); if (WaterMgr->m_NeedsFullReloading && !g_AtlasGameLoop->running) { WaterMgr->m_NeedsFullReloading = false; WaterMgr->CreateSuperfancyInfo(m_Simulation); } CPatch* patch = m_Patch; CTerrain* terrain = patch->m_Parent; ssize_t mapSize = (size_t)terrain->GetVerticesPerSide(); ssize_t x1 = m_Patch->m_X*PATCH_SIZE; ssize_t z1 = m_Patch->m_Z*PATCH_SIZE; // build vertices, uv, and shader varying for (ssize_t z = 0; z < PATCH_SIZE; z += water_cell_size) { for (ssize_t x = 0; x <= PATCH_SIZE; x += water_cell_size) { // Check that the edge at x is partially underwater float startTerrainHeight[2] = { terrain->GetVertexGroundLevel(x+x1, z+z1), terrain->GetVertexGroundLevel(x+x1, z+z1 + water_cell_size) }; float startWaterHeight[2] = { cmpWaterManager->GetExactWaterLevel(x+x1, z+z1), cmpWaterManager->GetExactWaterLevel(x+x1, z+z1 + water_cell_size) }; if (startTerrainHeight[0] >= startWaterHeight[0] && startTerrainHeight[1] >= startWaterHeight[1]) continue; // Move x back one cell (unless at start of patch), then scan rightwards bool belowWater = true; ssize_t stripStart; for (stripStart = x = std::max(x-water_cell_size, (ssize_t)0); x <= PATCH_SIZE; x += water_cell_size) { // If this edge is not underwater, and neither is the previous edge // (i.e. belowWater == false), then stop this strip since we've reached // a cell that's entirely above water float terrainHeight[2] = { terrain->GetVertexGroundLevel(x+x1, z+z1), terrain->GetVertexGroundLevel(x+x1, z+z1 + water_cell_size) }; float waterHeight[2] = { cmpWaterManager->GetExactWaterLevel(x+x1, z+z1), cmpWaterManager->GetExactWaterLevel(x+x1, z+z1 + water_cell_size) }; if (terrainHeight[0] >= waterHeight[0] && terrainHeight[1] >= waterHeight[1]) { if (!belowWater) break; belowWater = false; } else belowWater = true; // Edge (x,z)-(x,z+1) is at least partially underwater, so extend the water plane strip across it // Compute vertex data for the 2 points on the edge for (int j = 0; j < 2; j++) { // Check if we already computed this vertex from an earlier strip if (water_index_map[z+j*water_cell_size][x] != 0xFFFF) continue; SWaterVertex vertex; terrain->CalcPosition(x+x1, z+z1 + j*water_cell_size, vertex.m_Position); float depth = waterHeight[j] - vertex.m_Position.Y; vertex.m_Position.Y = waterHeight[j]; m_WaterBounds += vertex.m_Position; // NB: Usually this factor is view dependent, but for performance reasons // we do not take it into account with basic non-shader based water. // Average constant Fresnel effect for non-fancy water float alpha = clamp(depth / WaterMgr->m_WaterFullDepth + WaterMgr->m_WaterAlphaOffset, WaterMgr->m_WaterAlphaOffset, WaterMgr->m_WaterMaxAlpha); // Split the depth data across 24 bits, so the fancy-water shader can reconstruct // the depth value while the simple-water can just use the precomputed alpha float depthInt = floor(depth); float depthFrac = depth - depthInt; vertex.m_DepthData = SColor4ub( u8(clamp(depthInt, 0.0f, 255.0f)), u8(clamp(-depthInt, 0.0f, 255.0f)), u8(clamp(depthFrac*255.0f, 0.0f, 255.0f)), u8(clamp(alpha*255.0f, 0.0f, 255.0f))); int tx = x+x1; int ty = z+z1 + j*water_cell_size; if (g_AtlasGameLoop->running) { // currently no foam is used so push whatever vertex.m_WaterData = CVector4D(0.0f,0.0f,0.0f,0.0f); } else { vertex.m_WaterData = CVector4D(WaterMgr->m_WaveX[tx + ty*mapSize], WaterMgr->m_WaveZ[tx + ty*mapSize], WaterMgr->m_DistanceToShore[tx + ty*mapSize], WaterMgr->m_FoamFactor[tx + ty*mapSize]); } water_index_map[z+j*water_cell_size][x] = water_vertex_data.size(); water_vertex_data.push_back(vertex); } // If this was not the first x in the strip, then add a quad // using the computed vertex data if (x <= stripStart) continue; water_indices.push_back(water_index_map[z + water_cell_size][x - water_cell_size]); water_indices.push_back(water_index_map[z][x - water_cell_size]); water_indices.push_back(water_index_map[z][x]); water_indices.push_back(water_index_map[z + water_cell_size][x]); } } } // no vertex buffers if no data generated if (water_indices.size() == 0) return; // allocate vertex buffer m_VBWater = g_VBMan.Allocate(sizeof(SWaterVertex), water_vertex_data.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER); m_VBWater->m_Owner->UpdateChunkVertices(m_VBWater, &water_vertex_data[0]); // Construct indices buffer m_VBWaterIndices = g_VBMan.Allocate(sizeof(GLushort), water_indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER); m_VBWaterIndices->m_Owner->UpdateChunkVertices(m_VBWaterIndices, &water_indices[0]); }