mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-07-27 03:32:13 +00:00
# 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:
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user