# New territory border rendering.

Add textured line overlay rendering.
Change terrain height calculations to be triangulation-dependent for
improved accuracy.
Add triangulation-dependent terrain normal function.
Support separate S/T wrap modes for textures.
Rename CVector2D_Maths since it no longer conflicts with simulation
CVector2D.
Coalesce freed chunks in vertex buffers, to avoid excessive
fragmentation.
Add some things to help debug vertex buffer allocation a little.

This was SVN commit r9929.
This commit is contained in:
Ykkrosh
2011-07-30 00:56:45 +00:00
parent 239685d754
commit 8fee3d8ef8
36 changed files with 1084 additions and 143 deletions
@@ -20,15 +20,22 @@
#include "simulation2/system/Component.h"
#include "ICmpTerritoryManager.h"
#include "graphics/Overlay.h"
#include "graphics/Terrain.h"
#include "graphics/TextureManager.h"
#include "maths/MathUtil.h"
#include "maths/Vector2D.h"
#include "ps/Overlay.h"
#include "renderer/Renderer.h"
#include "renderer/Scene.h"
#include "renderer/TerrainOverlay.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpObstruction.h"
#include "simulation2/components/ICmpObstructionManager.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPathfinder.h"
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpSettlement.h"
#include "simulation2/components/ICmpTerrain.h"
@@ -36,6 +43,7 @@
#include "simulation2/helpers/Geometry.h"
#include "simulation2/helpers/Grid.h"
#include "simulation2/helpers/PriorityQueue.h"
#include "simulation2/helpers/Render.h"
class CCmpTerritoryManager;
@@ -58,6 +66,7 @@ public:
componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
componentManager.SubscribeToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
}
DEFAULT_COMPONENT_ALLOCATOR(TerritoryManager)
@@ -67,16 +76,30 @@ public:
return "<a:component type='system'/><empty/>";
}
u8 m_ImpassableCost;
float m_BorderThickness;
float m_BorderSeparation;
Grid<u8>* m_Territories;
TerritoryOverlay* m_DebugOverlay;
std::vector<SOverlayTexturedLine> m_BoundaryLines;
bool m_BoundaryLinesDirty;
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_Territories = NULL;
m_DebugOverlay = NULL;
// m_DebugOverlay = new TerritoryOverlay(*this);
m_BoundaryLinesDirty = true;
m_DirtyID = 1;
CParamNode externalParamNode;
CParamNode::LoadXML(externalParamNode, L"simulation/data/territorymanager.xml");
m_ImpassableCost = externalParamNode.GetChild("TerritoryManager").GetChild("ImpassableCost").ToInt();
m_BorderThickness = externalParamNode.GetChild("TerritoryManager").GetChild("BorderThickness").ToFixed().ToFloat();
m_BorderSeparation = externalParamNode.GetChild("TerritoryManager").GetChild("BorderSeparation").ToFixed().ToFloat();
}
virtual void Deinit()
@@ -116,6 +139,12 @@ public:
MakeDirty();
break;
}
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
RenderSubmit(msgData.collector);
break;
}
}
}
@@ -148,6 +177,7 @@ public:
{
SAFE_DELETE(m_Territories);
++m_DirtyID;
m_BoundaryLinesDirty = true;
}
virtual bool NeedUpdate(size_t* dirtyID)
@@ -169,6 +199,18 @@ public:
* or 1+c if the influence have cost c (assumed between 0 and 254).
*/
void RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid<u8>& grid);
struct TerritoryBoundary
{
player_id_t owner;
std::vector<CVector2D> points;
};
std::vector<TerritoryBoundary> ComputeBoundaries();
void UpdateBoundaryLines();
void RenderSubmit(SceneCollector& collector);
};
REGISTER_COMPONENT_TYPE(TerritoryManager)
@@ -252,15 +294,19 @@ void CCmpTerritoryManager::CalculateTerritories()
Grid<u8> influenceGrid(tilesW, tilesH);
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass("default");
ICmpPathfinder::pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted");
ICmpPathfinder::pass_class_t passClassDefault = cmpPathfinder->GetPassabilityClass("default");
const Grid<u16>& passGrid = cmpPathfinder->GetPassabilityGrid();
for (u32 j = 0; j < tilesH; ++j)
{
for (u32 i = 0; i < tilesW; ++i)
{
u8 g = passGrid.get(i, j);
u8 cost;
if (passGrid.get(i, j) & passClass)
cost = 4; // TODO: should come from some XML file
if (g & passClassUnrestricted)
cost = 255; // off the world; use maximum cost
else if (g & passClassDefault)
cost = m_ImpassableCost;
else
cost = 1;
influenceGrid.set(i, j, cost);
@@ -425,6 +471,219 @@ void CCmpTerritoryManager::RasteriseInfluences(CComponentManager::InterfaceList&
}
}
std::vector<CCmpTerritoryManager::TerritoryBoundary> CCmpTerritoryManager::ComputeBoundaries()
{
PROFILE("ComputeBoundaries");
std::vector<CCmpTerritoryManager::TerritoryBoundary> boundaries;
CalculateTerritories();
// Copy the territories grid so we can mess with it
Grid<u8> grid (*m_Territories);
// Some constants for the border walk
CVector2D edgeOffsets[] = {
CVector2D(0.5f, 0.0f),
CVector2D(1.0f, 0.5f),
CVector2D(0.5f, 1.0f),
CVector2D(0.0f, 0.5f)
};
// Try to find an assigned tile
for (int j = 0; j < grid.m_H; ++j)
{
for (int i = 0; i < grid.m_W; ++i)
{
u8 owner = grid.get(i, j);
if (owner)
{
// Found the first tile (which must be the lowest j value of any non-zero tile);
// start at the bottom edge of it and chase anticlockwise around the border until
// we reach the starting point again
boundaries.push_back(TerritoryBoundary());
boundaries.back().owner = owner;
std::vector<CVector2D>& points = boundaries.back().points;
int dir = 0; // 0 == bottom edge of tile, 1 == right, 2 == top, 3 == left
int cdir = dir;
int ci = i, cj = j;
while (true)
{
points.push_back((CVector2D(ci, cj) + edgeOffsets[cdir]) * CELL_SIZE);
// Given that we're on an edge on a continuous boundary and aiming anticlockwise,
// we can either carry on straight or turn left or turn right, so examine each
// of the three possible cases (depending on initial direction):
switch (cdir)
{
case 0:
if (ci < grid.m_W-1 && cj > 0 && grid.get(ci+1, cj-1) == owner)
{
++ci;
--cj;
cdir = 3;
}
else if (ci < grid.m_W-1 && grid.get(ci+1, cj) == owner)
++ci;
else
cdir = 1;
break;
case 1:
if (ci < grid.m_W-1 && cj < grid.m_H-1 && grid.get(ci+1, cj+1) == owner)
{
++ci;
++cj;
cdir = 0;
}
else if (cj < grid.m_H-1 && grid.get(ci, cj+1) == owner)
++cj;
else
cdir = 2;
break;
case 2:
if (ci > 0 && cj < grid.m_H-1 && grid.get(ci-1, cj+1) == owner)
{
--ci;
++cj;
cdir = 1;
}
else if (ci > 0 && grid.get(ci-1, cj) == owner)
--ci;
else
cdir = 3;
break;
case 3:
if (ci > 0 && cj > 0 && grid.get(ci-1, cj-1) == owner)
{
--ci;
--cj;
cdir = 2;
}
else if (cj > 0 && grid.get(ci, cj-1) == owner)
--cj;
else
cdir = 0;
break;
}
// Stop when we've reached the starting point again
if (ci == i && cj == j && cdir == dir)
break;
}
// Zero out this whole territory with a simple flood fill, so we don't
// process it a second time
std::vector<std::pair<int, int> > tileStack;
#define ZERO_AND_PUSH(i, j) STMT(grid.set(i, j, 0); tileStack.push_back(std::make_pair(i, j)); )
ZERO_AND_PUSH(i, j);
while (!tileStack.empty())
{
int ti = tileStack.back().first;
int tj = tileStack.back().second;
tileStack.pop_back();
if (ti > 0 && grid.get(ti-1, tj) == owner)
ZERO_AND_PUSH(ti-1, tj);
if (ti < grid.m_W-1 && grid.get(ti+1, tj) == owner)
ZERO_AND_PUSH(ti+1, tj);
if (tj > 0 && grid.get(ti, tj-1) == owner)
ZERO_AND_PUSH(ti, tj-1);
if (tj < grid.m_H-1 && grid.get(ti, tj+1) == owner)
ZERO_AND_PUSH(ti, tj+1);
if (ti > 0 && tj > 0 && grid.get(ti-1, tj-1) == owner)
ZERO_AND_PUSH(ti-1, tj-1);
if (ti > 0 && tj < grid.m_H-1 && grid.get(ti-1, tj+1) == owner)
ZERO_AND_PUSH(ti-1, tj+1);
if (ti < grid.m_W-1 && tj > 0 && grid.get(ti+1, tj-1) == owner)
ZERO_AND_PUSH(ti+1, tj-1);
if (ti < grid.m_W-1 && tj < grid.m_H-1 && grid.get(ti+1, tj+1) == owner)
ZERO_AND_PUSH(ti+1, tj+1);
}
#undef ZERO_AND_PUSH
}
}
}
return boundaries;
}
void CCmpTerritoryManager::UpdateBoundaryLines()
{
PROFILE("update boundary lines");
m_BoundaryLines.clear();
std::vector<CCmpTerritoryManager::TerritoryBoundary> boundaries = ComputeBoundaries();
CTextureProperties texturePropsBase("art/textures/misc/territory_border.png");
texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
texturePropsBase.SetMaxAnisotropy(2.f);
CTexturePtr textureBase = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
CTextureProperties texturePropsMask("art/textures/misc/territory_border_mask.png");
texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
texturePropsMask.SetMaxAnisotropy(2.f);
CTexturePtr textureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
if (cmpTerrain.null())
return;
CTerrain* terrain = cmpTerrain->GetCTerrain();
CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY);
if (cmpPlayerManager.null())
return;
for (size_t i = 0; i < boundaries.size(); ++i)
{
if (boundaries[i].points.empty())
continue;
CColor color(1, 0, 1, 1);
CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(boundaries[i].owner));
if (!cmpPlayer.null())
color = cmpPlayer->GetColour();
m_BoundaryLines.push_back(SOverlayTexturedLine());
m_BoundaryLines.back().m_Terrain = terrain;
m_BoundaryLines.back().m_TextureBase = textureBase;
m_BoundaryLines.back().m_TextureMask = textureMask;
m_BoundaryLines.back().m_Color = color;
m_BoundaryLines.back().m_Thickness = m_BorderThickness;
SimRender::SmoothPointsAverage(boundaries[i].points, true);
SimRender::InterpolatePointsRNS(boundaries[i].points, true, m_BorderSeparation);
std::vector<float>& points = m_BoundaryLines.back().m_Coords;
for (size_t j = 0; j < boundaries[i].points.size(); ++j)
{
points.push_back(boundaries[i].points[j].X);
points.push_back(boundaries[i].points[j].Y);
}
}
}
void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector)
{
if (m_BoundaryLinesDirty)
{
UpdateBoundaryLines();
m_BoundaryLinesDirty = false;
}
for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
collector.Submit(&m_BoundaryLines[i]);
}
void TerritoryOverlay::StartRender()
{
+106 -9
View File
@@ -25,12 +25,14 @@
#include "graphics/Overlay.h"
#include "graphics/Terrain.h"
#include "maths/MathUtil.h"
#include "maths/Vector2D.h"
#include "ps/Profile.h"
static const float RENDER_HEIGHT_DELTA = 0.25f; // distance above terrain
void SimRender::ConstructLineOnGround(const CSimContext& context, std::vector<float> xz,
SOverlayLine& overlay, bool floating)
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);
@@ -54,7 +56,7 @@ void SimRender::ConstructLineOnGround(const CSimContext& context, std::vector<fl
{
float px = xz[i];
float pz = xz[i+1];
float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + RENDER_HEIGHT_DELTA;
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);
@@ -62,7 +64,7 @@ void SimRender::ConstructLineOnGround(const CSimContext& context, std::vector<fl
}
void SimRender::ConstructCircleOnGround(const CSimContext& context, float x, float z, float radius,
SOverlayLine& overlay, bool floating)
SOverlayLine& overlay, bool floating, float heightOffset)
{
overlay.m_Coords.clear();
@@ -88,7 +90,7 @@ void SimRender::ConstructCircleOnGround(const CSimContext& context, float x, flo
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;
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);
@@ -113,7 +115,7 @@ static void SplitLine(std::vector<std::pair<float, float> >& coords, float x1, f
}
void SimRender::ConstructSquareOnGround(const CSimContext& context, float x, float z, float w, float h, float a,
SOverlayLine& overlay, bool floating)
SOverlayLine& overlay, bool floating, float heightOffset)
{
overlay.m_Coords.clear();
@@ -150,9 +152,104 @@ void SimRender::ConstructSquareOnGround(const CSimContext& context, float x, flo
{
float px = coords[i].first;
float pz = coords[i].second;
float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + RENDER_HEIGHT_DELTA;
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 SimRender::SmoothPointsAverage(std::vector<CVector2D>& points, bool closed)
{
PROFILE("SmoothPointsAverage");
size_t n = points.size();
if (n < 2)
return; // avoid out-of-bounds array accesses, and leave the points unchanged
std::vector<CVector2D> newPoints;
newPoints.resize(points.size());
// Handle the end points appropriately
if (closed)
{
newPoints[0] = (points[n-1] + points[0] + points[1]) / 3.f;
newPoints[n-1] = (points[n-2] + points[n-1] + points[0]) / 3.f;
}
else
{
newPoints[0] = points[0];
newPoints[n-1] = points[n-1];
}
// Average all the intermediate points
for (size_t i = 1; i < n-1; ++i)
newPoints[i] = (points[i-1] + points[i] + points[i+1]) / 3.f;
points.swap(newPoints);
}
static CVector2D EvaluateSpline(float t, CVector2D a0, CVector2D a1, CVector2D a2, CVector2D a3, float offset)
{
// Compute position on spline
CVector2D p = a0*(t*t*t) + a1*(t*t) + a2*t + a3;
// Compute unit-vector direction of spline
CVector2D dp = (a0*(3*t*t) + a1*(2*t) + a2).Normalized();
// Offset position perpendicularly
return p + CVector2D(dp.Y*-offset, dp.X*offset);
}
void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset)
{
PROFILE("InterpolatePointsRNS");
std::vector<CVector2D> newPoints;
// (This does some redundant computations for adjacent vertices,
// but it's fairly fast (<1ms typically) so we don't worry about it yet)
// TODO: Instead of doing a fixed number of line segments between each
// control point, it should probably be somewhat adaptive to get a nicer
// curve with fewer points
size_t n = points.size();
if (n < 1)
return; // can't do anything unless we have two points
size_t imax = closed ? n : n-1; // TODO: we probably need to do a bit more to handle non-closed paths
newPoints.reserve(imax*4);
for (size_t i = 0; i < imax; ++i)
{
// Get the relevant points for this spline segment
CVector2D p0 = points[(i-1+n)%n];
CVector2D p1 = points[i];
CVector2D p2 = points[(i+1)%n];
CVector2D p3 = points[(i+2)%n];
// Do the RNS computation (based on GPG4 "Nonuniform Splines")
float l1 = (p2 - p1).Length(); // length of spline segment (i)..(i+1)
CVector2D s0 = (p1 - p0).Normalized(); // unit vector of spline segment (i-1)..(i)
CVector2D s1 = (p2 - p1).Normalized(); // unit vector of spline segment (i)..(i+1)
CVector2D s2 = (p3 - p2).Normalized(); // unit vector of spline segment (i+1)..(i+2)
CVector2D v1 = (s0 + s1).Normalized() * l1; // spline velocity at i
CVector2D v2 = (s1 + s2).Normalized() * l1; // spline velocity at i+1
// Compute standard cubic spline parameters
CVector2D a0 = p1*2 + p2*-2 + v1 + v2;
CVector2D a1 = p1*-3 + p2*3 + v1*-2 + v2*-1;
CVector2D a2 = v1;
CVector2D a3 = p1;
// Interpolate at various points
newPoints.push_back(EvaluateSpline(0.f, a0, a1, a2, a3, offset));
newPoints.push_back(EvaluateSpline(1.f/4.f, a0, a1, a2, a3, offset));
newPoints.push_back(EvaluateSpline(2.f/4.f, a0, a1, a2, a3, offset));
newPoints.push_back(EvaluateSpline(3.f/4.f, a0, a1, a2, a3, offset));
}
points.swap(newPoints);
}
+28 -3
View File
@@ -24,6 +24,7 @@
*/
class CSimContext;
class CVector2D;
struct SOverlayLine;
namespace SimRender
@@ -33,18 +34,42 @@ namespace SimRender
* Updates @p overlay so that it represents the given line (a list of x, z coordinate pairs),
* flattened on the terrain (or on the water if @p floating).
*/
void ConstructLineOnGround(const CSimContext& context, std::vector<float> xz, SOverlayLine& overlay, bool floating);
void ConstructLineOnGround(const CSimContext& context, const std::vector<float>& xz,
SOverlayLine& overlay,
bool floating, float heightOffset = 0.25f);
/**
* Updates @p overlay so that it represents the given circle, flattened on the terrain.
*/
void ConstructCircleOnGround(const CSimContext& context, float x, float z, float radius, SOverlayLine& overlay, bool floating);
void ConstructCircleOnGround(const CSimContext& context, float x, float z, float radius,
SOverlayLine& overlay,
bool floating, float heightOffset = 0.25f);
/**
* Updates @p overlay so that it represents the given square, flattened on the terrain.
* @p x and @p z are position of center, @p w and @p h are size of rectangle, @p a is clockwise angle.
*/
void ConstructSquareOnGround(const CSimContext& context, float x, float z, float w, float h, float a, SOverlayLine& overlay, bool floating);
void ConstructSquareOnGround(const CSimContext& context, float x, float z, float w, float h, float a,
SOverlayLine& overlay,
bool floating, float heightOffset = 0.25f);
/**
* Updates @p points so each point is averaged with its neighbours, resulting in
* a somewhat smoother curve, assuming the points are roughly equally spaced.
* If @p closed then the points are treated as a closed path (the last is connected
* to the first).
*/
void SmoothPointsAverage(std::vector<CVector2D>& points, bool closed);
/**
* Updates @p points to include intermediate points interpolating between the original
* control points, using a rounded nonuniform spline.
* The points are also shifted by @p offset in a direction 90 degrees clockwise from
* the direction of the curve.
* If @p closed then the points are treated as a closed path (the last is connected
* to the first).
*/
void InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset);
} // namespace