void SimRender::ConstructCircleOnGround(const CSimContext& context, float x, float z, float radius, SOverlayLine& overlay, bool floating) { overlay.m_Coords.clear(); CmpPtr<ICmpTerrain> cmpTerrain(context, SYSTEM_ENTITY); if (cmpTerrain.null()) return; float water = 0.f; if (floating) { CmpPtr<ICmpWaterManager> cmpWaterMan(context, SYSTEM_ENTITY); if (!cmpWaterMan.null()) water = cmpWaterMan->GetExactWaterLevel(x, z); } // Adapt the circle resolution to look reasonable for small and largeish radiuses size_t numPoints = clamp((size_t)(radius*4.0f), (size_t)12, (size_t)48); overlay.m_Coords.reserve((numPoints + 1) * 3); for (size_t i = 0; i <= numPoints; ++i) // use '<=' so it's a closed loop { float a = i * 2 * (float)M_PI / numPoints; float px = x + radius * sin(a); float pz = z + radius * cos(a); float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + RENDER_HEIGHT_DELTA; overlay.m_Coords.push_back(px); overlay.m_Coords.push_back(py); overlay.m_Coords.push_back(pz); } }
void SimRender::ConstructLineOnGround(const CSimContext& context, std::vector<float> xz, SOverlayLine& overlay, bool floating) { overlay.m_Coords.clear(); CmpPtr<ICmpTerrain> cmpTerrain(context, SYSTEM_ENTITY); if (cmpTerrain.null()) return; if (xz.size() < 2) return; float water = 0.f; if (floating) { CmpPtr<ICmpWaterManager> cmpWaterMan(context, SYSTEM_ENTITY); if (!cmpWaterMan.null()) water = cmpWaterMan->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)) + RENDER_HEIGHT_DELTA; overlay.m_Coords.push_back(px); overlay.m_Coords.push_back(py); overlay.m_Coords.push_back(pz); } }
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); } }
void CTerritoryTexture::RecomputeTexture(int unit) { // If the map was resized, delete and regenerate the texture if (m_Texture) { CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY); if (cmpTerrain && m_MapSize != (ssize_t)cmpTerrain->GetVerticesPerSide()) DeleteTexture(); } if (!m_Texture) ConstructTexture(unit); PROFILE("recompute territory texture"); std::vector<u8> bitmap; bitmap.resize(m_MapSize * m_MapSize * 4); CmpPtr<ICmpTerritoryManager> cmpTerritoryManager(m_Simulation, SYSTEM_ENTITY); if (!cmpTerritoryManager) return; const Grid<u8> territories = cmpTerritoryManager->GetTerritoryGrid(); GenerateBitmap(territories, &bitmap[0], m_MapSize, m_MapSize); g_Renderer.BindTexture(unit, m_Texture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize, m_MapSize, GL_RGBA, GL_UNSIGNED_BYTE, &bitmap[0]); }
CTerrain* CObjectManager::GetTerrain() { CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY); if (!cmpTerrain) return NULL; return cmpTerrain->GetCTerrain(); }
float GetConstructionProgressOffset(const CVector3D& pos) { if (m_ConstructionProgress.IsZero()) return 0.0f; CmpPtr<ICmpVisual> cmpVisual(GetEntityHandle()); if (!cmpVisual) return 0.0f; // We use selection boxes to calculate the model size, since the model could be offset // TODO: this annoyingly shows decals, would be nice to hide them CBoundingBoxOriented bounds = cmpVisual->GetSelectionBox(); if (bounds.IsEmpty()) return 0.0f; float dy = 2.0f * bounds.m_HalfSizes.Y; // If this is a floating unit, we want it to start all the way under the terrain, // so find the difference between its current position and the terrain CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity()); if (cmpTerrain && (m_Floating || m_ActorFloating)) { float ground = cmpTerrain->GetExactGroundLevel(pos.X, pos.Z); dy += std::max(0.f, pos.Y - ground); } return (m_ConstructionProgress.ToFloat() - 1.0f) * dy; }
virtual CFixedVector3D GetPreviousPosition() { if (!m_InWorld) { LOGERROR(L"CCmpPosition::GetPreviousPosition 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_PrevX, m_PrevZ); if (m_Floating) { CmpPtr<ICmpWaterManager> cmpWaterMan(GetSimContext(), SYSTEM_ENTITY); if (cmpWaterMan) baseY = std::max(baseY, cmpWaterMan->GetWaterLevel(m_PrevX, m_PrevZ)); } } return CFixedVector3D(m_PrevX, baseY + m_YOffset, m_PrevZ); }
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); } }
ActorViewer::ActorViewer() : m(*new ActorViewerImpl()) { m.WalkEnabled = false; m.GroundEnabled = true; m.WaterEnabled = false; m.ShadowsEnabled = g_Renderer.GetOptionBool(CRenderer::OPT_SHADOWS); m.SelectionBoxEnabled = false; m.AxesMarkerEnabled = false; m.PropPointsMode = 0; m.Background = SColor4ub(0, 0, 0, 255); // Create a tiny empty piece of terrain, just so we can put shadows // on it without having to think too hard m.Terrain.Initialize(2, NULL); CTerrainTextureEntry* tex = g_TexMan.FindTexture("whiteness"); if (tex) { for (ssize_t pi = 0; pi < m.Terrain.GetPatchesPerSide(); ++pi) { for (ssize_t pj = 0; pj < m.Terrain.GetPatchesPerSide(); ++pj) { CPatch* patch = m.Terrain.GetPatch(pi, pj); for (ssize_t i = 0; i < PATCH_SIZE; ++i) { for (ssize_t j = 0; j < PATCH_SIZE; ++j) { CMiniPatch& mp = patch->m_MiniPatches[i][j]; mp.Tex = tex; mp.Priority = 0; } } } } } else { debug_warn(L"Failed to load whiteness texture"); } // Start the simulation m.Simulation2.LoadDefaultScripts(); m.Simulation2.ResetState(); // Tell the simulation we've already loaded the terrain CmpPtr<ICmpTerrain> cmpTerrain(m.Simulation2, SYSTEM_ENTITY); if (cmpTerrain) cmpTerrain->ReloadTerrain(false); // Remove FOW since we're in Atlas CmpPtr<ICmpRangeManager> cmpRangeManager(m.Simulation2, SYSTEM_ENTITY); if (cmpRangeManager) cmpRangeManager->SetLosRevealAll(-1, true); }
void CTerritoryTexture::ConstructTexture(int unit) { CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY); if (!cmpTerrain) return; // Convert size from terrain tiles to territory tiles m_MapSize = cmpTerrain->GetTilesPerSide() * Pathfinding::NAVCELLS_PER_TILE / ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE; m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize); glGenTextures(1, &m_Texture); g_Renderer.BindTexture(unit, m_Texture); // Initialise texture with transparency, for the areas we don't // overwrite with glTexSubImage2D later u8* texData = new u8[m_TextureSize * m_TextureSize * 4]; memset(texData, 0x00, m_TextureSize * m_TextureSize * 4); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData); delete[] texData; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); { // Texture matrix: We want to map // world pos (0, y, 0) (i.e. bottom-left of first tile) // onto texcoord (0, 0) (i.e. bottom-left of first texel); // world pos (mapsize*cellsize, y, mapsize*cellsize) (i.e. top-right of last tile) // onto texcoord (mapsize / texsize, mapsize / texsize) (i.e. top-right of last texel) float s = 1.f / (float)(m_TextureSize * TERRAIN_TILE_SIZE); float t = 0.f; m_TextureMatrix.SetZero(); m_TextureMatrix._11 = s; m_TextureMatrix._23 = s; m_TextureMatrix._14 = t; m_TextureMatrix._24 = t; m_TextureMatrix._44 = 1; } { // Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize) float s = m_MapSize / (float)m_TextureSize; m_MinimapTextureMatrix.SetZero(); m_MinimapTextureMatrix._11 = s; m_MinimapTextureMatrix._22 = s; m_MinimapTextureMatrix._44 = 1; } }
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; }
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; }
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; }
void SimRender::ConstructSquareOnGround(const CSimContext& context, float x, float z, float w, float h, float a, SOverlayLine& overlay, bool floating) { overlay.m_Coords.clear(); CmpPtr<ICmpTerrain> cmpTerrain(context, SYSTEM_ENTITY); if (cmpTerrain.null()) return; float water = 0.f; if (floating) { CmpPtr<ICmpWaterManager> cmpWaterMan(context, SYSTEM_ENTITY); if (!cmpWaterMan.null()) water = cmpWaterMan->GetExactWaterLevel(x, z); } float c = cos(a); float s = sin(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.push_back(std::make_pair(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)) + RENDER_HEIGHT_DELTA; 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 CLOSTexture::RecomputeTexture(int unit) { // If the map was resized, delete and regenerate the texture if (m_Texture) { CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY); if (cmpTerrain && m_MapSize != (ssize_t)cmpTerrain->GetVerticesPerSide()) DeleteTexture(); } bool recreated = false; if (!m_Texture) { ConstructTexture(unit); recreated = true; } PROFILE("recompute LOS texture"); std::vector<u8> losData; losData.resize(GetBitmapSize(m_MapSize, m_MapSize)); CmpPtr<ICmpRangeManager> cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager) return; ICmpRangeManager::CLosQuerier los(cmpRangeManager->GetLosQuerier(g_Game->GetPlayerID())); GenerateBitmap(los, &losData[0], m_MapSize, m_MapSize); if (CRenderer::IsInitialised() && g_Renderer.m_Options.m_SmoothLOS && recreated) { g_Renderer.BindTexture(unit, m_TextureSmooth1); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize + g_BlurSize - 1, m_MapSize + g_BlurSize - 1, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]); g_Renderer.BindTexture(unit, m_TextureSmooth2); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize + g_BlurSize - 1, m_MapSize + g_BlurSize - 1, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]); } g_Renderer.BindTexture(unit, m_Texture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize + g_BlurSize - 1, m_MapSize + g_BlurSize - 1, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]); }
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); }
int CMapReader::ApplyTerrainData() { if (m_PatchesPerSide == 0) { // we'll probably crash when trying to use this map later throw PSERROR_Game_World_MapLoadFailed("Error loading map: no terrain data.\nCheck application log for details."); } if (!only_xml) { // initialise the terrain pTerrain->Initialize(m_PatchesPerSide, &m_Heightmap[0]); // setup the textures on the minipatches STileDesc* tileptr = &m_Tiles[0]; for (ssize_t j=0; j<m_PatchesPerSide; j++) { for (ssize_t i=0; i<m_PatchesPerSide; i++) { for (ssize_t m=0; m<PATCH_SIZE; m++) { for (ssize_t k=0; k<PATCH_SIZE; k++) { CMiniPatch& mp = pTerrain->GetPatch(i,j)->m_MiniPatches[m][k]; // can't fail mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index]; mp.Priority = tileptr->m_Priority; tileptr++; } } } } } CmpPtr<ICmpTerrain> cmpTerrain(*pSimContext, SYSTEM_ENTITY); if (cmpTerrain) cmpTerrain->ReloadTerrain(); return 0; }
void CCmpPathfinder::UpdateGrid() { PROFILE3("UpdateGrid"); CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); if (!cmpTerrain) return; // error if (!m_PreserveUpdateInformations) m_ObstructionsDirty.Clean(); else m_PreserveUpdateInformations = false; // Next time will be a regular update // 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_TerrainOnlyGrid); } // Initialise the terrain data when first needed if (!m_Grid) { m_MapSize = cmpTerrain->GetTilesPerSide(); m_Grid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE); m_TerrainOnlyGrid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE); m_ObstructionsDirty.dirty = true; m_ObstructionsDirty.globallyDirty = true; m_ObstructionsDirty.globalRecompute = true; m_TerrainDirty = true; } CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); cmpObstructionManager->UpdateInformations(m_ObstructionsDirty); if (!m_ObstructionsDirty.dirty && !m_TerrainDirty) return; // If the terrain has changed, recompute m_Grid // Else, use data from m_TerrainOnlyGrid and add obstructions if (m_TerrainDirty) { Grid<u16> shoreGrid = ComputeShoreGrid(); ComputeTerrainPassabilityGrid(shoreGrid); // 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_Grid->m_W; int h = m_Grid->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_Grid->set(i, j, m_Grid->get(i, j) | edgeMask); } } } else { for (u16 j = 0; j < h; ++j) for (u16 i = 0; i < edgeSize; ++i) m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask); for (u16 j = 0; j < h; ++j) for (u16 i = w-edgeSize+1; i < w; ++i) m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask); for (u16 j = 0; j < edgeSize; ++j) for (u16 i = edgeSize; i < w-edgeSize+1; ++i) m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask); for (u16 j = h-edgeSize+1; j < h; ++j) for (u16 i = edgeSize; i < w-edgeSize+1; ++i) m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask); } // 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_Grid, clearance, passability.m_Mask); } // Store the updated terrain-only grid *m_TerrainOnlyGrid = *m_Grid; m_TerrainDirty = false; m_ObstructionsDirty.globalRecompute = true; m_ObstructionsDirty.globallyDirty = true; } else if (m_ObstructionsDirty.globalRecompute) { ENSURE(m_Grid->m_W == m_TerrainOnlyGrid->m_W && m_Grid->m_H == m_TerrainOnlyGrid->m_H); memcpy(m_Grid->m_Data, m_TerrainOnlyGrid->m_Data, (m_Grid->m_W)*(m_Grid->m_H)*sizeof(NavcellData)); m_ObstructionsDirty.globallyDirty = true; } else { ENSURE(m_Grid->m_W == m_ObstructionsDirty.dirtinessGrid.m_W && m_Grid->m_H == m_ObstructionsDirty.dirtinessGrid.m_H); ENSURE(m_Grid->m_W == m_TerrainOnlyGrid->m_W && m_Grid->m_H == m_TerrainOnlyGrid->m_H); for (u16 i = 0; i < m_ObstructionsDirty.dirtinessGrid.m_W; ++i) for (u16 j = 0; j < m_ObstructionsDirty.dirtinessGrid.m_H; ++j) if (m_ObstructionsDirty.dirtinessGrid.get(i, j) == 1) m_Grid->set(i, j, m_TerrainOnlyGrid->get(i, j)); } // Add obstructions onto the grid cmpObstructionManager->Rasterize(*m_Grid, m_PassClasses, m_ObstructionsDirty.globalRecompute); // Update the long-range pathfinder if (m_ObstructionsDirty.globallyDirty) m_LongPathfinder.Reload(GetPathfindingPassabilityClasses(), m_Grid); else m_LongPathfinder.Update(m_Grid, m_ObstructionsDirty.dirtinessGrid); }
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; }
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) { switch (msg.GetType()) { case MT_Interpolate: { if (!m_Active) break; const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg); CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId()); if (!cmpPosition || !cmpPosition->IsInWorld()) { // If there's no position (this usually shouldn't happen), destroy the unit immediately GetSimContext().GetComponentManager().DestroyComponentsSoon(GetEntityId()); break; } // Compute the depth the first time this is called // (This is a bit of an ugly place to do it but at least we'll be sure // the actor component was loaded already) if (m_TotalSinkDepth < 0.f) { m_TotalSinkDepth = 1.f; // minimum so we always sink at least a little CmpPtr<ICmpVisual> cmpVisual(GetSimContext(), GetEntityId()); if (cmpVisual) { CBoundingBoxAligned bound = cmpVisual->GetBounds(); m_TotalSinkDepth = std::max(m_TotalSinkDepth, bound[1].Y - bound[0].Y); } // If this is a floating unit, we want it to sink all the way under the terrain, // so find the difference between its current position and the terrain CFixedVector3D pos = cmpPosition->GetPosition(); CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); if (cmpTerrain) { fixed ground = cmpTerrain->GetGroundLevel(pos.X, pos.Z); m_TotalSinkDepth += std::max(0.f, (pos.Y - ground).ToFloat()); } // Sink it further down if it sinks like a ship, as it will rotate. if (m_ShipSink) m_TotalSinkDepth += 10.f; // probably 0 in both cases but we'll remember it anyway. m_InitialXRotation = cmpPosition->GetRotation().X; m_InitialZRotation = cmpPosition->GetRotation().Z; } m_CurrentTime += msgData.deltaSimTime; if (m_CurrentTime >= m_DelayTime) { float t = m_CurrentTime - m_DelayTime; float depth = (m_SinkRate * t) + (m_SinkAccel * t * t); if (m_ShipSink) { // exponential sinking with tilting float tilt_time = t > 5.f ? 5.f : t; float tiltSink = tilt_time * tilt_time / 5.f; entity_pos_t RotX = entity_pos_t::FromFloat(((m_InitialXRotation.ToFloat() * (5.f - tiltSink)) + (1.5f * tiltSink)) / 5.f); entity_pos_t RotZ = entity_pos_t::FromFloat(((m_InitialZRotation.ToFloat() * (3.f - tilt_time)) + (-0.3f * tilt_time)) / 3.f); cmpPosition->SetXZRotation(RotX,RotZ); depth = m_SinkRate * (exp(t - 1.f) - 0.54881163609f) + (m_SinkAccel * exp(t - 4.f) - 0.01831563888f); if (depth < 0.f) depth = 0.f; } cmpPosition->SetHeightOffset(entity_pos_t::FromFloat(-depth)); if (depth > m_TotalSinkDepth) GetSimContext().GetComponentManager().DestroyComponentsSoon(GetEntityId()); } break; } } }
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) { switch (msg.GetType()) { case MT_Interpolate: { if (!m_Active) break; const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg); CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId()); if (cmpPosition.null() || !cmpPosition->IsInWorld()) { // If there's no position (this usually shouldn't happen), destroy the unit immediately GetSimContext().GetComponentManager().DestroyComponentsSoon(GetEntityId()); break; } // Compute the depth the first time this is called // (This is a bit of an ugly place to do it but at least we'll be sure // the actor component was loaded already) if (m_TotalSinkDepth < 0.f) { m_TotalSinkDepth = 1.f; // minimum so we always sink at least a little CmpPtr<ICmpVisual> cmpVisual(GetSimContext(), GetEntityId()); if (!cmpVisual.null()) { CBound bound = cmpVisual->GetBounds(); m_TotalSinkDepth = std::max(m_TotalSinkDepth, bound[1].Y - bound[0].Y); } // If this is a floating unit, we want it to sink all the way under the terrain, // so find the difference between its current position and the terrain CFixedVector3D pos = cmpPosition->GetPosition(); CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); if (!cmpTerrain.null()) { fixed ground = cmpTerrain->GetGroundLevel(pos.X, pos.Z); m_TotalSinkDepth += std::max(0.f, (pos.Y - ground).ToFloat()); } } m_CurrentTime += msgData.frameTime; if (m_CurrentTime > m_DelayTime) { float t = m_CurrentTime - m_DelayTime; float depth = (m_SinkRate * t) + (m_SinkAccel * t * t); cmpPosition->SetHeightOffset(entity_pos_t::FromFloat(-depth)); if (depth > m_TotalSinkDepth) GetSimContext().GetComponentManager().DestroyComponentsSoon(GetEntityId()); } break; } } }
void CLOSTexture::ConstructTexture(int unit) { CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY); if (!cmpTerrain) return; m_MapSize = cmpTerrain->GetVerticesPerSide(); m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize + g_BlurSize - 1); glGenTextures(1, &m_Texture); // Initialise texture with SoD colour, for the areas we don't // overwrite with glTexSubImage2D later u8* texData = new u8[m_TextureSize * m_TextureSize * 4]; memset(texData, 0x00, m_TextureSize * m_TextureSize * 4); if (CRenderer::IsInitialised() && g_Renderer.m_Options.m_SmoothLOS) { glGenTextures(1, &m_TextureSmooth1); glGenTextures(1, &m_TextureSmooth2); g_Renderer.BindTexture(unit, m_TextureSmooth1); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); g_Renderer.BindTexture(unit, m_TextureSmooth2); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } g_Renderer.BindTexture(unit, m_Texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); delete[] texData; { // Texture matrix: We want to map // world pos (0, y, 0) (i.e. first vertex) // onto texcoord (0.5/texsize, 0.5/texsize) (i.e. middle of first texel); // world pos ((mapsize-1)*cellsize, y, (mapsize-1)*cellsize) (i.e. last vertex) // onto texcoord ((mapsize-0.5) / texsize, (mapsize-0.5) / texsize) (i.e. middle of last texel) float s = (m_MapSize-1) / (float)(m_TextureSize * (m_MapSize-1) * TERRAIN_TILE_SIZE); float t = 0.5f / m_TextureSize; m_TextureMatrix.SetZero(); m_TextureMatrix._11 = s; m_TextureMatrix._23 = s; m_TextureMatrix._14 = t; m_TextureMatrix._24 = t; m_TextureMatrix._44 = 1; } { // Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize) float s = m_MapSize / (float)m_TextureSize; m_MinimapTextureMatrix.SetZero(); m_MinimapTextureMatrix._11 = s; m_MinimapTextureMatrix._22 = s; m_MinimapTextureMatrix._44 = 1; } }
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; } }
// ApplyData: take all the input data, and rebuild the scene from it int CMapReader::ApplyData() { if (m_PatchesPerSide == 0) { // we'll probably crash when trying to use this map later throw PSERROR_Game_World_MapLoadFailed("Error loading map: no terrain data.\nCheck application log for details."); } if (!only_xml) { // initialise the terrain pTerrain->Initialize(m_PatchesPerSide, &m_Heightmap[0]); // setup the textures on the minipatches STileDesc* tileptr = &m_Tiles[0]; for (ssize_t j=0; j<m_PatchesPerSide; j++) { for (ssize_t i=0; i<m_PatchesPerSide; i++) { for (ssize_t m=0; m<PATCH_SIZE; m++) { for (ssize_t k=0; k<PATCH_SIZE; k++) { CMiniPatch& mp = pTerrain->GetPatch(i,j)->m_MiniPatches[m][k]; // can't fail mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index]; mp.Priority = tileptr->m_Priority; tileptr++; } } } } } // copy over the lighting parameters if (pLightEnv) *pLightEnv = m_LightEnv; CmpPtr<ICmpPlayerManager> cmpPlayerManager(*pSimContext, SYSTEM_ENTITY); if (pGameView && cmpPlayerManager) { // Default to global camera (with constraints) pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus()); // TODO: Starting rotation? CmpPtr<ICmpPlayer> cmpPlayer(*pSimContext, cmpPlayerManager->GetPlayerByID(m_PlayerID)); if (cmpPlayer && cmpPlayer->HasStartingCamera()) { // Use player starting camera CFixedVector3D pos = cmpPlayer->GetStartingCameraPos(); pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat())); } else if (m_StartingCameraTarget != INVALID_ENTITY) { // Point camera at entity CmpPtr<ICmpPosition> cmpPosition(*pSimContext, m_StartingCameraTarget); if (cmpPosition) { CFixedVector3D pos = cmpPosition->GetPosition(); pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat())); } } } CmpPtr<ICmpTerrain> cmpTerrain(*pSimContext, SYSTEM_ENTITY); if (cmpTerrain) cmpTerrain->ReloadTerrain(); return 0; }
void CCmpPathfinder::UpdateGrid() { PROFILE3("UpdateGrid"); CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); if (!cmpTerrain) return; // error if (!m_PreserveUpdateInformations) m_ObstructionsDirty.Clean(); else m_PreserveUpdateInformations = false; // Next time will be a regular update u16 terrainSize = cmpTerrain->GetTilesPerSide(); if (terrainSize == 0) return; // If the terrain was resized then delete the old grid data if (m_Grid && m_MapSize != terrainSize) { SAFE_DELETE(m_Grid); SAFE_DELETE(m_TerrainOnlyGrid); } // Initialise the terrain data when first needed if (!m_Grid) { m_MapSize = terrainSize; m_Grid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE); m_TerrainOnlyGrid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE); m_ObstructionsDirty.dirty = true; m_ObstructionsDirty.globallyDirty = true; m_ObstructionsDirty.globalRecompute = true; m_TerrainDirty = true; } CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); cmpObstructionManager->UpdateInformations(m_ObstructionsDirty); if (!m_ObstructionsDirty.dirty && !m_TerrainDirty) return; // If the terrain has changed, recompute m_Grid // Else, use data from m_TerrainOnlyGrid and add obstructions if (m_TerrainDirty) { TerrainUpdateHelper(); *m_Grid = *m_TerrainOnlyGrid; m_TerrainDirty = false; m_ObstructionsDirty.globalRecompute = true; m_ObstructionsDirty.globallyDirty = true; } else if (m_ObstructionsDirty.globalRecompute) { ENSURE(m_Grid->m_W == m_TerrainOnlyGrid->m_W && m_Grid->m_H == m_TerrainOnlyGrid->m_H); memcpy(m_Grid->m_Data, m_TerrainOnlyGrid->m_Data, (m_Grid->m_W)*(m_Grid->m_H)*sizeof(NavcellData)); m_ObstructionsDirty.globallyDirty = true; } else { ENSURE(m_Grid->m_W == m_ObstructionsDirty.dirtinessGrid.m_W && m_Grid->m_H == m_ObstructionsDirty.dirtinessGrid.m_H); ENSURE(m_Grid->m_W == m_TerrainOnlyGrid->m_W && m_Grid->m_H == m_TerrainOnlyGrid->m_H); for (u16 i = 0; i < m_ObstructionsDirty.dirtinessGrid.m_W; ++i) for (u16 j = 0; j < m_ObstructionsDirty.dirtinessGrid.m_H; ++j) if (m_ObstructionsDirty.dirtinessGrid.get(i, j) == 1) m_Grid->set(i, j, m_TerrainOnlyGrid->get(i, j)); } // Add obstructions onto the grid cmpObstructionManager->Rasterize(*m_Grid, m_PassClasses, m_ObstructionsDirty.globalRecompute); // Update the long-range pathfinder if (m_ObstructionsDirty.globallyDirty) { std::map<std::string, pass_class_t> nonPathfindingPassClasses, pathfindingPassClasses; GetPassabilityClasses(nonPathfindingPassClasses, pathfindingPassClasses); m_LongPathfinder.Reload(m_Grid, nonPathfindingPassClasses, pathfindingPassClasses); } else m_LongPathfinder.Update(m_Grid, m_ObstructionsDirty.dirtinessGrid); // Notify the units that their current paths can be invalid now CMessagePassabilityMapChanged msg; GetSimContext().GetComponentManager().BroadcastMessage(msg); }
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); } }
///////////////////////////////////////////////////////////////////////////////////////////////////////////// // ValidatePosition: ensure that current transform and bone matrices are both uptodate void CModel::ValidatePosition() { if (m_PositionValid) { ENSURE(!m_Parent || m_Parent->m_PositionValid); return; } if (m_Parent && !m_Parent->m_PositionValid) { // Make sure we don't base our calculations on // a parent animation state that is out of date. m_Parent->ValidatePosition(); // Parent will recursively call our validation. ENSURE(m_PositionValid); return; } if (m_Anim && m_BoneMatrices) { // PROFILE( "generating bone matrices" ); ENSURE(m_pModelDef->GetNumBones() == m_Anim->m_AnimDef->GetNumKeys()); m_Anim->m_AnimDef->BuildBoneMatrices(m_AnimTime, m_BoneMatrices, !(m_Flags & MODELFLAG_NOLOOPANIMATION)); } else if (m_BoneMatrices) { // Bones but no animation - probably a buggy actor forgot to set up the animation, // so just render it in its bind pose for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) { m_BoneMatrices[i].SetIdentity(); m_BoneMatrices[i].Rotate(m_pModelDef->GetBones()[i].m_Rotation); m_BoneMatrices[i].Translate(m_pModelDef->GetBones()[i].m_Translation); } } // For CPU skinning, we precompute as much as possible so that the only // per-vertex work is a single matrix*vec multiplication. // For GPU skinning, we try to minimise CPU work by doing most computation // in the vertex shader instead. // Using g_Renderer.m_Options to detect CPU vs GPU is a bit hacky, // and this doesn't allow the setting to change at runtime, but there isn't // an obvious cleaner way to determine what data needs to be computed, // and GPU skinning is a rarely-used experimental feature anyway. bool worldSpaceBoneMatrices = !g_Renderer.m_Options.m_GPUSkinning; bool computeBlendMatrices = !g_Renderer.m_Options.m_GPUSkinning; if (m_BoneMatrices && worldSpaceBoneMatrices) { // add world-space transformation to m_BoneMatrices const CMatrix3D transform = GetTransform(); for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) m_BoneMatrices[i].Concatenate(transform); } // our own position is now valid; now we can safely update our props' positions without fearing // that doing so will cause a revalidation of this model (see recursion above). m_PositionValid = true; // re-position and validate all props for (size_t j = 0; j < m_Props.size(); ++j) { const Prop& prop=m_Props[j]; CMatrix3D proptransform = prop.m_Point->m_Transform; if (prop.m_Point->m_BoneIndex != 0xff) { CMatrix3D boneMatrix = m_BoneMatrices[prop.m_Point->m_BoneIndex]; if (!worldSpaceBoneMatrices) boneMatrix.Concatenate(GetTransform()); proptransform.Concatenate(boneMatrix); } else { // not relative to any bone; just apply world-space transformation (i.e. relative to object-space origin) proptransform.Concatenate(m_Transform); } // Adjust prop height to terrain level when needed if (prop.m_MaxHeight != 0.f || prop.m_MinHeight != 0.f) { CVector3D propTranslation = proptransform.GetTranslation(); CVector3D objTranslation = m_Transform.GetTranslation(); CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY); if (cmpTerrain) { float objTerrain = cmpTerrain->GetExactGroundLevel(objTranslation.X, objTranslation.Z); float propTerrain = cmpTerrain->GetExactGroundLevel(propTranslation.X, propTranslation.Z); float translateHeight = std::min(prop.m_MaxHeight, std::max(prop.m_MinHeight, propTerrain - objTerrain)); CMatrix3D translate = CMatrix3D(); translate.SetTranslation(0.f, translateHeight, 0.f); proptransform.Concatenate(translate); } } prop.m_Model->SetTransform(proptransform); prop.m_Model->ValidatePosition(); } if (m_BoneMatrices) { for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) { m_BoneMatrices[i] = m_BoneMatrices[i] * m_pModelDef->GetInverseBindBoneMatrices()[i]; } // Note: there is a special case of joint influence, in which the vertex // is influenced by the bind-shape transform instead of a particular bone, // which we indicate with the blending bone ID set to the total number // of bones. But since we're skinning in world space, we use the model's // world space transform and store that matrix in this special index. // (see http://trac.wildfiregames.com/ticket/1012) m_BoneMatrices[m_pModelDef->GetNumBones()] = m_Transform; if (computeBlendMatrices) m_pModelDef->BlendBoneMatrices(m_BoneMatrices); } }