/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "TerritoryManager.h"
#include "graphics/Frustum.h"
#include "graphics/Camera.h"
#include "graphics/GameView.h"
#include "graphics/Model.h"
#include "graphics/Terrain.h"
#include "graphics/Unit.h"
#include "lib/allocators/allocators.h" // matrix_alloc
#include "lib/ogl.h"
#include "lib/timer.h"
#include "maths/Bound.h"
#include "maths/MathUtil.h"
#include "ps/Game.h"
#include "ps/Player.h"
#include "ps/Profile.h"
#include "simulation/Entity.h"
#include "simulation/EntityManager.h"
#include "simulation/EntityManager.h"
#include "simulation/EntityTemplate.h"
#include "simulation/LOSManager.h"
CTerritoryManager::CTerritoryManager()
{
m_TerritoryMatrix = 0;
m_DelayedRecalculate = false;
}
CTerritoryManager::~CTerritoryManager()
{
if(m_TerritoryMatrix)
{
matrix_free( (void**) m_TerritoryMatrix );
m_TerritoryMatrix = 0;
}
for( size_t i=0; iGetWorld()->GetTerrain();
m_TilesPerSide = terrain->GetVerticesPerSide() - 1;
m_TerritoryMatrix = (CTerritory***) matrix_alloc( m_TilesPerSide, m_TilesPerSide, sizeof(CTerritory*) );
Recalculate();
}
void CTerritoryManager::Recalculate()
{
// Delete any territories created last time we called Recalculate()
for( size_t i=0; icentre )
m_Territories[i]->centre->m_associatedTerritory = 0;
delete m_Territories[i];
}
m_Territories.clear();
// First, find all the units that are territory centres
std::vector centres;
std::vector entities;
g_EntityManager.GetExtant(entities);
for( size_t i=0; ientf_get(ENTF_DESTROYED) && entities[i]->m_base->m_isTerritoryCentre )
centres.push_back(entities[i]);
}
const size_t mapSize = m_TilesPerSide * CELL_SIZE;
// If there aren't any centre objects, create one big Gaia territory which spans the whole map
if( centres.empty() )
{
std::vector boundary;
boundary.push_back( CVector2D(0, 0) );
boundary.push_back( CVector2D(0, mapSize) );
boundary.push_back( CVector2D(mapSize, mapSize) );
boundary.push_back( CVector2D(mapSize, 0) );
CTerritory* ter = new CTerritory( g_Game->GetPlayer(0), HEntity(), boundary );
m_Territories.push_back(ter);
for( size_t x=0; x boundary;
CalculateBoundary( centres, i, boundary );
CTerritory* ter = new CTerritory( centres[i]->GetPlayer(), centres[i]->me, boundary );
centres[i]->m_associatedTerritory = ter;
m_Territories.push_back(ter);
}
// For each tile, match it to the closest centre object to it.
// TODO: Optimize this, for example by intersecting scanlines with the Voronoi polygons.
for( size_t x=0; xm_position.X, centres[i]->m_position.Z );
float squareDist = (centreLoc - tileLoc).length2();
if( squareDist < bestSquareDist )
{
bestSquareDist = squareDist;
m_TerritoryMatrix[x][z] = m_Territories[i];
}
}
}
}
}
}
void CTerritoryManager::DelayedRecalculate()
{
// This is useful particularly for Atlas, which wants to recalculate
// the boundaries as you move an object around but which doesn't want
// to waste time recalculating multiple times per frame
m_DelayedRecalculate = true;
}
CTerritory* CTerritoryManager::GetTerritory(int x, int z)
{
debug_assert( (size_t) x < m_TilesPerSide && (size_t) z < m_TilesPerSide );
return m_TerritoryMatrix[x][z];
}
CTerritory* CTerritoryManager::GetTerritory(float x, float z)
{
ssize_t ix, iz;
CTerrain::CalcFromPosition(x, z, ix, iz);
return GetTerritory((int)ix, (int)iz);
}
// Calculate the boundary points of a given territory into the given vector
void CTerritoryManager::CalculateBoundary( std::vector& centres, size_t myIndex, std::vector& boundary )
{
// Start with a boundary equal to the whole map
const size_t mapSize = m_TilesPerSide * CELL_SIZE;
boundary.push_back( CVector2D(0, 0) );
boundary.push_back( CVector2D(0, mapSize) );
boundary.push_back( CVector2D(mapSize, mapSize) );
boundary.push_back( CVector2D(mapSize, 0) );
// Clip this polygon against the perpendicular bisector between this centre and each other territory centre
CVector2D myPos( centres[myIndex]->m_position.X, centres[myIndex]->m_position.Z );
for( size_t i=0; im_position.X, centres[i]->m_position.Z );
CVector2D midpoint = (myPos + itsPos) / 2.0f;
CVector2D normal = itsPos - myPos;
// Clip our polygon to the negative side of the half-space with normal "normal"
// containing point "midpoint", i.e. the side of the perpendicular bisector
// between myPos and itsPos that contains myPos. We do this by tracing around
// the polygon looking at each vertex to decide which ones to add as follows:
// - If a vertex is inside the half-space, take it.
// - If a vertex is inside but the next one is outside, also take the
// intersection of that edge with the perpendicular bisector.
// - If a vertex is outside but the next one is inside, take the
// intersection of that edge with the perpendicular bisector.
std::vector newBoundary;
for( size_t j=0; jGetWorld()->GetLOSManager();
CFrustum frustum = g_Game->GetView()->GetCamera()->GetFrustum();
std::vector::iterator terr=m_Territories.begin();
for ( ; terr != m_Territories.end(); ++terr )
{
float r, g, b;
if ( (*terr)->owner->GetPlayerID() == 0 )
{
// Use a dark gray for Gaia territories since white looks a bit weird
//glColor3f( 0.65f, 0.65f, 0.65f );
r = g = b = 0.65f;
}
else
{
// Use the player's colour
const SPlayerColour& col = (*terr)->owner->GetColour();
//glColor3f(col.r, col.g, col.b);
r = col.r;
g = col.g;
b = col.b;
}
for ( size_t edge=0; edge < (*terr)->boundary.size(); edge++ )
{
const std::vector& coords = (*terr)->GetEdgeCoords(edge);
CVector3D start = coords[0];
CVector3D end = coords[coords.size() - 1];
if ( !frustum.DoesSegmentIntersect(start, end) )
continue;
glBegin( GL_LINE_STRIP );
for( size_t i=0; iGetStatus(coords[i].X, coords[i].Z, g_Game->GetLocalPlayer());
if( los & LOS_VISIBLE ) losScale = 1.0f;
else if( los & LOS_EXPLORED ) losScale = 0.7f;
glColor3f( r*losScale, g*losScale, b*losScale );
glVertex3f( coords[i].X, coords[i].Y, coords[i].Z );
}
glEnd();
}
}
glEnable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
glDisable(GL_LINE_SMOOTH);
glLineWidth(1.0f);
glColor4f(1,1,1,1);
}
const std::vector& CTerritory::GetEdgeCoords(size_t edge)
{
if ( edgeCoords.size() == 0 )
{
// Edge coords have not been calculated - calculate them now
edgeCoords.resize( boundary.size() );
const CTerrain* pTerrain = g_Game->GetWorld()->GetTerrain();
// Tweak the boundary to shift all edges "inwards" by 0.3 units towards the territory's centre,
// so that boundaries for adjacent territories don't overlap
std::vector tweakedBoundary = boundary;
for ( size_t i=0; i& coords = edgeCoords[e];
CVector2D start = tweakedBoundary[e];
CVector2D end = tweakedBoundary[(e+1) % boundary.size()];
float iterf = (end - start).Length() / TERRITORY_PRECISION_STEP;
for ( float i=0; i < iterf; i += TERRITORY_PRECISION_STEP )
{
CVector2D pos = Interpolate( start, end, i/iterf );
coords.push_back( CVector3D( pos.x, pTerrain->GetExactGroundLevel(pos)+0.25f, pos.y ) );
}
coords.push_back( CVector3D( end.x, pTerrain->GetExactGroundLevel(end)+0.25f, end.y ) );
}
}
return edgeCoords[edge];
}
void CTerritory::ClearEdgeCache()
{
edgeCoords.clear();
edgeCoords.resize( boundary.size() );
}