void SimRender::ConstructLineOnGround(const CSimContext& context, const std::vector<float>& xz, SOverlayLine& overlay, bool floating, float heightOffset) { PROFILE("ConstructLineOnGround"); overlay.m_Coords.clear(); CmpPtr<ICmpTerrain> cmpTerrain(context, SYSTEM_ENTITY); if (!cmpTerrain) return; if (xz.size() < 2) return; float water = 0.f; if (floating) { CmpPtr<ICmpWaterManager> cmpWaterManager(context, SYSTEM_ENTITY); if (cmpWaterManager) water = cmpWaterManager->GetExactWaterLevel(xz[0], xz[1]); } overlay.m_Coords.reserve(xz.size()/2 * 3); for (size_t i = 0; i < xz.size(); i += 2) { float px = xz[i]; float pz = xz[i+1]; float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset; overlay.m_Coords.push_back(px); overlay.m_Coords.push_back(py); overlay.m_Coords.push_back(pz); } }
virtual CFixedVector3D GetPosition() { if (!m_InWorld) { LOGERROR(L"CCmpPosition::GetPosition called on entity when IsInWorld is false"); return CFixedVector3D(); } entity_pos_t baseY; if (m_RelativeToGround) { CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); if (cmpTerrain) baseY = cmpTerrain->GetGroundLevel(m_X, m_Z); if (m_Floating) { CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); if (cmpWaterManager) baseY = std::max(baseY, cmpWaterManager->GetWaterLevel(m_X, m_Z)); } } return CFixedVector3D(m_X, baseY + m_YOffset, m_Z); }
void ActorViewer::SetWaterEnabled(bool enabled) { m.WaterEnabled = enabled; // Adjust water level entity_pos_t waterLevel = entity_pos_t::FromFloat(enabled ? 10.f : 0.f); CmpPtr<ICmpWaterManager> cmpWaterManager(m.Simulation2, SYSTEM_ENTITY); if (cmpWaterManager) cmpWaterManager->SetWaterLevel(waterLevel); }
static void ConstructCircleOrClosedArc( const CSimContext& context, float x, float z, float radius, bool isCircle, float start, float end, SOverlayLine& overlay, bool floating, float heightOffset) { overlay.m_Coords.clear(); CmpPtr<ICmpTerrain> cmpTerrain(context, SYSTEM_ENTITY); if (!cmpTerrain) return; float water = 0.f; if (floating) { CmpPtr<ICmpWaterManager> cmpWaterManager(context, SYSTEM_ENTITY); if (cmpWaterManager) water = cmpWaterManager->GetExactWaterLevel(x, z); } // Adapt the circle resolution to look reasonable for small and largeish radiuses size_t numPoints = clamp((size_t)(radius*(end-start)), (size_t)12, (size_t)48); if (!isCircle) overlay.m_Coords.reserve((numPoints + 1 + 2) * 3); else overlay.m_Coords.reserve((numPoints + 1) * 3); float cy; if (!isCircle) { // Start at the center point cy = std::max(water, cmpTerrain->GetExactGroundLevel(x, z)) + heightOffset; overlay.m_Coords.push_back(x); overlay.m_Coords.push_back(cy); overlay.m_Coords.push_back(z); } for (size_t i = 0; i <= numPoints; ++i) // use '<=' so it's a closed loop { float a = start + (float)i * (end - start) / (float)numPoints; float px = x + radius * cosf(a); float pz = z + radius * sinf(a); float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset; overlay.m_Coords.push_back(px); overlay.m_Coords.push_back(py); overlay.m_Coords.push_back(pz); } if (!isCircle) { // Return to the center point overlay.m_Coords.push_back(x); overlay.m_Coords.push_back(cy); overlay.m_Coords.push_back(z); } }
sEnvironmentSettings GetSettings() { sEnvironmentSettings s; CmpPtr<ICmpWaterManager> cmpWaterManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); ENSURE(cmpWaterManager); s.waterheight = cmpWaterManager->GetExactWaterLevel(0, 0) / (65536.f * HEIGHT_SCALE); WaterManager* wm = g_Renderer.GetWaterManager(); s.watertype = wm->m_WaterType; s.waterwaviness = wm->m_Waviness; s.watermurkiness = wm->m_Murkiness; s.windangle = wm->m_WindAngle; // CColor colors #define COLOR(A, B) A = Color((int)(B.r*255), (int)(B.g*255), (int)(B.b*255)) COLOR(s.watercolor, wm->m_WaterColor); COLOR(s.watertint, wm->m_WaterTint); #undef COLOR float sunrotation = g_LightEnv.GetRotation(); if (sunrotation > (float)M_PI) sunrotation -= (float)M_PI*2; s.sunrotation = sunrotation; s.sunelevation = g_LightEnv.GetElevation(); s.posteffect = g_Renderer.GetPostprocManager().GetPostEffect(); s.skyset = g_Renderer.GetSkyManager()->GetSkySet(); s.fogfactor = g_LightEnv.m_FogFactor; s.fogmax = g_LightEnv.m_FogMax; s.brightness = g_LightEnv.m_Brightness; s.contrast = g_LightEnv.m_Contrast; s.saturation = g_LightEnv.m_Saturation; s.bloom = g_LightEnv.m_Bloom; // RGBColor (CVector3D) colors #define COLOR(A, B) A = Color((int)(B.X*255), (int)(B.Y*255), (int)(B.Z*255)) s.sunoverbrightness = MaxComponent(g_LightEnv.m_SunColor); // clamp color to [0..1] before packing into u8 triplet if(s.sunoverbrightness > 1.0f) g_LightEnv.m_SunColor *= 1.0/s.sunoverbrightness; // (there's no operator/=) // no component was above 1.0, so reset scale factor (don't want to darken) else s.sunoverbrightness = 1.0f; COLOR(s.suncolor, g_LightEnv.m_SunColor); COLOR(s.terraincolor, g_LightEnv.m_TerrainAmbientColor); COLOR(s.unitcolor, g_LightEnv.m_UnitsAmbientColor); COLOR(s.fogcolor, g_LightEnv.m_FogColor); #undef COLOR return s; }
void SetSettings(const sEnvironmentSettings& s) { CmpPtr<ICmpWaterManager> cmpWaterManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); ENSURE(cmpWaterManager); cmpWaterManager->SetWaterLevel(entity_pos_t::FromFloat(s.waterheight * (65536.f * HEIGHT_SCALE))); WaterManager* wm = g_Renderer.GetWaterManager(); wm->m_Waviness = s.waterwaviness; wm->m_Murkiness = s.watermurkiness; wm->m_WindAngle = s.windangle; if (wm->m_WaterType != *s.watertype) { wm->m_WaterType = *s.watertype; wm->ReloadWaterNormalTextures(); } #define COLOR(A, B) B = CColor(A->r/255.f, A->g/255.f, A->b/255.f, 1.f) COLOR(s.watercolor, wm->m_WaterColor); COLOR(s.watertint, wm->m_WaterTint); #undef COLOR g_LightEnv.SetRotation(s.sunrotation); g_LightEnv.SetElevation(s.sunelevation); CStrW posteffect = *s.posteffect; if (posteffect.length() == 0) posteffect = L"default"; g_Renderer.GetPostprocManager().SetPostEffect(posteffect); CStrW skySet = *s.skyset; if (skySet.length() == 0) skySet = L"default"; g_Renderer.GetSkyManager()->SetSkySet(skySet); g_LightEnv.m_FogFactor = s.fogfactor; g_LightEnv.m_FogMax = s.fogmax; g_LightEnv.m_Brightness = s.brightness; g_LightEnv.m_Contrast = s.contrast; g_LightEnv.m_Saturation = s.saturation; g_LightEnv.m_Bloom = s.bloom; #define COLOR(A, B) B = RGBColor(A->r/255.f, A->g/255.f, A->b/255.f) COLOR(s.suncolor, g_LightEnv.m_SunColor); g_LightEnv.m_SunColor *= s.sunoverbrightness; COLOR(s.terraincolor, g_LightEnv.m_TerrainAmbientColor); COLOR(s.unitcolor, g_LightEnv.m_UnitsAmbientColor); COLOR(s.fogcolor, g_LightEnv.m_FogColor); #undef COLOR cmpWaterManager->RecomputeWaterData(); }
virtual CMatrix3D GetInterpolatedTransform(float frameOffset, bool forceFloating) { if (!m_InWorld) { LOGERROR(L"CCmpPosition::GetInterpolatedTransform called on entity when IsInWorld is false"); CMatrix3D m; m.SetIdentity(); return m; } float x, z, rotY; GetInterpolatedPosition2D(frameOffset, x, z, rotY); float baseY = 0; if (m_RelativeToGround) { CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); if (cmpTerrain) baseY = cmpTerrain->GetExactGroundLevel(x, z); if (m_Floating || forceFloating) { CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); if (cmpWaterManager) baseY = std::max(baseY, cmpWaterManager->GetExactWaterLevel(x, z)); } } float y = baseY + m_YOffset.ToFloat(); // TODO: do something with m_AnchorType CMatrix3D m; CMatrix3D mXZ; float Cos = cosf(rotY); float Sin = sinf(rotY); m.SetIdentity(); m._11 = -Cos; m._13 = -Sin; m._31 = Sin; m._33 = -Cos; mXZ.SetIdentity(); mXZ.SetXRotation(m_RotX.ToFloat()); mXZ.RotateZ(m_RotZ.ToFloat()); // TODO: is this all done in the correct order? mXZ = m * mXZ; mXZ.Translate(CVector3D(x, y, z)); return mXZ; }
void CCmpPathfinder::ComputeTerrainPassabilityGrid(const Grid<u16>& shoreGrid) { PROFILE3("terrain passability"); CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); CTerrain& terrain = GetSimContext().GetTerrain(); // Compute initial terrain-dependent passability for (int j = 0; j < m_MapSize * Pathfinding::NAVCELLS_PER_TILE; ++j) { for (int i = 0; i < m_MapSize * Pathfinding::NAVCELLS_PER_TILE; ++i) { // World-space coordinates for this navcell fixed x, z; Pathfinding::NavcellCenter(i, j, x, z); // Terrain-tile coordinates for this navcell int itile = i / Pathfinding::NAVCELLS_PER_TILE; int jtile = j / Pathfinding::NAVCELLS_PER_TILE; // Gather all the data potentially needed to determine passability: fixed height = terrain.GetExactGroundLevelFixed(x, z); fixed water; if (cmpWaterManager) water = cmpWaterManager->GetWaterLevel(x, z); fixed depth = water - height; //fixed slope = terrain.GetExactSlopeFixed(x, z); // Exact slopes give kind of weird output, so just use rough tile-based slopes fixed slope = terrain.GetSlopeFixed(itile, jtile); // Get world-space coordinates from shoreGrid (which uses terrain tiles) fixed shoredist = fixed::FromInt(shoreGrid.get(itile, jtile)).MultiplyClamp(TERRAIN_TILE_SIZE); // Compute the passability for every class for this cell: NavcellData t = 0; for (PathfinderPassability& passability : m_PassClasses) { if (!passability.IsPassable(depth, slope, shoredist)) t |= passability.m_Mask; } m_Grid->set(i, j, t); } } }
void CPatchRData::BuildSide(std::vector<SSideVertex>& vertices, CPatchSideFlags side) { ssize_t vsize = PATCH_SIZE + 1; CTerrain* terrain = m_Patch->m_Parent; CmpPtr<ICmpWaterManager> cmpWaterManager(*m_Simulation, SYSTEM_ENTITY); for (ssize_t k = 0; k < vsize; k++) { ssize_t gx = m_Patch->m_X * PATCH_SIZE; ssize_t gz = m_Patch->m_Z * PATCH_SIZE; switch (side) { case CPATCH_SIDE_NEGX: gz += k; break; case CPATCH_SIDE_POSX: gx += PATCH_SIZE; gz += PATCH_SIZE-k; break; case CPATCH_SIDE_NEGZ: gx += PATCH_SIZE-k; break; case CPATCH_SIDE_POSZ: gz += PATCH_SIZE; gx += k; break; } CVector3D pos; terrain->CalcPosition(gx, gz, pos); // Clamp the height to the water level float waterHeight = 0.f; if (cmpWaterManager) waterHeight = cmpWaterManager->GetExactWaterLevel(pos.X, pos.Z); pos.Y = std::max(pos.Y, waterHeight); SSideVertex v0, v1; v0.m_Position = pos; v1.m_Position = pos; v1.m_Position.Y = 0; // If this is the start of this tristrip, but we've already got a partial // tristrip, add a couple of degenerate triangles to join the strips properly if (k == 0 && !vertices.empty()) { vertices.push_back(vertices.back()); vertices.push_back(v1); } // Now add the new triangles vertices.push_back(v1); vertices.push_back(v0); } }
virtual entity_pos_t GetHeightFixed() { if (!m_RelativeToGround) return m_Y; // relative to the ground, so the fixed height = ground height + m_Y entity_pos_t baseY; CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity()); if (cmpTerrain) baseY = cmpTerrain->GetGroundLevel(m_X, m_Z); if (m_Floating) { CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity()); if (cmpWaterManager) baseY = std::max(baseY, cmpWaterManager->GetWaterLevel(m_X, m_Z)); } return m_Y + baseY; }
virtual entity_pos_t GetHeightOffset() { if (m_RelativeToGround) return m_Y; // not relative to the ground, so the height offset is m_Y - ground height entity_pos_t baseY; CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity()); if (cmpTerrain) baseY = cmpTerrain->GetGroundLevel(m_X, m_Z); if (m_Floating) { CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity()); if (cmpWaterManager) baseY = std::max(baseY, cmpWaterManager->GetWaterLevel(m_X, m_Z)); } return m_Y - baseY; }
void SimRender::ConstructSquareOnGround(const CSimContext& context, float x, float z, float w, float h, float a, SOverlayLine& overlay, bool floating, float heightOffset) { overlay.m_Coords.clear(); CmpPtr<ICmpTerrain> cmpTerrain(context, SYSTEM_ENTITY); if (!cmpTerrain) return; float water = 0.f; if (floating) { CmpPtr<ICmpWaterManager> cmpWaterManager(context, SYSTEM_ENTITY); if (cmpWaterManager) water = cmpWaterManager->GetExactWaterLevel(x, z); } float c = cosf(a); float s = sinf(a); std::vector<std::pair<float, float> > coords; // Add the first vertex, since SplitLine will be adding only the second end-point of the each line to // the coordinates list. We don't have to worry about the other lines, since the end-point of one line // will be the starting point of the next coords.emplace_back(x - w/2*c + h/2*s, z + w/2*s + h/2*c); SplitLine(coords, x - w/2*c + h/2*s, z + w/2*s + h/2*c, x - w/2*c - h/2*s, z + w/2*s - h/2*c); SplitLine(coords, x - w/2*c - h/2*s, z + w/2*s - h/2*c, x + w/2*c - h/2*s, z - w/2*s - h/2*c); SplitLine(coords, x + w/2*c - h/2*s, z - w/2*s - h/2*c, x + w/2*c + h/2*s, z - w/2*s + h/2*c); SplitLine(coords, x + w/2*c + h/2*s, z - w/2*s + h/2*c, x - w/2*c + h/2*s, z + w/2*s + h/2*c); overlay.m_Coords.reserve(coords.size() * 3); for (size_t i = 0; i < coords.size(); ++i) { float px = coords[i].first; float pz = coords[i].second; float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset; overlay.m_Coords.push_back(px); overlay.m_Coords.push_back(py); overlay.m_Coords.push_back(pz); } }
virtual CMatrix3D GetInterpolatedTransform(float frameOffset, bool forceFloating) { if (!m_InWorld) { LOGERROR(L"CCmpPosition::GetInterpolatedTransform called on entity when IsInWorld is false"); CMatrix3D m; m.SetIdentity(); return m; } float x, z, rotY; GetInterpolatedPosition2D(frameOffset, x, z, rotY); float baseY = 0; if (m_RelativeToGround) { CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); if (cmpTerrain) baseY = cmpTerrain->GetExactGroundLevel(x, z); if (m_Floating || forceFloating) { CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); if (cmpWaterManager) baseY = std::max(baseY, cmpWaterManager->GetExactWaterLevel(x, z)); } } float y = baseY + m_YOffset.ToFloat(); CMatrix3D m; // linear interpolation is good enough (for RotX/Z). // As you always stay close to zero angle. m.SetXRotation(Interpolate(m_LastInterpolatedRotX, m_InterpolatedRotX, frameOffset)); m.RotateZ(Interpolate(m_LastInterpolatedRotZ, m_InterpolatedRotZ, frameOffset)); m.RotateY(rotY + (float)M_PI); m.Translate(CVector3D(x, y, z)); return m; }
void GetInterpolatedPositions(CVector3D& pos0, CVector3D& pos1) { float baseY0 = 0; float baseY1 = 0; float x0 = m_LastX.ToFloat(); float z0 = m_LastZ.ToFloat(); float x1 = m_X.ToFloat(); float z1 = m_Z.ToFloat(); if (m_RelativeToGround) { CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); if (cmpTerrain) { baseY0 = cmpTerrain->GetExactGroundLevel(x0, z0); baseY1 = cmpTerrain->GetExactGroundLevel(x1, z1); } if (m_Floating || m_ActorFloating) { CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); if (cmpWaterManager) { baseY0 = std::max(baseY0, cmpWaterManager->GetExactWaterLevel(x0, z0)); baseY1 = std::max(baseY1, cmpWaterManager->GetExactWaterLevel(x1, z1)); } } } float y0 = baseY0 + m_Y.ToFloat() + m_LastYDifference.ToFloat(); float y1 = baseY1 + m_Y.ToFloat(); pos0 = CVector3D(x0, y0, z0); pos1 = CVector3D(x1, y1, z1); pos0.Y += GetConstructionProgressOffset(pos0); pos1.Y += GetConstructionProgressOffset(pos1); }
void CCmpPathfinder::TerrainUpdateHelper(bool expandPassability) { PROFILE3("TerrainUpdateHelper"); CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); CTerrain& terrain = GetSimContext().GetTerrain(); if (!cmpTerrain || !cmpObstructionManager) return; u16 terrainSize = cmpTerrain->GetTilesPerSide(); if (terrainSize == 0) return; if (!m_TerrainOnlyGrid || m_MapSize != terrainSize) { m_MapSize = terrainSize; SAFE_DELETE(m_TerrainOnlyGrid); m_TerrainOnlyGrid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE); } Grid<u16> shoreGrid = ComputeShoreGrid(); // Compute initial terrain-dependent passability for (int j = 0; j < m_MapSize * Pathfinding::NAVCELLS_PER_TILE; ++j) { for (int i = 0; i < m_MapSize * Pathfinding::NAVCELLS_PER_TILE; ++i) { // World-space coordinates for this navcell fixed x, z; Pathfinding::NavcellCenter(i, j, x, z); // Terrain-tile coordinates for this navcell int itile = i / Pathfinding::NAVCELLS_PER_TILE; int jtile = j / Pathfinding::NAVCELLS_PER_TILE; // Gather all the data potentially needed to determine passability: fixed height = terrain.GetExactGroundLevelFixed(x, z); fixed water; if (cmpWaterManager) water = cmpWaterManager->GetWaterLevel(x, z); fixed depth = water - height; // Exact slopes give kind of weird output, so just use rough tile-based slopes fixed slope = terrain.GetSlopeFixed(itile, jtile); // Get world-space coordinates from shoreGrid (which uses terrain tiles) fixed shoredist = fixed::FromInt(shoreGrid.get(itile, jtile)).MultiplyClamp(TERRAIN_TILE_SIZE); // Compute the passability for every class for this cell NavcellData t = 0; for (PathfinderPassability& passability : m_PassClasses) if (!passability.IsPassable(depth, slope, shoredist)) t |= passability.m_Mask; m_TerrainOnlyGrid->set(i, j, t); } } // Compute off-world passability // WARNING: CCmpRangeManager::LosIsOffWorld needs to be kept in sync with this const int edgeSize = 3 * Pathfinding::NAVCELLS_PER_TILE; // number of tiles around the edge that will be off-world NavcellData edgeMask = 0; for (PathfinderPassability& passability : m_PassClasses) edgeMask |= passability.m_Mask; int w = m_TerrainOnlyGrid->m_W; int h = m_TerrainOnlyGrid->m_H; if (cmpObstructionManager->GetPassabilityCircular()) { for (int j = 0; j < h; ++j) { for (int i = 0; i < w; ++i) { // Based on CCmpRangeManager::LosIsOffWorld // but tweaked since it's tile-based instead. // (We double all the values so we can handle half-tile coordinates.) // This needs to be slightly tighter than the LOS circle, // else units might get themselves lost in the SoD around the edge. int dist2 = (i*2 + 1 - w)*(i*2 + 1 - w) + (j*2 + 1 - h)*(j*2 + 1 - h); if (dist2 >= (w - 2*edgeSize) * (h - 2*edgeSize)) m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask); } } } else { for (u16 j = 0; j < h; ++j) for (u16 i = 0; i < edgeSize; ++i) m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask); for (u16 j = 0; j < h; ++j) for (u16 i = w-edgeSize+1; i < w; ++i) m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask); for (u16 j = 0; j < edgeSize; ++j) for (u16 i = edgeSize; i < w-edgeSize+1; ++i) m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask); for (u16 j = h-edgeSize+1; j < h; ++j) for (u16 i = edgeSize; i < w-edgeSize+1; ++i) m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask); } if (!expandPassability) return; // Expand the impassability grid, for any class with non-zero clearance, // so that we can stop units getting too close to impassable navcells. // Note: It's not possible to perform this expansion once for all passabilities // with the same clearance, because the impassable cells are not necessarily the // same for all these passabilities. for (PathfinderPassability& passability : m_PassClasses) { if (passability.m_Clearance == fixed::Zero()) continue; int clearance = (passability.m_Clearance / Pathfinding::NAVCELL_SIZE).ToInt_RoundToInfinity(); ExpandImpassableCells(*m_TerrainOnlyGrid, clearance, passability.m_Mask); } }
virtual CMatrix3D GetInterpolatedTransform(float frameOffset) { if (m_TurretParent != INVALID_ENTITY) { CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent); if (!cmpPosition) { LOGERROR("Turret with parent without position component"); CMatrix3D m; m.SetIdentity(); return m; } if (!cmpPosition->IsInWorld()) { LOGERROR("CCmpPosition::GetInterpolatedTransform called on turret entity when IsInWorld is false"); CMatrix3D m; m.SetIdentity(); return m; } else { CMatrix3D parentTransformMatrix = cmpPosition->GetInterpolatedTransform(frameOffset); CMatrix3D ownTransformation = CMatrix3D(); ownTransformation.SetYRotation(m_InterpolatedRotY); ownTransformation.Translate(-m_TurretPosition.X.ToFloat(), m_TurretPosition.Y.ToFloat(), -m_TurretPosition.Z.ToFloat()); return parentTransformMatrix * ownTransformation; } } if (!m_InWorld) { LOGERROR("CCmpPosition::GetInterpolatedTransform called on entity when IsInWorld is false"); CMatrix3D m; m.SetIdentity(); return m; } float x, z, rotY; GetInterpolatedPosition2D(frameOffset, x, z, rotY); float baseY = 0; if (m_RelativeToGround) { CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity()); if (cmpTerrain) baseY = cmpTerrain->GetExactGroundLevel(x, z); if (m_Floating || m_ActorFloating) { CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity()); if (cmpWaterManager) baseY = std::max(baseY, cmpWaterManager->GetExactWaterLevel(x, z)); } } float y = baseY + m_Y.ToFloat() + Interpolate(-1 * m_LastYDifference.ToFloat(), 0.f, frameOffset); CMatrix3D m; // linear interpolation is good enough (for RotX/Z). // As you always stay close to zero angle. m.SetXRotation(Interpolate(m_LastInterpolatedRotX, m_InterpolatedRotX, frameOffset)); m.RotateZ(Interpolate(m_LastInterpolatedRotZ, m_InterpolatedRotZ, frameOffset)); CVector3D pos(x, y, z); pos.Y += GetConstructionProgressOffset(pos); m.RotateY(rotY + (float)M_PI); m.Translate(pos); return m; }
void CCmpPathfinder::UpdateGrid() { CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); if (!cmpTerrain) return; // error // If the terrain was resized then delete the old grid data if (m_Grid && m_MapSize != cmpTerrain->GetTilesPerSide()) { SAFE_DELETE(m_Grid); SAFE_DELETE(m_ObstructionGrid); m_TerrainDirty = true; } // Initialise the terrain data when first needed if (!m_Grid) { m_MapSize = cmpTerrain->GetTilesPerSide(); m_Grid = new Grid<TerrainTile>(m_MapSize, m_MapSize); m_ObstructionGrid = new Grid<u8>(m_MapSize, m_MapSize); } CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); bool obstructionsDirty = cmpObstructionManager->Rasterise(*m_ObstructionGrid); if (obstructionsDirty && !m_TerrainDirty) { PROFILE("UpdateGrid obstructions"); // Obstructions changed - we need to recompute passability // Since terrain hasn't changed we only need to update the obstruction bits // and can skip the rest of the data // TODO: if ObstructionManager::SetPassabilityCircular was called at runtime // (which should probably never happen, but that's not guaranteed), // then TILE_OUTOFBOUNDS will change and we can't use this fast path, but // currently it'll just set obstructionsDirty and we won't notice for (u16 j = 0; j < m_MapSize; ++j) { for (u16 i = 0; i < m_MapSize; ++i) { TerrainTile& t = m_Grid->get(i, j); u8 obstruct = m_ObstructionGrid->get(i, j); if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING) t |= 1; else t &= (TerrainTile)~1; if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION) t |= 2; else t &= (TerrainTile)~2; } } ++m_Grid->m_DirtyID; } else if (obstructionsDirty || m_TerrainDirty) { PROFILE("UpdateGrid full"); // Obstructions or terrain changed - we need to recompute passability // TODO: only bother recomputing the region that has actually changed CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); // TOOD: these bits should come from ICmpTerrain CTerrain& terrain = GetSimContext().GetTerrain(); // avoid integer overflow in intermediate calculation const u16 shoreMax = 32767; // First pass - find underwater tiles Grid<bool> waterGrid(m_MapSize, m_MapSize); for (u16 j = 0; j < m_MapSize; ++j) { for (u16 i = 0; i < m_MapSize; ++i) { fixed x, z; TileCenter(i, j, x, z); bool underWater = cmpWaterManager && (cmpWaterManager->GetWaterLevel(x, z) > terrain.GetExactGroundLevelFixed(x, z)); waterGrid.set(i, j, underWater); } } // Second pass - find shore tiles Grid<u16> shoreGrid(m_MapSize, m_MapSize); for (u16 j = 0; j < m_MapSize; ++j) { for (u16 i = 0; i < m_MapSize; ++i) { // Find a land tile if (!waterGrid.get(i, j)) { if ((i > 0 && waterGrid.get(i-1, j)) || (i > 0 && j < m_MapSize-1 && waterGrid.get(i-1, j+1)) || (i > 0 && j > 0 && waterGrid.get(i-1, j-1)) || (i < m_MapSize-1 && waterGrid.get(i+1, j)) || (i < m_MapSize-1 && j < m_MapSize-1 && waterGrid.get(i+1, j+1)) || (i < m_MapSize-1 && j > 0 && waterGrid.get(i+1, j-1)) || (j > 0 && waterGrid.get(i, j-1)) || (j < m_MapSize-1 && waterGrid.get(i, j+1)) ) { // If it's bordered by water, it's a shore tile shoreGrid.set(i, j, 0); } else { shoreGrid.set(i, j, shoreMax); } } } } // Expand influences on land to find shore distance for (u16 y = 0; y < m_MapSize; ++y) { u16 min = shoreMax; for (u16 x = 0; x < m_MapSize; ++x) { if (!waterGrid.get(x, y)) { u16 g = shoreGrid.get(x, y); if (g > min) shoreGrid.set(x, y, min); else if (g < min) min = g; ++min; } } for (u16 x = m_MapSize; x > 0; --x) { if (!waterGrid.get(x-1, y)) { u16 g = shoreGrid.get(x-1, y); if (g > min) shoreGrid.set(x-1, y, min); else if (g < min) min = g; ++min; } } } for (u16 x = 0; x < m_MapSize; ++x) { u16 min = shoreMax; for (u16 y = 0; y < m_MapSize; ++y) { if (!waterGrid.get(x, y)) { u16 g = shoreGrid.get(x, y); if (g > min) shoreGrid.set(x, y, min); else if (g < min) min = g; ++min; } } for (u16 y = m_MapSize; y > 0; --y) { if (!waterGrid.get(x, y-1)) { u16 g = shoreGrid.get(x, y-1); if (g > min) shoreGrid.set(x, y-1, min); else if (g < min) min = g; ++min; } } } // Apply passability classes to terrain for (u16 j = 0; j < m_MapSize; ++j) { for (u16 i = 0; i < m_MapSize; ++i) { fixed x, z; TileCenter(i, j, x, z); TerrainTile t = 0; u8 obstruct = m_ObstructionGrid->get(i, j); fixed height = terrain.GetExactGroundLevelFixed(x, z); fixed water; if (cmpWaterManager) water = cmpWaterManager->GetWaterLevel(x, z); fixed depth = water - height; fixed slope = terrain.GetSlopeFixed(i, j); fixed shoredist = fixed::FromInt(shoreGrid.get(i, j)); if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING) t |= 1; if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION) t |= 2; if (obstruct & ICmpObstructionManager::TILE_OUTOFBOUNDS) { // If out of bounds, nobody is allowed to pass for (size_t n = 0; n < m_PassClasses.size(); ++n) t |= m_PassClasses[n].m_Mask; } else { for (size_t n = 0; n < m_PassClasses.size(); ++n) { if (!m_PassClasses[n].IsPassable(depth, slope, shoredist)) t |= m_PassClasses[n].m_Mask; } } std::string moveClass = terrain.GetMovementClass(i, j); if (m_TerrainCostClassTags.find(moveClass) != m_TerrainCostClassTags.end()) t |= COST_CLASS_MASK(m_TerrainCostClassTags[moveClass]); m_Grid->set(i, j, t); } } m_TerrainDirty = false; ++m_Grid->m_DirtyID; } }
void CDecalRData::BuildArrays() { PROFILE("decal build"); const SDecal& decal = m_Decal->m_Decal; // TODO: Currently this constructs an axis-aligned bounding rectangle around // the decal. It would be more efficient for rendering if we excluded tiles // that are outside the (non-axis-aligned) decal rectangle. ssize_t i0, j0, i1, j1; m_Decal->CalcVertexExtents(i0, j0, i1, j1); // Construct vertex data arrays CmpPtr<ICmpWaterManager> cmpWaterManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); m_Array.SetNumVertices((i1-i0+1)*(j1-j0+1)); m_Array.Layout(); VertexArrayIterator<CVector3D> Position = m_Position.GetIterator<CVector3D>(); VertexArrayIterator<SColor4ub> DiffuseColor = m_DiffuseColor.GetIterator<SColor4ub>(); VertexArrayIterator<float[2]> UV = m_UV.GetIterator<float[2]>(); const CLightEnv& lightEnv = g_Renderer.GetLightEnv(); bool cpuLighting = (g_Renderer.GetRenderPath() == CRenderer::RP_FIXED); for (ssize_t j = j0; j <= j1; ++j) { for (ssize_t i = i0; i <= i1; ++i) { CVector3D pos; m_Decal->m_Terrain->CalcPosition(i, j, pos); if (decal.m_Floating && cmpWaterManager) pos.Y = std::max(pos.Y, cmpWaterManager->GetExactWaterLevel(pos.X, pos.Z)); *Position = pos; ++Position; CVector3D normal; m_Decal->m_Terrain->CalcNormal(i, j, normal); *DiffuseColor = cpuLighting ? lightEnv.EvaluateTerrainDiffuseScaled(normal) : lightEnv.EvaluateTerrainDiffuseFactor(normal); ++DiffuseColor; // Map from world space back into decal texture space CVector3D inv = m_Decal->GetInvTransform().Transform(pos); (*UV)[0] = 0.5f + (inv.X - decal.m_OffsetX) / decal.m_SizeX; (*UV)[1] = 0.5f - (inv.Z - decal.m_OffsetZ) / decal.m_SizeZ; // flip V to match our texture convention ++UV; } } m_Array.Upload(); m_Array.FreeBackingStore(); // Construct index arrays for each terrain tile m_IndexArray.SetNumVertices((i1-i0)*(j1-j0)*6); m_IndexArray.Layout(); VertexArrayIterator<u16> Index = m_IndexArray.GetIterator(); u16 base = 0; ssize_t w = i1-i0+1; for (ssize_t dj = 0; dj < j1-j0; ++dj) { for (ssize_t di = 0; di < i1-i0; ++di) { bool dir = m_Decal->m_Terrain->GetTriangulationDir(i0+di, j0+dj); if (dir) { *Index++ = u16(((dj+0)*w+(di+0))+base); *Index++ = u16(((dj+0)*w+(di+1))+base); *Index++ = u16(((dj+1)*w+(di+0))+base); *Index++ = u16(((dj+0)*w+(di+1))+base); *Index++ = u16(((dj+1)*w+(di+1))+base); *Index++ = u16(((dj+1)*w+(di+0))+base); } else { *Index++ = u16(((dj+0)*w+(di+0))+base); *Index++ = u16(((dj+0)*w+(di+1))+base); *Index++ = u16(((dj+1)*w+(di+1))+base); *Index++ = u16(((dj+1)*w+(di+1))+base); *Index++ = u16(((dj+1)*w+(di+0))+base); *Index++ = u16(((dj+0)*w+(di+0))+base); } } } m_IndexArray.Upload(); m_IndexArray.FreeBackingStore(); }
/////////////////////////////////////////////////////////////////// // 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]); }
void CMapWriter::WriteXML(const VfsPath& filename, WaterManager* pWaterMan, SkyManager* pSkyMan, CLightEnv* pLightEnv, CCamera* pCamera, CCinemaManager* pCinema, CPostprocManager* pPostproc, CSimulation2* pSimulation2) { XML_Start(); { XML_Element("Scenario"); XML_Attribute("version", (int)FILE_VERSION); ENSURE(pSimulation2); CSimulation2& sim = *pSimulation2; if (!sim.GetStartupScript().empty()) { XML_Element("Script"); XML_CDATA(sim.GetStartupScript().c_str()); } { XML_Element("Environment"); XML_Setting("SkySet", pSkyMan->GetSkySet()); { XML_Element("SunColor"); XML_Attribute("r", pLightEnv->m_SunColor.X); // yes, it's X/Y/Z... XML_Attribute("g", pLightEnv->m_SunColor.Y); XML_Attribute("b", pLightEnv->m_SunColor.Z); } { XML_Element("SunElevation"); XML_Attribute("angle", pLightEnv->m_Elevation); } { XML_Element("SunRotation"); XML_Attribute("angle", pLightEnv->m_Rotation); } { XML_Element("TerrainAmbientColor"); XML_Attribute("r", pLightEnv->m_TerrainAmbientColor.X); XML_Attribute("g", pLightEnv->m_TerrainAmbientColor.Y); XML_Attribute("b", pLightEnv->m_TerrainAmbientColor.Z); } { XML_Element("UnitsAmbientColor"); XML_Attribute("r", pLightEnv->m_UnitsAmbientColor.X); XML_Attribute("g", pLightEnv->m_UnitsAmbientColor.Y); XML_Attribute("b", pLightEnv->m_UnitsAmbientColor.Z); } { XML_Element("Fog"); XML_Setting("FogFactor", pLightEnv->m_FogFactor); XML_Setting("FogThickness", pLightEnv->m_FogMax); { XML_Element("FogColor"); XML_Attribute("r", pLightEnv->m_FogColor.X); XML_Attribute("g", pLightEnv->m_FogColor.Y); XML_Attribute("b", pLightEnv->m_FogColor.Z); } } { XML_Element("Water"); { XML_Element("WaterBody"); CmpPtr<ICmpWaterManager> cmpWaterManager(sim, SYSTEM_ENTITY); ENSURE(cmpWaterManager); XML_Setting("Type", pWaterMan->m_WaterType); { XML_Element("Color"); XML_Attribute("r", pWaterMan->m_WaterColor.r); XML_Attribute("g", pWaterMan->m_WaterColor.g); XML_Attribute("b", pWaterMan->m_WaterColor.b); } { XML_Element("Tint"); XML_Attribute("r", pWaterMan->m_WaterTint.r); XML_Attribute("g", pWaterMan->m_WaterTint.g); XML_Attribute("b", pWaterMan->m_WaterTint.b); } XML_Setting("Height", cmpWaterManager->GetExactWaterLevel(0, 0)); XML_Setting("Waviness", pWaterMan->m_Waviness); XML_Setting("Murkiness", pWaterMan->m_Murkiness); XML_Setting("WindAngle", pWaterMan->m_WindAngle); } } { XML_Element("Postproc"); { XML_Setting("Brightness", pLightEnv->m_Brightness); XML_Setting("Contrast", pLightEnv->m_Contrast); XML_Setting("Saturation", pLightEnv->m_Saturation); XML_Setting("Bloom", pLightEnv->m_Bloom); XML_Setting("PostEffect", pPostproc->GetPostEffect()); } } } { XML_Element("Camera"); { XML_Element("Position"); CVector3D pos = pCamera->m_Orientation.GetTranslation(); XML_Attribute("x", pos.X); XML_Attribute("y", pos.Y); XML_Attribute("z", pos.Z); } CVector3D in = pCamera->m_Orientation.GetIn(); // Convert to spherical coordinates float rotation = atan2(in.X, in.Z); float declination = atan2(sqrt(in.X*in.X + in.Z*in.Z), in.Y) - (float)M_PI/2; { XML_Element("Rotation"); XML_Attribute("angle", rotation); } { XML_Element("Declination"); XML_Attribute("angle", declination); } } { std::string settings = sim.GetMapSettingsString(); if (!settings.empty()) { XML_Element("ScriptSettings"); XML_CDATA(("\n" + settings + "\n").c_str()); } } { XML_Element("Entities"); CmpPtr<ICmpTemplateManager> cmpTemplateManager(sim, SYSTEM_ENTITY); ENSURE(cmpTemplateManager); // This will probably need to be changed in the future, but for now we'll // just save all entities that have a position CSimulation2::InterfaceList ents = sim.GetEntitiesWithInterface(IID_Position); for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) { entity_id_t ent = it->first; // Don't save local entities (placement previews etc) if (ENTITY_IS_LOCAL(ent)) continue; XML_Element("Entity"); XML_Attribute("uid", ent); XML_Setting("Template", cmpTemplateManager->GetCurrentTemplateName(ent)); CmpPtr<ICmpOwnership> cmpOwnership(sim, ent); if (cmpOwnership) XML_Setting("Player", (int)cmpOwnership->GetOwner()); CmpPtr<ICmpPosition> cmpPosition(sim, ent); if (cmpPosition) { CFixedVector3D pos; if (cmpPosition->IsInWorld()) pos = cmpPosition->GetPosition(); CFixedVector3D rot = cmpPosition->GetRotation(); { XML_Element("Position"); XML_Attribute("x", pos.X); XML_Attribute("z", pos.Z); // TODO: height offset etc } { XML_Element("Orientation"); XML_Attribute("y", rot.Y); // TODO: X, Z maybe } } CmpPtr<ICmpObstruction> cmpObstruction(sim, ent); if (cmpObstruction) { // TODO: Currently only necessary because Atlas // does not set up control groups for its walls. cmpObstruction->ResolveFoundationCollisions(); entity_id_t group = cmpObstruction->GetControlGroup(); entity_id_t group2 = cmpObstruction->GetControlGroup2(); // Don't waste space writing the default control groups. if (group != ent || group2 != INVALID_ENTITY) { XML_Element("Obstruction"); if (group != ent) XML_Attribute("group", group); if (group2 != INVALID_ENTITY) XML_Attribute("group2", group2); } } CmpPtr<ICmpVisual> cmpVisual(sim, ent); if (cmpVisual) { u32 seed = cmpVisual->GetActorSeed(); if (seed != (u32)ent) { XML_Element("Actor"); XML_Attribute("seed", seed); } // TODO: variation/selection strings } } } const std::map<CStrW, CCinemaPath>& paths = pCinema->GetAllPaths(); std::map<CStrW, CCinemaPath>::const_iterator it = paths.begin(); { XML_Element("Paths"); for ( ; it != paths.end(); ++it ) { fixed timescale = it->second.GetTimescale(); const std::vector<SplineData>& nodes = it->second.GetAllNodes(); const std::vector<SplineData>& target_nodes = it->second.GetTargetSpline().GetAllNodes(); const CCinemaData* data = it->second.GetData(); XML_Element("Path"); XML_Attribute("name", data->m_Name); XML_Attribute("timescale", timescale); XML_Attribute("orientation", data->m_Orientation); XML_Attribute("mode", data->m_Mode); XML_Attribute("style", data->m_Style); fixed last_target = fixed::Zero(); for (size_t i = 0, j = 0; i < nodes.size(); ++i) { XML_Element("Node"); fixed distance = i > 0 ? nodes[i - 1].Distance : fixed::Zero(); last_target += distance; XML_Attribute("deltatime", distance); { XML_Element("Position"); XML_Attribute("x", nodes[i].Position.X); XML_Attribute("y", nodes[i].Position.Y); XML_Attribute("z", nodes[i].Position.Z); } { XML_Element("Rotation"); XML_Attribute("x", nodes[i].Rotation.X); XML_Attribute("y", nodes[i].Rotation.Y); XML_Attribute("z", nodes[i].Rotation.Z); } if (j >= target_nodes.size()) continue; fixed target_distance = j > 0 ? target_nodes[j - 1].Distance : fixed::Zero(); if (target_distance > last_target) continue; { XML_Element("Target"); XML_Attribute("x", target_nodes[j].Position.X); XML_Attribute("y", target_nodes[j].Position.Y); XML_Attribute("z", target_nodes[j].Position.Z); } last_target = fixed::Zero(); ++j; } } } } if (!XML_StoreVFS(g_VFS, filename)) LOGERROR("Failed to write map '%s'", filename.string8()); }
// 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]); }
Grid<u16> CCmpPathfinder::ComputeShoreGrid(bool expandOnWater) { PROFILE3("ComputeShoreGrid"); CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity()); // TODO: these bits should come from ICmpTerrain CTerrain& terrain = GetSimContext().GetTerrain(); // avoid integer overflow in intermediate calculation const u16 shoreMax = 32767; // First pass - find underwater tiles Grid<u8> waterGrid(m_MapSize, m_MapSize); for (u16 j = 0; j < m_MapSize; ++j) { for (u16 i = 0; i < m_MapSize; ++i) { fixed x, z; Pathfinding::TileCenter(i, j, x, z); bool underWater = cmpWaterManager && (cmpWaterManager->GetWaterLevel(x, z) > terrain.GetExactGroundLevelFixed(x, z)); waterGrid.set(i, j, underWater ? 1 : 0); } } // Second pass - find shore tiles Grid<u16> shoreGrid(m_MapSize, m_MapSize); for (u16 j = 0; j < m_MapSize; ++j) { for (u16 i = 0; i < m_MapSize; ++i) { // Find a land tile if (!waterGrid.get(i, j)) { // If it's bordered by water, it's a shore tile if ((i > 0 && waterGrid.get(i-1, j)) || (i > 0 && j < m_MapSize-1 && waterGrid.get(i-1, j+1)) || (i > 0 && j > 0 && waterGrid.get(i-1, j-1)) || (i < m_MapSize-1 && waterGrid.get(i+1, j)) || (i < m_MapSize-1 && j < m_MapSize-1 && waterGrid.get(i+1, j+1)) || (i < m_MapSize-1 && j > 0 && waterGrid.get(i+1, j-1)) || (j > 0 && waterGrid.get(i, j-1)) || (j < m_MapSize-1 && waterGrid.get(i, j+1)) ) shoreGrid.set(i, j, 0); else shoreGrid.set(i, j, shoreMax); } // If we want to expand on water, we want water tiles not to be shore tiles else if (expandOnWater) shoreGrid.set(i, j, shoreMax); } } // Expand influences on land to find shore distance for (u16 y = 0; y < m_MapSize; ++y) { u16 min = shoreMax; for (u16 x = 0; x < m_MapSize; ++x) { if (!waterGrid.get(x, y) || expandOnWater) { u16 g = shoreGrid.get(x, y); if (g > min) shoreGrid.set(x, y, min); else if (g < min) min = g; ++min; } } for (u16 x = m_MapSize; x > 0; --x) { if (!waterGrid.get(x-1, y) || expandOnWater) { u16 g = shoreGrid.get(x-1, y); if (g > min) shoreGrid.set(x-1, y, min); else if (g < min) min = g; ++min; } } } for (u16 x = 0; x < m_MapSize; ++x) { u16 min = shoreMax; for (u16 y = 0; y < m_MapSize; ++y) { if (!waterGrid.get(x, y) || expandOnWater) { u16 g = shoreGrid.get(x, y); if (g > min) shoreGrid.set(x, y, min); else if (g < min) min = g; ++min; } } for (u16 y = m_MapSize; y > 0; --y) { if (!waterGrid.get(x, y-1) || expandOnWater) { u16 g = shoreGrid.get(x, y-1); if (g > min) shoreGrid.set(x, y-1, min); else if (g < min) min = g; ++min; } } } return shoreGrid; }
void CTexturedLineRData::Update(const SOverlayTexturedLine& line) { if (m_VB) { g_VBMan.Release(m_VB); m_VB = NULL; } if (m_VBIndices) { g_VBMan.Release(m_VBIndices); m_VBIndices = NULL; } if (!line.m_SimContext) { debug_warn(L"[TexturedLineRData] No SimContext set for textured overlay line, cannot render (no terrain data)"); return; } const CTerrain& terrain = line.m_SimContext->GetTerrain(); CmpPtr<ICmpWaterManager> cmpWaterManager(*line.m_SimContext, SYSTEM_ENTITY); float v = 0.f; std::vector<SVertex> vertices; std::vector<u16> indices; size_t n = line.m_Coords.size() / 2; // number of line points bool closed = line.m_Closed; ENSURE(n >= 2); // minimum needed to avoid errors (also minimum value to make sense, can't draw a line between 1 point) // In each iteration, p1 is the position of vertex i, p0 is i-1, p2 is i+1. // To avoid slightly expensive terrain computations we cycle these around and // recompute p2 at the end of each iteration. CVector3D p0; CVector3D p1(line.m_Coords[0], 0, line.m_Coords[1]); CVector3D p2(line.m_Coords[(1 % n)*2], 0, line.m_Coords[(1 % n)*2+1]); if (closed) // grab the ending point so as to close the loop p0 = CVector3D(line.m_Coords[(n-1)*2], 0, line.m_Coords[(n-1)*2+1]); else // we don't want to loop around and use the direction towards the other end of the line, so create an artificial p0 that // extends the p2 -> p1 direction, and use that point instead p0 = p1 + (p1 - p2); bool p1floating = false; bool p2floating = false; // Compute terrain heights, clamped to the water height (and remember whether // each point was floating on water, for normal computation later) // TODO: if we ever support more than one water level per map, recompute this per point float w = cmpWaterManager->GetExactWaterLevel(p0.X, p0.Z); p0.Y = terrain.GetExactGroundLevel(p0.X, p0.Z); if (p0.Y < w) p0.Y = w; p1.Y = terrain.GetExactGroundLevel(p1.X, p1.Z); if (p1.Y < w) { p1.Y = w; p1floating = true; } p2.Y = terrain.GetExactGroundLevel(p2.X, p2.Z); if (p2.Y < w) { p2.Y = w; p2floating = true; } for (size_t i = 0; i < n; ++i) { // For vertex i, compute bisector of lines (i-1)..(i) and (i)..(i+1) // perpendicular to terrain normal // Normal is vertical if on water, else computed from terrain CVector3D norm; if (p1floating) norm = CVector3D(0, 1, 0); else norm = terrain.CalcExactNormal(p1.X, p1.Z); CVector3D b = ((p1 - p0).Normalized() + (p2 - p1).Normalized()).Cross(norm); // Adjust bisector length to match the line thickness, along the line's width float l = b.Dot((p2 - p1).Normalized().Cross(norm)); if (fabs(l) > 0.000001f) // avoid unlikely divide-by-zero b *= line.m_Thickness / l; // Push vertices and indices for each quad in GL_TRIANGLES order. The two triangles of each quad are indexed using // the winding orders (BR, BL, TR) and (TR, BL, TR) (where BR is bottom-right of this iteration's quad, TR top-right etc). SVertex vertex1(p1 + b + norm*OverlayRenderer::OVERLAY_VOFFSET, 0.f, v); SVertex vertex2(p1 - b + norm*OverlayRenderer::OVERLAY_VOFFSET, 1.f, v); vertices.push_back(vertex1); vertices.push_back(vertex2); u16 index1 = vertices.size() - 2; // index of vertex1 in this iteration (TR of this quad) u16 index2 = vertices.size() - 1; // index of the vertex2 in this iteration (TL of this quad) if (i == 0) { // initial two vertices to continue building triangles from (n must be >= 2 for this to work) indices.push_back(index1); indices.push_back(index2); } else { u16 index1Prev = vertices.size() - 4; // index of the vertex1 in the previous iteration (BR of this quad) u16 index2Prev = vertices.size() - 3; // index of the vertex2 in the previous iteration (BL of this quad) ENSURE(index1Prev < vertices.size()); ENSURE(index2Prev < vertices.size()); // Add two corner points from last iteration and join with one of our own corners to create triangle 1 // (don't need to do this if i == 1 because i == 0 are the first two ones, they don't need to be copied) if (i > 1) { indices.push_back(index1Prev); indices.push_back(index2Prev); } indices.push_back(index1); // complete triangle 1 // create triangle 2, specifying the adjacent side's vertices in the opposite order from triangle 1 indices.push_back(index1); indices.push_back(index2Prev); indices.push_back(index2); } // alternate V coordinate for debugging v = 1 - v; // cycle the p's and compute the new p2 p0 = p1; p1 = p2; p1floating = p2floating; // if in closed mode, wrap around the coordinate array for p2 -- otherwise, extend linearly if (!closed && i == n-2) // next iteration is the last point of the line, so create an artificial p2 that extends the p0 -> p1 direction p2 = p1 + (p1 - p0); else p2 = CVector3D(line.m_Coords[((i+2) % n)*2], 0, line.m_Coords[((i+2) % n)*2+1]); p2.Y = terrain.GetExactGroundLevel(p2.X, p2.Z); if (p2.Y < w) { p2.Y = w; p2floating = true; } else p2floating = false; } if (closed) { // close the path indices.push_back(vertices.size()-2); indices.push_back(vertices.size()-1); indices.push_back(0); indices.push_back(0); indices.push_back(vertices.size()-1); indices.push_back(1); } else { // Create start and end caps. On either end, this is done by taking the centroid between the last and second-to-last pair of // vertices that was generated along the path (i.e. the vertex1's and vertex2's from above), taking a directional vector // between them, and drawing the line cap in the plane given by the two butt-end corner points plus said vector. std::vector<u16> capIndices; std::vector<SVertex> capVertices; // create end cap CreateLineCap( line, // the order of these vertices is important here, swapping them produces caps at the wrong side vertices[vertices.size()-2].m_Position, // top-right vertex of last quad vertices[vertices.size()-1].m_Position, // top-left vertex of last quad // directional vector between centroids of last vertex pair and second-to-last vertex pair (Centroid(vertices[vertices.size()-2], vertices[vertices.size()-1]) - Centroid(vertices[vertices.size()-4], vertices[vertices.size()-3])).Normalized(), line.m_EndCapType, capVertices, capIndices ); for (unsigned i = 0; i < capIndices.size(); i++) capIndices[i] += vertices.size(); vertices.insert(vertices.end(), capVertices.begin(), capVertices.end()); indices.insert(indices.end(), capIndices.begin(), capIndices.end()); capIndices.clear(); capVertices.clear(); // create start cap CreateLineCap( line, // the order of these vertices is important here, swapping them produces caps at the wrong side vertices[1].m_Position, vertices[0].m_Position, // directional vector between centroids of first vertex pair and second vertex pair (Centroid(vertices[1], vertices[0]) - Centroid(vertices[3], vertices[2])).Normalized(), line.m_StartCapType, capVertices, capIndices ); for (unsigned i = 0; i < capIndices.size(); i++) capIndices[i] += vertices.size(); vertices.insert(vertices.end(), capVertices.begin(), capVertices.end()); indices.insert(indices.end(), capIndices.begin(), capIndices.end()); } ENSURE(indices.size() % 3 == 0); // GL_TRIANGLES indices, so must be multiple of 3 m_VB = g_VBMan.Allocate(sizeof(SVertex), vertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER); if (m_VB) // allocation might fail (e.g. due to too many vertices) { m_VB->m_Owner->UpdateChunkVertices(m_VB, &vertices[0]); // copy data into VBO for (size_t k = 0; k < indices.size(); ++k) indices[k] += m_VB->m_Index; m_VBIndices = g_VBMan.Allocate(sizeof(u16), indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER); if (m_VBIndices) m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices, &indices[0]); } }
/////////////////////////////////////////////////////////////////// // 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(); 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 = m_MapSize*m_MapSize; float halfSize = (m_MapSize/2.0); // Warning: this won't work with multiple water planes m_WaterHeight = cmpWaterManager->GetExactWaterLevel(0,0); // Get the square we want to work on. size_t Xstart = m_updatei0 >= m_MapSize ? m_MapSize-1 : m_updatei0; size_t Xend = m_updatei1 >= m_MapSize ? m_MapSize-1 : m_updatei1; size_t Zstart = m_updatej0 >= m_MapSize ? m_MapSize-1 : m_updatej0; size_t Zend = m_updatej1 >= m_MapSize ? m_MapSize-1 : m_updatej1; if (m_WaveX == NULL) { m_WaveX = new float[m_MapSize*m_MapSize]; m_WaveZ = new float[m_MapSize*m_MapSize]; m_DistanceToShore = new float[m_MapSize*m_MapSize]; m_FoamFactor = new float[m_MapSize*m_MapSize]; } u16* heightmap = terrain->GetHeightMap(); // some temporary stuff for wave intensity // not really used too much right now. //u8* waveForceHQ = new u8[mapSize*mapSize]; // 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[m_MapSize*m_MapSize]; // taken out of the bottom loop, blurs the normal map // To remove if below is reactivated size_t blurZstart = (int)(Zstart-4) < 0 ? 0 : Zstart - 4; size_t blurZend = Zend+4 >= m_MapSize ? m_MapSize-1 : Zend + 4; size_t blurXstart = (int)(Xstart-4) < 0 ? 0 : Xstart - 4; size_t blurXend = Xend+4 >= m_MapSize ? m_MapSize-1 : Xend + 4; float ii = blurXstart*4.0f, jj = blurXend*4.0f; for (size_t j = blurZstart; j < blurZend; ++j, jj += 4.0f) { for (size_t i = blurXstart; i < blurXend; ++i, ii += 4.0f) { normals[j*m_MapSize + i] = terrain->CalcExactNormal(ii,jj); } } // TODO: reactivate? /* // 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; } } */ // Cache some data to spiral-search for the closest tile that's either coastal or water depending on what we are. // this is insanely faster. // I use a define because it's more readable and C++11 doesn't like this otherwise #define m_MapSize (ssize_t)m_MapSize ssize_t offset[24] = { -1,1,-m_MapSize,+m_MapSize, -1-m_MapSize,+1-m_MapSize,-1+m_MapSize,1+m_MapSize, -2,2,-2*m_MapSize,2*m_MapSize,-2-m_MapSize,-2+m_MapSize,2-m_MapSize,2+m_MapSize, -1-2*m_MapSize,+1-2*m_MapSize,-1+2*m_MapSize,1+2*m_MapSize, -2-2*m_MapSize,2+2*m_MapSize,-2+2*m_MapSize,2-2*m_MapSize }; float dist[24] = { 1.0f, 1.0f, 1.0f, 1.0f, 1.414f, 1.414f, 1.414f, 1.414f, 2.0f, 2.0f, 2.0f, 2.0f, 2.236f, 2.236f, 2.236f, 2.236f, 2.236f, 2.236f, 2.236f, 2.236f, 2.828f, 2.828f, 2.828f, 2.828f }; #undef m_MapSize // this creates information for waves and stores it in float arrays. PatchRData then puts it in the vertex info for speed. CVector3D normal; for (size_t j = Zstart; j < Zend; ++j) { for (size_t i = Xstart; i < Xend; ++i) { ssize_t register index = j*m_MapSize + i; if (circular && (i-halfSize)*(i-halfSize)+(j-halfSize)*(j-halfSize) > mSize) { m_WaveX[index] = 0.0f; m_WaveZ[index] = 0.0f; m_DistanceToShore[index] = 100; m_FoamFactor[index] = 0.0f; continue; } float depth = m_WaterHeight - heightmap[index]*HEIGHT_SCALE; float register distanceToShore = 10000.0f; // calculation of the distance to the shore. if (i > 0 && i < m_MapSize-1 && j > 0 && j < m_MapSize-1) { // search a 5x5 array with us in the center (do not search me) // much faster since we spiral search and can just stop once we've found the shore. // also everything is precomputed and we get exact results instead. int max = 8; if (i > 1 && i < m_MapSize-2 && j > 1 && j < m_MapSize-2) max = 24; for(int lookupI = 0; lookupI < max;++lookupI) { float hereDepth = m_WaterHeight - heightmap[index+offset[lookupI]]*HEIGHT_SCALE; distanceToShore = hereDepth <= 0 && depth >= 0 ? dist[lookupI] : (depth < 0 ? 1 : distanceToShore); if (distanceToShore < 5000.0f) goto FoundShore; } } else { // revert to for and if-based because I can't be bothered to special case all that. for (int xx = -1; xx <= 1;++xx) for (int yy = -1; yy <= 1;++yy) { if ((int)(i+xx) >= 0 && i+xx < m_MapSize && (int)(j+yy) >= 0 && j+yy < m_MapSize) { float hereDepth = m_WaterHeight - heightmap[index+xx+yy*m_MapSize]*HEIGHT_SCALE; distanceToShore = (hereDepth < 0 && sqrt((double)xx*xx+yy*yy) < distanceToShore) ? sqrt((double)xx*xx+yy*yy) : distanceToShore; } } } // speedup with default values for land squares if (distanceToShore > 5000.0f) { m_WaveX[index] = 0.0f; m_WaveZ[index] = 0.0f; m_DistanceToShore[index] = 100.0f; m_FoamFactor[index] = 0.0f; continue; } FoundShore: // 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. normal.X = normal.Y = normal.Z = 0.0f; int waterRaise = 0; for (size_t yy = (int(j-3) < 0 ? 0 : j-3); yy <= (j+3 < m_MapSize-1 ? 0 : j-3); yy += 2) { for (size_t xx = (int(i-3) < 0 ? 0 : i-3); xx <= (i+3 < m_MapSize-1 ? 0 : i+3); xx += 2) // every 2 tile is good enough. { normal += normals[yy*m_MapSize + xx]; waterRaise += (heightmap[index]*HEIGHT_SCALE - heightmap[yy*m_MapSize + xx]) > 0 ? (heightmap[index]*HEIGHT_SCALE - heightmap[yy*m_MapSize + xx]) : 0; } } // normalizes the terrain info to avoid foam moving at too different speeds. normal *= 0.08f; // divide by about 11. normal[1] = 0.1f; normal = normal.Normalized(); m_WaveX[index] = normal[0]; m_WaveZ[index] = normal[2]; // distance is /5.0 to be a [0,1] value. m_DistanceToShore[index] = distanceToShore; // 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*m_MapSize+i]/255.0f)*/ * (m_Waviness/8.0f); foamAmount += clamp(m_Waviness/2.0f,0.0f,m_Waviness/2.0f)/(m_Waviness/2.0f) * clamp(m_Waviness/9.0f,0.3f,1.0f); foamAmount *= (m_Waviness/4.0f - distanceToShore); foamAmount = foamAmount > 1.0f ? 1.0f: (foamAmount < 0.0f ? 0.0f : foamAmount); m_FoamFactor[index] = foamAmount; } } delete[] normals; //delete[] waveForceHQ; // TODO: reactivate this with something that looks good and is efficient. /* // 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 (size_t j = 0; j < m_MapSize/size; ++j) { for (size_t i = 0; i < m_MapSize/size; ++i) { 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 yy = 0; yy < size; ++yy) { for (int xx = 0; xx < size; ++xx) { 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 yy = -size/2; yy < size/2; ++yy) { for (int xx = -size/2; xx < size/2; ++xx) { 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(); GLushort index[4]; 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; index[0] = waves_vertex_data.size(); waves_vertex_data.push_back(vertex[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; index[1] = waves_vertex_data.size(); waves_vertex_data.push_back(vertex[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; index[3] = waves_vertex_data.size(); waves_vertex_data.push_back(vertex[3]); 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; index[2] = waves_vertex_data.size(); waves_vertex_data.push_back(vertex[2]); waves_indices.push_back(index[0]); waves_indices.push_back(index[1]); waves_indices.push_back(index[2]); waves_indices.push_back(index[2]); waves_indices.push_back(index[3]); waves_indices.push_back(index[0]); } // 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]); */ }