1
0
forked from 0ad/0ad

# Handle terrain passability and movement costs in pathfinder.

Simplify terrain code (remove Handle indirection).
Delete unused terrain properties.

This was SVN commit r7590.
This commit is contained in:
Ykkrosh 2010-05-27 23:31:03 +00:00
parent 828400d82e
commit 11a20e1bcf
48 changed files with 907 additions and 386 deletions

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<Pathfinder>
<PassabilityClasses>
<default>
<MaxWaterDepth>2</MaxWaterDepth>
<MaxTerrainSlope>1.0</MaxTerrainSlope>
</default>
<ship>
<MinWaterDepth>1</MinWaterDepth>
</ship>
</PassabilityClasses>
<MovementClasses>
<default Speed="1.0" Cost="1.08"/>
</MovementClasses>
</Pathfinder>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
<!DOCTYPE Terrains SYSTEM "/art/textures/terrain/types/terrains.dtd">
<Terrains>
<Terrain groups="city" movementclass="city" mmap="230 227 198" />
</Terrains>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<Pathfinder>
<PassabilityClasses>
<default>
<MaxWaterDepth>2</MaxWaterDepth>
<MaxTerrainSlope>1.0</MaxTerrainSlope>
</default>
<ship>
<MinWaterDepth>1</MinWaterDepth>
</ship>
</PassabilityClasses>
<!--
Warning: Movement costs are a subtle tradeoff between
pathfinding accuracy and computation cost. Be extremely
careful if you change them.
(Speeds are safer to change, but ought to be kept roughly
in sync with costs.)
-->
<MovementClasses>
<default Speed="1.0" Cost="1.08"/>
<city Speed="1.0" Cost="1.0">
<UnitClasses>
<infantry Speed="1.4" Cost="0.6"/>
</UnitClasses>
</city>
</MovementClasses>
</Pathfinder>

View File

@ -38,6 +38,8 @@
<RegenTime>0.1</RegenTime>
<DecayTime>0.2</DecayTime>
</Run>
<PassabilityClass>default</PassabilityClass>
<CostClass>default</CostClass>
</UnitMotion>
<StatusBars>
<HeightOffset>5.0</HeightOffset>

View File

@ -53,6 +53,7 @@
<Run>
<Speed>17.5</Speed>
</Run>
<CostClass>infantry</CostClass>
</UnitMotion>
<Vision>
<Range>12</Range>

View File

@ -17,10 +17,16 @@
<MinRange>0.0</MinRange>
</Charge>
</Attack>
<UnitMotion>
<PassabilityClass>ship</PassabilityClass>
</UnitMotion>
<Footprint replace="">
<Square width="6.0" depth="30.0"/>
<Height>8.0</Height>
</Footprint>
<Obstruction>
<Unit radius="8.0"/>
</Obstruction>
<Vision>
<Range>10</Range>
</Vision>

View File

@ -212,6 +212,25 @@ function init(window, bottomWindow)
var visualiseSizer = new wxStaticBoxSizer(new wxStaticBox(window, -1, 'Visualise'), wxOrientation.VERTICAL);
window.sizer.add(visualiseSizer);
var visualiseSettingsSizer = new wxFlexGridSizer(2);
visualiseSizer.add(visualiseSettingsSizer);
visualiseSettingsSizer.add(new wxStaticText(window, -1, 'Passability'), 0, wxAlignment.RIGHT);
var passabilityClasses = Atlas.Message.GetTerrainPassabilityClasses().classnames;
var passabilitySelector = new wxChoice(window, -1, wxDefaultPosition, wxDefaultSize,
["(none)"].concat(passabilityClasses)
);
visualiseSettingsSizer.add(passabilitySelector);
passabilitySelector.onChoice = function (evt) {
if (evt.selection == 0)
Atlas.Message.SetViewParamS(Atlas.RenderView.GAME, "passability", "");
else
Atlas.Message.SetViewParamS(Atlas.RenderView.GAME, "passability", evt.string);
};
var terrainGroups = Atlas.Message.GetTerrainGroups();
var nb = new wxNotebook(bottomWindow, -1);
bottomWindow.sizer = new wxBoxSizer(wxOrientation.VERTICAL);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -168,14 +168,10 @@ int CMapReader::UnpackTerrain()
CStr texturename;
unpacker.UnpackString(texturename);
Handle handle = 0;
CTextureEntry* texentry = NULL;
if (CTextureManager::IsInitialised())
{
CTextureEntry* texentry = g_TexMan.FindTexture(texturename);
if (texentry)
handle = texentry->GetHandle();
}
m_TerrainTextures.push_back(handle);
texentry = g_TexMan.FindTexture(texturename);
m_TerrainTextures.push_back(texentry);
cur_terrain_tex++;
LDR_CHECK_TIMEOUT(cur_terrain_tex, num_terrain_tex);
@ -215,8 +211,8 @@ int CMapReader::ApplyData()
for (ssize_t k=0; k<PATCH_SIZE; k++) {
CMiniPatch& mp = pTerrain->GetPatch(i,j)->m_MiniPatches[m][k]; // can't fail
mp.Tex1 = m_TerrainTextures[tileptr->m_Tex1Index];
mp.Tex1Priority = tileptr->m_Priority;
mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index];
mp.Priority = tileptr->m_Priority;
tileptr++;
}
@ -386,10 +382,7 @@ void CXMLReader::ReadTerrain(XMBElement parent)
m_MapReader.m_PatchesPerSide = patches;
// Load the texture
Handle handle = 0;
CTextureEntry* texentry = g_TexMan.FindTexture(texture);
if (texentry)
handle = texentry->GetHandle();
m_MapReader.pTerrain->Initialize(patches, NULL);
@ -410,8 +403,8 @@ void CXMLReader::ReadTerrain(XMBElement parent)
{
for (ssize_t x = 0; x < PATCH_SIZE; ++x)
{
patch->m_MiniPatches[z][x].Tex1 = handle;
patch->m_MiniPatches[z][x].Tex1Priority = priority;
patch->m_MiniPatches[z][x].Tex = texentry;
patch->m_MiniPatches[z][x].Priority = priority;
}
}
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -35,6 +35,7 @@ class CCinemaManager;
class CTriggerManager;
class CSimulation2;
class CEntityManager;
class CTextureEntry;
class CXMLReader;
@ -73,7 +74,7 @@ private:
// heightmap for map
std::vector<u16> m_Heightmap;
// list of terrain textures used by map
std::vector<Handle> m_TerrainTextures;
std::vector<CTextureEntry*> m_TerrainTextures;
// tile descriptions for each tile
std::vector<STileDesc> m_Tiles;
// lightenv stored in file

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -75,11 +75,11 @@ void CMapWriter::SaveMap(const VfsPath& pathname, CTerrain* pTerrain,
///////////////////////////////////////////////////////////////////////////////////////////////////
// GetHandleIndex: return the index of the given handle in the given list; or 0xFFFF if
// handle isn't in list
static u16 GetHandleIndex(const Handle handle, const std::vector<Handle>& handles)
static u16 GetEntryIndex(const CTextureEntry* entry, const std::vector<CTextureEntry*>& entries)
{
const size_t limit = std::min(handles.size(), size_t(0xFFFEu)); // paranoia
const size_t limit = std::min(entries.size(), size_t(0xFFFEu)); // paranoia
for (size_t i=0;i<limit;i++) {
if (handles[i]==handle) {
if (entries[i]==entry) {
return (u16)i;
}
}
@ -95,7 +95,7 @@ void CMapWriter::EnumTerrainTextures(CTerrain *pTerrain,
std::vector<STileDesc>& tiles)
{
// the list of all handles in use
std::vector<Handle> handles;
std::vector<CTextureEntry*> entries;
// resize tile array to required size
tiles.resize(SQR(pTerrain->GetVerticesPerSide()-1));
@ -108,15 +108,15 @@ void CMapWriter::EnumTerrainTextures(CTerrain *pTerrain,
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
u16 index=u16(GetHandleIndex(mp.Tex1,handles));
u16 index=u16(GetEntryIndex(mp.GetTextureEntry(),entries));
if (index==0xFFFF) {
index=(u16)handles.size();
handles.push_back(mp.Tex1);
index=(u16)entries.size();
entries.push_back(mp.GetTextureEntry());
}
tileptr->m_Tex1Index=index;
tileptr->m_Tex2Index=0xFFFF;
tileptr->m_Priority=mp.Tex1Priority;
tileptr->m_Priority=mp.GetPriority();
tileptr++;
}
}
@ -124,9 +124,9 @@ void CMapWriter::EnumTerrainTextures(CTerrain *pTerrain,
}
// now find the texture names for each handle
for (size_t i=0;i<handles.size();i++) {
for (size_t i=0;i<entries.size();i++) {
CStr texturename;
CTextureEntry* texentry=g_TexMan.FindTexture(handles[i]);
CTextureEntry* texentry=entries[i];
if (!texentry) {
// uh-oh, this shouldn't happen; set texturename to empty string
texturename="";

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -21,30 +21,11 @@
#include "precompiled.h"
#include "Patch.h"
#include "MiniPatch.h"
#include "Terrain.h"
///////////////////////////////////////////////////////////////////////////////
// Constructor
CMiniPatch::CMiniPatch() : Tex1(0), Tex1Priority(0), m_Parent(0)
CMiniPatch::CMiniPatch() :
Tex(NULL), Priority(0)
{
}
///////////////////////////////////////////////////////////////////////////////
// Destructor
CMiniPatch::~CMiniPatch()
{
}
///////////////////////////////////////////////////////////////////////////////
// GetTileIndex: get the index of this tile in the root terrain object;
// on return, parameters x,y contain index in [0,MapSize)
void CMiniPatch::GetTileIndex(ssize_t& x,ssize_t& z)
{
const ptrdiff_t tindex = this - &m_Parent->m_MiniPatches[0][0];
x=(m_Parent->m_X*PATCH_SIZE)+tindex%PATCH_SIZE;
z=(m_Parent->m_Z*PATCH_SIZE)+tindex/PATCH_SIZE;
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -24,7 +24,7 @@
#include "lib/res/handle.h"
class CPatch;
#include "graphics/TextureEntry.h"
///////////////////////////////////////////////////////////////////////////////
// CMiniPatch: definition of a single terrain tile
@ -33,19 +33,15 @@ class CMiniPatch
public:
// constructor
CMiniPatch();
// destructor
~CMiniPatch();
// get the index of this tile in the root terrain object; x,y in [0,MapSize)
void GetTileIndex(ssize_t& x,ssize_t& z);
public:
// texture applied to tile
Handle Tex1;
CTextureEntry* Tex;
// 'priority' of the texture - determines drawing order of terrain textures
int Tex1Priority;
// parent patch
CPatch* m_Parent;
int Priority;
CTextureEntry* GetTextureEntry() { return Tex; }
Handle GetHandle() { return Tex ? Tex->GetHandle() : 0; }
int GetPriority() { return Priority; }
};

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -50,15 +50,6 @@ void CPatch::Initialize(CTerrain* parent,ssize_t x,ssize_t z)
m_X=x;
m_Z=z;
// set parent of each patch
for (ssize_t j=0;j<PATCH_SIZE;j++)
{
for (ssize_t i=0;i<PATCH_SIZE;i++)
{
m_MiniPatches[j][i].m_Parent=this;
}
}
InvalidateBounds();
}

View File

@ -95,37 +95,15 @@ bool CTerrain::Initialize(ssize_t patchesPerSide,const u16* data)
///////////////////////////////////////////////////////////////////////////////
float CTerrain::GetExactGroundLevel(const CVector2D& v) const
CStr8 CTerrain::GetMovementClass(ssize_t i, ssize_t j) const
{
return GetExactGroundLevel(v.x, v.y);
}
CMiniPatch* tile = GetTile(i, j);
if (tile && tile->GetTextureEntry())
return tile->GetTextureEntry()->GetProperties()->GetMovementClass();
bool CTerrain::IsOnMap(const CVector2D& v) const
{
return IsOnMap(v.x, v.y);
return "default";
}
//bool CTerrain::IsPassable(const CVector2D &loc/*tile space*/, HEntity entity) const
//{
// CMiniPatch *pTile = GetTile(loc.x, loc.y);
// if(!pTile)
// {
// LOGWARNING(L"IsPassable: invalid coordinates %.1f %.1f\n", loc.x, loc.y);
// return false;
// }
// if(!pTile->Tex1)
// return false; // Invalid terrain type in the scenario file
// CTextureEntry *pTexEntry = g_TexMan.FindTexture(pTile->Tex1);
// CTerrainPropertiesPtr pProperties = pTexEntry->GetProperties();
// if(!pProperties)
// {
// VfsPath texturePath = pTexEntry->GetTexturePath();
// LOGWARNING(L"IsPassable: no properties loaded for %ls\n", texturePath.string().c_str());
// return false;
// }
// return pProperties->IsPassable(entity);
//}
///////////////////////////////////////////////////////////////////////////////
// CalcPosition: calculate the world space position of the vertex at (i,j)
void CTerrain::CalcPosition(ssize_t i, ssize_t j, CVector3D& pos) const
@ -283,20 +261,31 @@ float CTerrain::GetVertexGroundLevel(ssize_t i, ssize_t j) const
return HEIGHT_SCALE * m_Heightmap[j*m_MapSize + i];
}
float CTerrain::GetSlope(float x, float z) const
fixed CTerrain::GetVertexGroundLevelFixed(ssize_t i, ssize_t j) const
{
// Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
const ssize_t xi = clamp((ssize_t)floor(x/CELL_SIZE), (ssize_t)0, m_MapSize-2);
const ssize_t zi = clamp((ssize_t)floor(z/CELL_SIZE), (ssize_t)0, m_MapSize-2);
i = clamp(i, (ssize_t)0, m_MapSize-1);
j = clamp(j, (ssize_t)0, m_MapSize-1);
// Convert to fixed metres (being careful to avoid intermediate overflows)
return fixed::FromInt(m_Heightmap[j*m_MapSize + i] / 2) / (int)(HEIGHT_UNITS_PER_METRE / 2);
}
float h00 = m_Heightmap[zi*m_MapSize + xi];
float h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
float h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
float h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];
fixed CTerrain::GetSlopeFixed(ssize_t i, ssize_t j) const
{
// Clamp to size-2 so we can use the tiles (i,j)-(i+1,j+1)
i = clamp(i, (ssize_t)0, m_MapSize-2);
j = clamp(j, (ssize_t)0, m_MapSize-2);
u16 h00 = m_Heightmap[j*m_MapSize + i];
u16 h01 = m_Heightmap[(j+1)*m_MapSize + i];
u16 h10 = m_Heightmap[j*m_MapSize + (i+1)];
u16 h11 = m_Heightmap[(j+1)*m_MapSize + (i+1)];
// Difference of highest point from lowest point
return std::max(std::max(h00, h01), std::max(h10, h11)) -
u16 delta = std::max(std::max(h00, h01), std::max(h10, h11)) -
std::min(std::min(h00, h01), std::min(h10, h11));
// Compute fractional slope (being careful to avoid intermediate overflows)
return fixed::FromInt(delta / CELL_SIZE) / (int)HEIGHT_UNITS_PER_METRE;
}
float CTerrain::GetExactGroundLevel(float x, float z) const
@ -413,8 +402,7 @@ void CTerrain::Resize(ssize_t size)
CMiniPatch& src=m_Patches[j*m_MapSizePatches+m_MapSizePatches-1].m_MiniPatches[m][15];
for (ssize_t k=0;k<PATCH_SIZE;k++) {
CMiniPatch& dst=newPatches[j*size+m_MapSizePatches+n].m_MiniPatches[m][k];
dst.Tex1=src.Tex1;
dst.Tex1Priority=src.Tex1Priority;
dst = src;
}
}
}
@ -431,8 +419,7 @@ void CTerrain::Resize(ssize_t size)
for (ssize_t k=0;k<PATCH_SIZE;k++) {
CMiniPatch& src=srcpatch->m_MiniPatches[15][k];
CMiniPatch& dst=dstpatch->m_MiniPatches[m][k];
dst.Tex1=src.Tex1;
dst.Tex1Priority=src.Tex1Priority;
dst = src;
}
}
srcpatch++;

View File

@ -30,6 +30,7 @@
class CPatch;
class CMiniPatch;
class CFixedVector3D;
class CStr8;
///////////////////////////////////////////////////////////////////////////////
// Terrain Constants:
@ -72,16 +73,15 @@ public:
&& (z >= 0.0f) && (z < (float)((m_MapSize-1) * CELL_SIZE)));
}
bool IsOnMap(const CVector2D& v) const;
// bool IsPassable(const CVector2D& tileSpaceLoc, HEntity entity) const;
CStr8 GetMovementClass(ssize_t i, ssize_t j) const;
float GetVertexGroundLevel(ssize_t i, ssize_t j) const;
fixed GetVertexGroundLevelFixed(ssize_t i, ssize_t j) const;
float GetExactGroundLevel(float x, float z) const;
fixed GetExactGroundLevelFixed(fixed x, fixed z) const;
float GetExactGroundLevel(const CVector2D& v) const;
float GetSlope(float x, float z) const ;
// get the approximate slope (0 = horizontal, 0.5 = 30 degrees, 1.0 = 45 degrees, etc)
fixed GetSlopeFixed(ssize_t i, ssize_t j) const;
// resize this terrain such that each side has given number of patches
void Resize(ssize_t size);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -29,13 +29,12 @@
#include "ps/XML/Xeromyces.h"
#include "ps/CLogger.h"
#define LOG_CATEGORY L"graphics"
CTerrainProperties::CTerrainProperties(CTerrainPropertiesPtr parent):
m_pParent(parent),
m_BaseColor(0),
m_HasBaseColor(false)
m_HasBaseColor(false),
m_MovementClass("default")
{
if (m_pParent)
m_Groups = m_pParent->m_Groups;
@ -53,30 +52,23 @@ CTerrainPropertiesPtr CTerrainProperties::FromXML(const CTerrainPropertiesPtr& p
// Check that we've got the right kind of xml document
if (rootName != "Terrains")
{
LOG(CLogger::Error,
LOG_CATEGORY,
L"TextureManager: Loading %ls: Root node is not terrains (found \"%hs\")",
LOGERROR(
L"TerrainProperties: Loading %ls: Root node is not terrains (found \"%hs\")",
pathname.string().c_str(),
rootName.c_str());
return CTerrainPropertiesPtr();
}
#define ELMT(x) int el_##x = XeroFile.GetElementID(#x)
#define ATTR(x) int at_##x = XeroFile.GetAttributeID(#x)
ELMT(terrain);
#undef ELMT
#undef ATTR
// Ignore all non-terrain nodes, loading the first terrain node and
// returning it.
// Really, we only expect there to be one child and it to be of the right
// type, though.
XMBElementList children = root.GetChildNodes();
for (int i=0; i<children.Count; ++i)
XERO_ITER_EL(root, child)
{
//debug_printf(L"Object %d\n", i);
XMBElement child = children.Item(i);
if (child.GetNodeName() == el_terrain)
{
CTerrainPropertiesPtr ret (new CTerrainProperties(parent));
@ -85,7 +77,7 @@ CTerrainPropertiesPtr CTerrainProperties::FromXML(const CTerrainPropertiesPtr& p
}
else
{
LOG(CLogger::Warning, LOG_CATEGORY,
LOGWARNING(
L"TerrainProperties: Loading %ls: Unexpected node %hs\n",
pathname.string().c_str(),
XeroFile.GetElementString(child.GetNodeName()).c_str());
@ -100,35 +92,15 @@ void CTerrainProperties::LoadXml(XMBElement node, CXeromyces *pFile, const VfsPa
{
#define ELMT(x) int elmt_##x = pFile->GetElementID(#x)
#define ATTR(x) int attr_##x = pFile->GetAttributeID(#x)
ELMT(doodad);
ELMT(passable);
ELMT(impassable);
ELMT(event);
// Terrain Attribs
ATTR(mmap);
ATTR(groups);
ATTR(properties);
// Doodad Attribs
ATTR(name);
ATTR(max);
// Event attribs
ATTR(on);
ATTR(movementclass);
#undef ELMT
#undef ATTR
// stomp on "unused" warnings
UNUSED2(attr_name);
UNUSED2(attr_on);
UNUSED2(attr_max);
UNUSED2(elmt_event);
UNUSED2(elmt_passable);
UNUSED2(elmt_doodad);
XMBAttributeList attribs = node.GetAttributes();
for (int i=0;i<attribs.Count;i++)
XERO_ITER_ATTR(node, attr)
{
XMBAttribute attr = attribs.Item(i);
if (attr.Name == attr_groups)
{
// Parse a comma-separated list of groups, add the new entry to
@ -163,84 +135,11 @@ void CTerrainProperties::LoadXml(XMBElement node, CXeromyces *pFile, const VfsPa
baseColor[3] = (u8)(col.a*255);
m_HasBaseColor = true;
}
else if (attr.Name == attr_properties)
else if (attr.Name == attr_movementclass)
{
// TODO Parse a list of properties and store them somewhere
m_MovementClass = CStr(attr.Value);
}
}
XMBElementList children = node.GetChildNodes();
for (int i=0; i<children.Count; ++i)
{
XMBElement child = children.Item(i);
if (child.GetNodeName() == elmt_passable)
{
ReadPassability(true, child, pFile, pathname);
}
else if (child.GetNodeName() == elmt_impassable)
{
ReadPassability(false, child, pFile, pathname);
}
// TODO Parse information about doodads and events and store it
}
}
void CTerrainProperties::ReadPassability(bool passable, XMBElement node, CXeromyces *pFile, const VfsPath& UNUSED(pathname))
{
#define ATTR(x) int attr_##x = pFile->GetAttributeID(#x)
// Passable Attribs
ATTR(type);
ATTR(speed);
ATTR(effect);
ATTR(prints);
#undef ATTR
STerrainPassability pass(passable);
// Set default speed
pass.m_SpeedFactor = 100;
bool hasType = false;
XMBAttributeList attribs = node.GetAttributes();
for (int i=0;i<attribs.Count;i++)
{
XMBAttribute attr = attribs.Item(i);
if (attr.Name == attr_type)
{
// FIXME Should handle lists of types as well!
pass.m_Type = attr.Value;
hasType = true;
}
else if (attr.Name == attr_speed)
{
CStr val=attr.Value;
CStr trimmedVal=val.Trim(PS_TRIM_BOTH);
pass.m_SpeedFactor = trimmedVal.ToDouble();
if (trimmedVal[trimmedVal.size()-1] == '%')
{
pass.m_SpeedFactor /= 100.0;
}
// FIXME speed=0 could/should be made to set the terrain impassable
}
else if (attr.Name == attr_effect)
{
// TODO Parse and store list of effects
}
else if (attr.Name == attr_prints)
{
// TODO Parse and store footprint effect
}
}
if (!hasType)
{
m_DefaultPassability = pass;
}
else
{
m_Passabilities.push_back(pass);
}
}
bool CTerrainProperties::HasBaseColor()
@ -258,14 +157,3 @@ u32 CTerrainProperties::GetBaseColor()
// White, full opacity.. but this value shouldn't ever be used
return 0xFFFFFFFF;
}
//const STerrainPassability &CTerrainProperties::GetPassability(HEntity entity)
//{
// std::vector<STerrainPassability>::iterator it=m_Passabilities.begin();
// for (;it != m_Passabilities.end();++it)
// {
// if (entity->m_classes.IsMember(it->m_Type))
// return *it;
// }
// return m_DefaultPassability;
//}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -38,18 +38,6 @@ class CTerrainProperties;
typedef shared_ptr<CTerrainProperties> CTerrainPropertiesPtr;
struct STerrainPassability
{
explicit STerrainPassability(bool passable=true):
m_Type(), m_Passable(passable), m_SpeedFactor(passable?1.0:0.0)
{}
CStr m_Type;
bool m_Passable;
double m_SpeedFactor;
// TODO Effects
};
class CTerrainProperties
{
public:
@ -66,15 +54,11 @@ private:
u32 m_BaseColor;
bool m_HasBaseColor;
STerrainPassability m_DefaultPassability;
CStr m_MovementClass;
// All terrain type groups we're a member of
GroupVector m_Groups;
// Passability definitions
std::vector<STerrainPassability> m_Passabilities;
void ReadPassability(bool passable, XMBElement node, CXeromyces *pFile, const VfsPath& pathname);
void LoadXml(XMBElement node, CXeromyces *pFile, const VfsPath& pathname);
public:
@ -89,7 +73,9 @@ public:
// bool WriteXML(const CStr& path);
inline CTerrainPropertiesPtr GetParent() const
{ return m_pParent; }
{
return m_pParent;
}
// Return true if this property object or any of its parents has a basecolor
// override (mmap attribute in the XML file)
@ -100,10 +86,15 @@ public:
// The color value is in BGRA format
u32 GetBaseColor();
// const STerrainPassability &GetPassability(HEntity entity);
CStr GetMovementClass() const
{
return m_MovementClass;
}
const GroupVector &GetGroups() const
{ return m_Groups; }
{
return m_Groups;
}
};
#endif

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -32,13 +32,10 @@
#define LOG_CATEGORY L"graphics"
std::map<Handle, CTextureEntry *> CTextureEntry::m_LoadedTextures;
/////////////////////////////////////////////////////////////////////////////////////
// CTextureEntry constructor
CTextureEntry::CTextureEntry(CTerrainPropertiesPtr props, const VfsPath& path):
m_pProperties(props),
m_Bitmap(NULL),
m_Handle(-1),
m_BaseColor(0),
m_BaseColorValid(false),
@ -58,10 +55,6 @@ CTextureEntry::CTextureEntry(CTerrainPropertiesPtr props, const VfsPath& path):
// CTextureEntry destructor
CTextureEntry::~CTextureEntry()
{
std::map<Handle,CTextureEntry *>::iterator it=m_LoadedTextures.find(m_Handle);
if (it != m_LoadedTextures.end())
m_LoadedTextures.erase(it);
if (m_Handle > 0)
(void)ogl_tex_free(m_Handle);
@ -76,7 +69,6 @@ void CTextureEntry::LoadTexture()
CTexture texture(m_TexturePath);
if (g_Renderer.LoadTexture(&texture,GL_REPEAT)) {
m_Handle=texture.GetHandle();
m_LoadedTextures[m_Handle] = this;
} else {
m_Handle=0;
}
@ -122,13 +114,3 @@ void CTextureEntry::BuildBaseColor()
m_BaseColorValid=true;
}
CTextureEntry *CTextureEntry::GetByHandle(Handle handle)
{
std::map<Handle, CTextureEntry *>::iterator it=m_LoadedTextures.find(handle);
if (it != m_LoadedTextures.end())
return it->second;
else
return NULL;
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -45,7 +45,6 @@ private:
VfsPath m_TexturePath;
void* m_Bitmap; // UI bitmap object (user data for ScEd)
Handle m_Handle; // handle to GL texture data
// BGRA color of topmost mipmap level, for coloring minimap, or a color
@ -57,9 +56,6 @@ private:
// All terrain type groups we're a member of
GroupVector m_Groups;
// A map of all loaded textures and their texture handles for GetByHandle.
static std::map<Handle, CTextureEntry *> m_LoadedTextures;
// load texture from file
void LoadTexture();
// calculate the root color of the texture, used for coloring minimap
@ -80,9 +76,6 @@ public:
VfsPath GetTexturePath() const
{ return m_TexturePath; }
void* GetBitmap() const { return m_Bitmap; }
void SetBitmap(void* bmp) { m_Bitmap=bmp; }
// Get texture handle, load texture if not loaded.
Handle GetHandle() {
if (m_Handle==-1) LoadTexture();
@ -102,10 +95,6 @@ public:
// returns whether this texture-entry has loaded any data yet
bool IsLoaded() { return (m_Handle!=-1); }
// The texture entry class maintains a map of loaded textures and their
// handles.
static CTextureEntry *GetByHandle(Handle handle);
};
#endif

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -78,11 +78,6 @@ CTextureEntry* CTextureManager::FindTexture(const CStr& tag_)
return 0;
}
CTextureEntry* CTextureManager::FindTexture(Handle handle)
{
return CTextureEntry::GetByHandle(handle);
}
CTerrainPropertiesPtr CTextureManager::GetPropertiesFromFile(const CTerrainPropertiesPtr& props, const VfsPath& pathname)
{
return CTerrainProperties::FromXML(props, pathname);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -103,7 +103,6 @@ public:
void UnloadTerrainTextures();
CTextureEntry* FindTexture(const CStr& tag);
CTextureEntry* FindTexture(Handle handle);
// Create a texture object for a new terrain texture at path, using the
// property sheet props.

View File

@ -509,9 +509,9 @@ void CMiniMap::RebuildTerrainTexture()
u32 color = 0xFFFFFFFF;
CMiniPatch *mp = m_Terrain->GetTile(x + i, y + j);
if(mp && mp->Tex1)
if(mp)
{
CTextureEntry *tex = g_TexMan.FindTexture(mp->Tex1);
CTextureEntry *tex = mp->GetTextureEntry();
if(tex)
color = tex->GetBaseColor();
}

View File

@ -283,6 +283,22 @@ typedef CFixed<i32, (i32)0x7fffffff, 32, 15, 16, 65536> CFixed_15_16;
*/
typedef CFixed_15_16 fixed;
namespace std
{
/**
* std::numeric_limits specialisation, currently just providing min and max
*/
template<typename T, T max_t, int total_bits, int int_bits, int fract_bits_, int fract_pow2>
class numeric_limits<CFixed<T, max_t, total_bits, int_bits, fract_bits_, fract_pow2> >
{
typedef CFixed<T, max_t, total_bits, int_bits, fract_bits_, fract_pow2> fixed;
public:
static const bool is_specialized = true;
static fixed min() throw() { fixed f; f.SetInternalValue(std::numeric_limits<T>::min()); return f; }
static fixed max() throw() { fixed f; f.SetInternalValue(std::numeric_limits<T>::max()); return f; }
};
}
/**
* Inaccurate approximation of atan2 over fixed-point numbers.
* Maximum error is almost 0.08 radians (4.5 degrees).

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -73,7 +73,9 @@ CPatchRData::~CPatchRData()
static Handle GetTerrainTileTexture(CTerrain* terrain,ssize_t gx,ssize_t gz)
{
CMiniPatch* mp=terrain->GetTile(gx,gz);
return mp ? mp->Tex1 : 0;
if (!mp)
return 0;
return mp->GetHandle();
}
const float uvFactor = 0.125f / sqrt(2.f);
@ -106,20 +108,20 @@ void CPatchRData::BuildBlends()
// for each tile in patch ..
for (ssize_t j=0;j<PATCH_SIZE;j++) {
for (ssize_t i=0;i<PATCH_SIZE;i++) {
ssize_t gx,gz;
CMiniPatch* mp=&m_Patch->m_MiniPatches[j][i];
mp->GetTileIndex(gx,gz);
ssize_t gx = m_Patch->m_X * PATCH_SIZE + i;
ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j;
// build list of textures of higher priority than current tile that are used by neighbouring tiles
std::vector<STex> neighbourTextures;
for (int m=-1;m<=1;m++) {
for (int k=-1;k<=1;k++) {
CMiniPatch* nmp=terrain->GetTile(gx+k,gz+m);
if (nmp && nmp->Tex1 != mp->Tex1) {
if (nmp->Tex1Priority>mp->Tex1Priority || (nmp->Tex1Priority==mp->Tex1Priority && nmp->Tex1>mp->Tex1)) {
if (nmp && nmp->GetTextureEntry() != mp->GetTextureEntry()) {
if (nmp->GetPriority() > mp->GetPriority() || (nmp->GetPriority() == mp->GetPriority() && nmp->GetTextureEntry() > mp->GetTextureEntry())) {
STex tex;
tex.m_Handle=nmp->Tex1;
tex.m_Priority=nmp->Tex1Priority;
tex.m_Handle=nmp->GetHandle();
tex.m_Priority=nmp->GetPriority();
if (std::find(neighbourTextures.begin(),neighbourTextures.end(),tex)==neighbourTextures.end()) {
neighbourTextures.push_back(tex);
}
@ -308,7 +310,7 @@ void CPatchRData::BuildIndices()
Handle texgrid[PATCH_SIZE][PATCH_SIZE];
for (ssize_t j=0;j<PATCH_SIZE;j++) {
for (ssize_t i=0;i<PATCH_SIZE;i++) {
Handle h=m_Patch->m_MiniPatches[j][i].Tex1;
Handle h=m_Patch->m_MiniPatches[j][i].GetHandle();
texgrid[j][i]=h;
if (std::find(textures.begin(),textures.end(),h)==textures.end()) {
textures.push_back(h);

View File

@ -159,4 +159,20 @@ public:
fixed speed; // metres per second, or 0 if not moving
};
/**
* Sent when terrain (texture or elevation) has been changed.
*/
class CMessageTerrainChanged : public CMessage
{
public:
DEFAULT_MESSAGE_IMPL(TerrainChanged)
CMessageTerrainChanged(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) :
i0(i0), j0(j0), i1(i1), j1(j1)
{
}
ssize_t i0, j0, i1, j1; // inclusive lower bound, exclusive upper bound, in tiles
};
#endif // INCLUDED_MESSAGETYPES

View File

@ -33,6 +33,7 @@
#include "ps/Filesystem.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "ps/XML/Xeromyces.h"
#include <iomanip>
@ -56,6 +57,20 @@ public:
// (can't call ResetState here since the scripts haven't been loaded yet)
}
CParamNode LoadXML(const std::wstring& name)
{
CParamNode ret;
VfsPath path = VfsPath(L"simulation/templates/") / name;
CXeromyces xero;
PSRETURN ok = xero.Load(path);
if (ok != PSRETURN_OK)
return ret; // (Xeromyces already logged an error)
CParamNode::LoadXML(ret, xero);
return ret;
}
void ResetState(bool skipScriptedComponents)
{
m_ComponentManager.ResetState();
@ -71,7 +86,7 @@ public:
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_CommandQueue, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ObstructionManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Pathfinder, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Pathfinder, LoadXML(L"special/pathfinder.xml").GetChild("Pathfinder"));
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ProjectileManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_SoundManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Terrain, noParam);

View File

@ -38,6 +38,7 @@ MESSAGE(Destroy)
MESSAGE(OwnershipChanged)
MESSAGE(PositionChanged)
MESSAGE(MotionChanged)
MESSAGE(TerrainChanged)
// TemplateManager must come before all other (non-test) components,
// so that it is the first to be (de)serialized

View File

@ -28,12 +28,15 @@
#include "graphics/Terrain.h"
#include "maths/FixedVector2D.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/Overlay.h"
#include "ps/Profile.h"
#include "renderer/Scene.h"
#include "renderer/TerrainOverlay.h"
#include "simulation2/helpers/Render.h"
#include "simulation2/helpers/Geometry.h"
#include "simulation2/components/ICmpWaterManager.h"
/*
* Note this file contains two separate pathfinding implementations, the 'normal' tile-based
@ -72,6 +75,82 @@ public:
virtual void ProcessTile(ssize_t i, ssize_t j);
};
/*
* For efficient pathfinding we want to try hard to minimise the per-tile search cost,
* so we precompute the tile passability flags and movement costs for the various different
* types of unit.
* We also want to minimise memory usage (there can easily be 100K tiles so we don't want
* to store many bytes for each).
*
* To handle passability efficiently, we have a small number of passability classes
* (e.g. "infantry", "ship"). Each unit belongs to a single passability class, and
* uses that for all its pathfinding.
* Passability is determined by water depth, terrain slope, forestness, buildingness.
* We need at least one bit per class per tile to represent passability.
*
* We use a separate bit to indicate building obstructions (instead of folding it into
* the class passabilities) so that it can be ignored when doing the accurate short paths.
*
* To handle movement costs, we have an arbitrary number of unit cost classes (e.g. "infantry", "camel"),
* and a small number of terrain cost classes (e.g. "grass", "steep grass", "road", "sand"),
* and a cost mapping table between the classes (e.g. camels are fast on sand).
* We need log2(|terrain cost classes|) bits per tile to represent costs.
*
* We could have one passability bitmap per class, and another array for cost classes,
* but instead (for no particular reason) we'll pack them all into a single u8 array.
* Space is a bit tight so maybe this should be changed to a u16 in the future.
*
* We handle dynamic updates currently by recomputing the entire array, which is stupid;
* it should only bother updating the region that has changed.
*/
class PathfinderPassability
{
public:
PathfinderPassability(u8 mask, const CParamNode& node) :
m_Mask(mask)
{
if (node.GetChild("MinWaterDepth").IsOk())
m_MinDepth = node.GetChild("MinWaterDepth").ToFixed();
else
m_MinDepth = std::numeric_limits<fixed>::min();
if (node.GetChild("MaxWaterDepth").IsOk())
m_MaxDepth = node.GetChild("MaxWaterDepth").ToFixed();
else
m_MaxDepth = std::numeric_limits<fixed>::max();
if (node.GetChild("MaxTerrainSlope").IsOk())
m_MaxSlope = node.GetChild("MaxTerrainSlope").ToFixed();
else
m_MaxSlope = std::numeric_limits<fixed>::max();
}
bool IsPassable(fixed waterdepth, fixed steepness)
{
return ((m_MinDepth <= waterdepth && waterdepth <= m_MaxDepth) && (steepness < m_MaxSlope));
}
u8 m_Mask;
private:
fixed m_MinDepth;
fixed m_MaxDepth;
fixed m_MaxSlope;
};
typedef u8 TerrainTile; // 1 bit for obstructions, PASS_CLASS_BITS for terrain passability, COST_CLASS_BITS for movement costs
const int PASS_CLASS_BITS = 4;
const int COST_CLASS_BITS = 8 - (PASS_CLASS_BITS + 1);
#define IS_TERRAIN_PASSABLE(item, classmask) (((item) & (classmask)) == 0)
#define IS_PASSABLE(item, classmask) (((item) & ((classmask) | 1)) == 0)
#define GET_COST_CLASS(item) ((item) >> (PASS_CLASS_BITS + 1))
#define COST_CLASS_TAG(id) ((id) << (PASS_CLASS_BITS + 1))
// Default cost to move a single tile is a fairly arbitrary number, which should be big
// enough to be precise when multiplied/divided and small enough to never overflow when
// summing the cost of a whole path.
const int DEFAULT_MOVE_COST = 256;
/**
* Implementation of ICmpPathfinder
*/
@ -81,18 +160,30 @@ public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays
componentManager.SubscribeToMessageType(MT_TerrainChanged);
}
DEFAULT_COMPONENT_ALLOCATOR(Pathfinder)
std::map<std::string, u8> m_PassClassMasks;
std::vector<PathfinderPassability> m_PassClasses;
std::map<std::string, u8> m_TerrainCostClassTags;
std::map<std::string, u8> m_UnitCostClassTags;
std::vector<std::vector<u32> > m_MoveCosts; // costs[unitClass][terrainClass]
std::vector<std::vector<fixed> > m_MoveSpeeds; // speeds[unitClass][terrainClass]
u16 m_MapSize; // tiles per side
Grid<u8>* m_Grid; // terrain/passability information
Grid<TerrainTile>* m_Grid; // terrain/passability information
Grid<u8>* m_ObstructionGrid; // cached obstruction information (TODO: we shouldn't bother storing this, it's redundant with LSBs of m_Grid)
bool m_TerrainDirty; // indicates if m_Grid has been updated since terrain changed
// Debugging - output from last pathfind operation:
Grid<PathfindTile>* m_DebugGrid;
u32 m_DebugSteps;
Path* m_DebugPath;
PathfinderOverlay* m_DebugOverlay;
u8 m_DebugPassClass;
std::vector<SOverlayLine> m_DebugOverlayShortPathLines;
@ -101,19 +192,87 @@ public:
return "<a:component type='system'/><empty/>";
}
virtual void Init(const CSimContext& UNUSED(context), const CParamNode& UNUSED(paramNode))
virtual void Init(const CSimContext& UNUSED(context), const CParamNode& paramNode)
{
m_MapSize = 0;
m_Grid = NULL;
m_ObstructionGrid = NULL;
m_TerrainDirty = true;
m_DebugOverlay = NULL;
m_DebugGrid = NULL;
m_DebugPath = NULL;
const CParamNode::ChildrenMap& passClasses = paramNode.GetChild("PassabilityClasses").GetChildren();
for (CParamNode::ChildrenMap::const_iterator it = passClasses.begin(); it != passClasses.end(); ++it)
{
std::string name = it->first;
debug_assert((int)m_PassClasses.size() <= PASS_CLASS_BITS);
u8 mask = (1 << (m_PassClasses.size() + 1));
m_PassClasses.push_back(PathfinderPassability(mask, it->second));
m_PassClassMasks[name] = mask;
}
const CParamNode::ChildrenMap& moveClasses = paramNode.GetChild("MovementClasses").GetChildren();
// First find the set of unit classes used by any terrain classes,
// and assign unique tags to terrain classes
std::set<std::string> unitClassNames;
unitClassNames.insert("default"); // must always have costs for default
{
size_t i = 0;
for (CParamNode::ChildrenMap::const_iterator it = moveClasses.begin(); it != moveClasses.end(); ++it)
{
std::string terrainClassName = it->first;
m_TerrainCostClassTags[terrainClassName] = COST_CLASS_TAG(i);
++i;
const CParamNode::ChildrenMap& unitClasses = it->second.GetChild("UnitClasses").GetChildren();
for (CParamNode::ChildrenMap::const_iterator uit = unitClasses.begin(); uit != unitClasses.end(); ++uit)
unitClassNames.insert(uit->first);
}
}
// For each terrain class, set the costs for every unit class,
// and assign unique tags to unit classes
{
size_t i = 0;
for (std::set<std::string>::const_iterator nit = unitClassNames.begin(); nit != unitClassNames.end(); ++nit)
{
m_UnitCostClassTags[*nit] = i;
++i;
std::vector<u32> costs;
std::vector<fixed> speeds;
for (CParamNode::ChildrenMap::const_iterator it = moveClasses.begin(); it != moveClasses.end(); ++it)
{
// Default to the general costs for this terrain class
fixed cost = it->second.GetChild("@Cost").ToFixed();
fixed speed = it->second.GetChild("@Speed").ToFixed();
// Check for specific cost overrides for this unit class
const CParamNode& unitClass = it->second.GetChild("UnitClasses").GetChild(nit->c_str());
if (unitClass.IsOk())
{
cost = unitClass.GetChild("@Cost").ToFixed();
speed = unitClass.GetChild("@Speed").ToFixed();
}
costs.push_back((cost * DEFAULT_MOVE_COST).ToInt_RoundToZero());
speeds.push_back(speed);
}
m_MoveCosts.push_back(costs);
m_MoveSpeeds.push_back(speeds);
}
}
}
virtual void Deinit(const CSimContext& UNUSED(context))
{
delete m_Grid;
delete m_ObstructionGrid;
delete m_DebugOverlay;
delete m_DebugGrid;
delete m_DebugPath;
@ -143,14 +302,50 @@ public:
RenderSubmit(context, msgData.collector);
break;
}
case MT_TerrainChanged:
{
// TODO: we ought to only bother updating the dirtied region
m_TerrainDirty = true;
break;
}
}
}
virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, Path& ret);
virtual u8 GetPassabilityClass(const std::string& name)
{
if (m_PassClassMasks.find(name) == m_PassClassMasks.end())
{
LOGERROR(L"Invalid passability class name '%hs'", name.c_str());
return 0;
}
virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, Path& ret);
return m_PassClassMasks[name];
}
virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal)
virtual std::vector<std::string> GetPassabilityClasses()
{
std::vector<std::string> classes;
for (std::map<std::string, u8>::iterator it = m_PassClassMasks.begin(); it != m_PassClassMasks.end(); ++it)
classes.push_back(it->first);
return classes;
}
virtual u8 GetCostClass(const std::string& name)
{
if (m_UnitCostClassTags.find(name) == m_UnitCostClassTags.end())
{
LOGERROR(L"Invalid unit cost class name '%hs'", name.c_str());
return m_UnitCostClassTags["default"];
}
return m_UnitCostClassTags[name];
}
virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, u8 passClass, u8 costClass, Path& ret);
virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, u8 passClass, Path& ret);
virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, u8 passClass, u8 costClass)
{
if (!m_DebugOverlay)
return;
@ -159,7 +354,8 @@ public:
m_DebugGrid = NULL;
delete m_DebugPath;
m_DebugPath = new Path();
ComputePath(x0, z0, goal, *m_DebugPath);
ComputePath(x0, z0, goal, passClass, costClass, *m_DebugPath);
m_DebugPassClass = passClass;
}
virtual void SetDebugOverlay(bool enabled)
@ -175,6 +371,14 @@ public:
}
}
virtual fixed GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, u8 costClass)
{
u16 i, j;
NearestTile(x0, z0, i, j);
TerrainTile tileTag = m_Grid->get(i, j);
return m_MoveSpeeds.at(costClass).at(GET_COST_CLASS(tileTag));
}
/**
* Returns the tile containing the given position
*/
@ -208,11 +412,60 @@ public:
debug_assert(size >= 1 && size <= 0xffff); // must fit in 16 bits
m_MapSize = size;
m_Grid = new Grid<u8>(m_MapSize, m_MapSize);
m_Grid = new Grid<TerrainTile>(m_MapSize, m_MapSize);
m_ObstructionGrid = new Grid<u8>(m_MapSize, m_MapSize);
}
CmpPtr<ICmpWaterManager> cmpWaterMan(GetSimContext(), SYSTEM_ENTITY);
CTerrain& terrain = GetSimContext().GetTerrain();
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
cmpObstructionManager->Rasterise(*m_Grid);
if (cmpObstructionManager->Rasterise(*m_ObstructionGrid) || m_TerrainDirty)
{
// Obstructions or terrain changed - we need to recompute passability
// TODO: only bother recomputing the region that has actually changed
for (size_t j = 0; j < m_MapSize; ++j)
{
for (size_t i = 0; i < m_MapSize; ++i)
{
fixed x, z;
TileCenter(i, j, x, z);
TerrainTile t = 0;
bool obstruct = (m_ObstructionGrid->get(i, j) != 0);
fixed height = terrain.GetVertexGroundLevelFixed(i, j); // TODO: should use tile centre
fixed water;
if (!cmpWaterMan.null())
water = cmpWaterMan->GetWaterLevel(x, z);
fixed depth = water - height;
fixed slope = terrain.GetSlopeFixed(i, j);
if (obstruct)
t |= 1;
for (size_t n = 0; n < m_PassClasses.size(); ++n)
{
if (!m_PassClasses[n].IsPassable(depth, slope))
t |= (m_PassClasses[n].m_Mask & ~1);
}
std::string moveClass = terrain.GetMovementClass(i, j);
if (m_TerrainCostClassTags.find(moveClass) != m_TerrainCostClassTags.end())
t |= m_TerrainCostClassTags[moveClass];
m_Grid->set(i, j, t);
}
}
m_TerrainDirty = false;
}
}
void RenderSubmit(const CSimContext& context, SceneCollector& collector);
@ -292,7 +545,7 @@ void PathfinderOverlay::EndRender()
void PathfinderOverlay::ProcessTile(ssize_t i, ssize_t j)
{
if (m_Pathfinder.m_Grid && m_Pathfinder.m_Grid->get(i, j))
if (m_Pathfinder.m_Grid && !IS_PASSABLE(m_Pathfinder.m_Grid->get(i, j), m_Pathfinder.m_DebugPassClass))
RenderTile(CColor(1, 0, 0, 0.6f), false);
if (m_Pathfinder.m_DebugGrid)
@ -515,9 +768,9 @@ static u32 CalculateHeuristic(u16 i, u16 j, u16 iGoal, u16 jGoal, u16 rGoal)
}
// Calculate movement cost from predecessor tile pi,pj to tile i,j
static u32 CalculateCostDelta(u16 pi, u16 pj, u16 i, u16 j, Grid<PathfindTile>* tempGrid)
static u32 CalculateCostDelta(u16 pi, u16 pj, u16 i, u16 j, Grid<PathfindTile>* tempGrid, u32 tileCost)
{
u32 dg = g_CostPerTile;
u32 dg = tileCost;
#ifdef USE_DIAGONAL_MOVEMENT
// XXX: Probably a terrible hack:
@ -554,11 +807,14 @@ struct PathfinderState
u16 iGoal, jGoal; // goal tile
u16 rGoal; // radius of goal (around tile center)
u8 passClass;
std::vector<u32> moveCosts;
PriorityQueue open;
// (there's no explicit closed list; it's encoded in PathfindTile)
Grid<PathfindTile>* tiles;
Grid<u8>* terrain;
Grid<TerrainTile>* terrain;
u32 hBest; // heuristic of closest discovered tile to goal
u16 iBest, jBest; // closest tile
@ -581,10 +837,11 @@ static void ProcessNeighbour(u16 pi, u16 pj, u16 i, u16 j, u32 pg, PathfinderSta
#endif
// Reject impassable tiles
if (state.terrain->get(i, j))
TerrainTile tileTag = state.terrain->get(i, j);
if (!IS_PASSABLE(tileTag, state.passClass))
return;
u32 dg = CalculateCostDelta(pi, pj, i, j, state.tiles);
u32 dg = CalculateCostDelta(pi, pj, i, j, state.tiles, state.moveCosts.at(GET_COST_CLASS(tileTag)));
u32 g = pg + dg; // cost to this tile = cost to predecessor + delta from predecessor
@ -683,7 +940,7 @@ static bool AtGoal(u16 i, u16 j, const ICmpPathfinder::Goal& goal)
return (dist < tolerance);
}
void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, Path& path)
void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, u8 passClass, u8 costClass, Path& path)
{
UpdateGrid();
@ -712,6 +969,9 @@ void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& g
else
state.rGoal = 0;
state.passClass = passClass;
state.moveCosts = m_MoveCosts.at(costClass);
state.steps = 0;
state.tiles = new Grid<PathfindTile>(m_MapSize, m_MapSize);
@ -818,6 +1078,11 @@ struct Edge
CFixedVector2D p0, p1;
};
// When computing vertexes to insert into the search graph,
// add a small delta so that the vertexes of an edge don't get interpreted
// as crossing the edge (given minor numerical inaccuracies)
static const entity_pos_t EDGE_EXPAND_DELTA = entity_pos_t::FromInt(1)/4;
/**
* Check whether a ray from 'a' to 'b' crosses any of the edges.
* (Edges are one-sided so it's only considered a cross if going from front to back.)
@ -887,8 +1152,115 @@ static CFixedVector2D NearestPointOnGoal(CFixedVector2D pos, const CCmpPathfinde
typedef PriorityQueueList<u16, fixed> ShortPathPriorityQueue;
void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, Path& path)
struct TileEdge
{
u16 i, j;
enum { TOP, BOTTOM, LEFT, RIGHT } dir;
};
static void AddTerrainEdges(std::vector<Edge>& edges, std::vector<Vertex>& vertexes, u16 i0, u16 j0, u16 i1, u16 j1, fixed r, u8 passClass, const Grid<TerrainTile>& terrain)
{
PROFILE("AddTerrainEdges");
std::vector<TileEdge> tileEdges;
// Find all edges between tiles of differently passability statuses
for (u16 j = j0; j <= j1; ++j)
{
for (u16 i = i0; i <= i1; ++i)
{
if (!IS_TERRAIN_PASSABLE(terrain.get(i, j), passClass))
{
if (j > 0 && IS_TERRAIN_PASSABLE(terrain.get(i, j-1), passClass))
{
TileEdge e = { i, j, TileEdge::BOTTOM };
tileEdges.push_back(e);
}
if (j < terrain.m_H-1 && IS_TERRAIN_PASSABLE(terrain.get(i, j+1), passClass))
{
TileEdge e = { i, j, TileEdge::TOP };
tileEdges.push_back(e);
}
if (i > 0 && IS_TERRAIN_PASSABLE(terrain.get(i-1, j), passClass))
{
TileEdge e = { i, j, TileEdge::LEFT };
tileEdges.push_back(e);
}
if (i < terrain.m_W-1 && IS_TERRAIN_PASSABLE(terrain.get(i+1, j), passClass))
{
TileEdge e = { i, j, TileEdge::RIGHT };
tileEdges.push_back(e);
}
}
}
}
// TODO: maybe we should precompute these terrain edges since they'll rarely change?
// TODO: for efficiency (minimising the A* search space), we should coalesce adjoining edges
// Add all the tile edges to the search edge/vertex lists
for (size_t n = 0; n < tileEdges.size(); ++n)
{
u16 i = tileEdges[n].i;
u16 j = tileEdges[n].j;
CFixedVector2D v0, v1;
Vertex vert;
vert.status = Vertex::UNEXPLORED;
switch (tileEdges[n].dir)
{
case TileEdge::BOTTOM:
{
v0 = CFixedVector2D(fixed::FromInt(i * CELL_SIZE) - r, fixed::FromInt(j * CELL_SIZE) - r);
v1 = CFixedVector2D(fixed::FromInt((i+1) * CELL_SIZE) + r, fixed::FromInt(j * CELL_SIZE) - r);
Edge e = { v0, v1 };
edges.push_back(e);
vert.p.X = v0.X - EDGE_EXPAND_DELTA; vert.p.Y = v0.Y - EDGE_EXPAND_DELTA; vertexes.push_back(vert);
vert.p.X = v1.X + EDGE_EXPAND_DELTA; vert.p.Y = v1.Y - EDGE_EXPAND_DELTA; vertexes.push_back(vert);
break;
}
case TileEdge::TOP:
{
v0 = CFixedVector2D(fixed::FromInt((i+1) * CELL_SIZE) + r, fixed::FromInt((j+1) * CELL_SIZE) + r);
v1 = CFixedVector2D(fixed::FromInt(i * CELL_SIZE) - r, fixed::FromInt((j+1) * CELL_SIZE) + r);
Edge e = { v0, v1 };
edges.push_back(e);
vert.p.X = v0.X + EDGE_EXPAND_DELTA; vert.p.Y = v0.Y + EDGE_EXPAND_DELTA; vertexes.push_back(vert);
vert.p.X = v1.X - EDGE_EXPAND_DELTA; vert.p.Y = v1.Y + EDGE_EXPAND_DELTA; vertexes.push_back(vert);
break;
}
case TileEdge::LEFT:
{
v0 = CFixedVector2D(fixed::FromInt(i * CELL_SIZE) - r, fixed::FromInt((j+1) * CELL_SIZE) + r);
v1 = CFixedVector2D(fixed::FromInt(i * CELL_SIZE) - r, fixed::FromInt(j * CELL_SIZE) - r);
Edge e = { v0, v1 };
edges.push_back(e);
vert.p.X = v0.X - EDGE_EXPAND_DELTA; vert.p.Y = v0.Y + EDGE_EXPAND_DELTA; vertexes.push_back(vert);
vert.p.X = v1.X - EDGE_EXPAND_DELTA; vert.p.Y = v1.Y - EDGE_EXPAND_DELTA; vertexes.push_back(vert);
break;
}
case TileEdge::RIGHT:
{
v0 = CFixedVector2D(fixed::FromInt((i+1) * CELL_SIZE) + r, fixed::FromInt(j * CELL_SIZE) - r);
v1 = CFixedVector2D(fixed::FromInt((i+1) * CELL_SIZE) + r, fixed::FromInt((j+1) * CELL_SIZE) + r);
Edge e = { v0, v1 };
edges.push_back(e);
vert.p.X = v0.X + EDGE_EXPAND_DELTA; vert.p.Y = v0.Y - EDGE_EXPAND_DELTA; vertexes.push_back(vert);
vert.p.X = v1.X + EDGE_EXPAND_DELTA; vert.p.Y = v1.Y + EDGE_EXPAND_DELTA; vertexes.push_back(vert);
break;
}
}
}
}
void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, u8 passClass, Path& path)
{
UpdateGrid(); // TODO: only need to bother updating if the terrain changed
PROFILE("ComputeShortPath");
m_DebugOverlayShortPathLines.clear();
@ -941,7 +1313,6 @@ void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, enti
edges.push_back(e3);
}
CFixedVector2D goalVec(goal.x, goal.z);
// List of obstruction vertexes (plus start/end points); we'll try to find paths through
@ -960,6 +1331,13 @@ void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, enti
vertexes.push_back(end);
const size_t GOAL_VERTEX_ID = 1;
// Add terrain obstructions
{
u16 i0, j0, i1, j1;
NearestTile(rangeXMin, rangeZMin, i0, j0);
NearestTile(rangeXMax, rangeZMax, i1, j1);
AddTerrainEdges(edges, vertexes, i0, j0, i1, j1, r, passClass, *m_Grid);
}
// Find all the obstruction squares that might affect us
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
@ -980,11 +1358,8 @@ void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, enti
// Expand the vertexes by the moving unit's collision radius, to find the
// closest we can get to it
entity_pos_t delta = entity_pos_t::FromInt(1)/4;
// add a small delta so that the vertexes of an edge don't get interpreted
// as crossing the edge (given minor numerical inaccuracies)
CFixedVector2D hd0(squares[i].hw + r + delta, squares[i].hh + r + delta);
CFixedVector2D hd1(squares[i].hw + r + delta, -(squares[i].hh + r + delta));
CFixedVector2D hd0(squares[i].hw + r + EDGE_EXPAND_DELTA, squares[i].hh + r + EDGE_EXPAND_DELTA);
CFixedVector2D hd1(squares[i].hw + r + EDGE_EXPAND_DELTA, -(squares[i].hh + r + EDGE_EXPAND_DELTA));
Vertex vert;
vert.status = Vertex::UNEXPLORED;

View File

@ -20,6 +20,8 @@
#include "simulation2/system/Component.h"
#include "ICmpTerrain.h"
#include "simulation2/MessageTypes.h"
#include "graphics/Terrain.h"
class CCmpTerrain : public ICmpTerrain
@ -72,6 +74,12 @@ public:
{
return m_Terrain->GetExactGroundLevel(x, z);
}
virtual void MakeDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1)
{
CMessageTerrainChanged msg(i0, j0, i1, j1);
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
}
};
REGISTER_COMPONENT_TYPE(Terrain)

View File

@ -61,6 +61,8 @@ public:
fixed m_Speed; // in units per second
entity_pos_t m_Radius;
u8 m_PassClass;
u8 m_CostClass;
// Dynamic state:
@ -85,6 +87,8 @@ public:
"<a:help>Provides the unit with the ability to move around the world by itself.</a:help>"
"<a:example>"
"<WalkSpeed>7.0</WalkSpeed>"
"<PassabilityClass>default</PassabilityClass>"
"<CostClass>infantry</CostClass>"
"</a:example>"
"<element name='WalkSpeed' a:help='Basic movement speed (in metres per second)'>"
"<ref name='positiveDecimal'/>"
@ -99,7 +103,13 @@ public:
"<element name='DecayTime'><ref name='positiveDecimal'/></element>"
"</interleave>"
"</element>"
"</optional>";
"</optional>"
"<element name='PassabilityClass' a:help='Identifies the terrain passability class (values are defined in special/pathfinder.xml)'>"
"<text/>"
"</element>"
"<element name='CostClass' a:help='Identifies the movement speed/cost class (values are defined in special/pathfinder.xml)'>"
"<text/>"
"</element>";
}
/*
@ -116,6 +126,13 @@ public:
if (!cmpObstruction.null())
m_Radius = cmpObstruction->GetUnitRadius();
CmpPtr<ICmpPathfinder> cmpPathfinder(context, SYSTEM_ENTITY);
if (!cmpPathfinder.null())
{
m_PassClass = cmpPathfinder->GetPassabilityClass(paramNode.GetChild("PassabilityClass").ToASCIIString());
m_CostClass = cmpPathfinder->GetCostClass(paramNode.GetChild("CostClass").ToASCIIString());
}
m_State = IDLE;
m_DebugOverlayEnabled = false;
@ -287,6 +304,10 @@ bool CCmpUnitMotion::CheckMovement(CFixedVector2D pos, CFixedVector2D target)
return false;
}
// NOTE: we ignore terrain here - we assume the pathfinder won't give us a path that crosses impassable
// terrain (which is a valid assumption) and that the terrain will never change (which is not).
// Probably not worth fixing since it'll happen very rarely.
return true;
}
@ -297,6 +318,10 @@ void CCmpUnitMotion::Move(fixed dt)
if (!m_HasTarget)
return;
CmpPtr<ICmpPathfinder> cmpPathfinder (GetSimContext(), SYSTEM_ENTITY);
if (cmpPathfinder.null())
return;
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
if (cmpPosition.null())
return;
@ -315,8 +340,13 @@ void CCmpUnitMotion::Move(fixed dt)
entity_angle_t angle = atan2_approx(offset.X, offset.Y);
cmpPosition->TurnTo(angle);
// Find the speed factor of the underlying terrain
// (We only care about the tile we start on - it doesn't matter if we're moving
// partially onto a much slower/faster tile)
fixed terrainSpeed = cmpPathfinder->GetMovementSpeed(pos.X, pos.Y, m_CostClass);
// Work out how far we can travel in dt
fixed maxdist = m_Speed.Multiply(dt);
fixed maxdist = m_Speed.Multiply(terrainSpeed).Multiply(dt);
// If the target is close, we can move there directly
fixed offsetLength = offset.Length();
@ -743,8 +773,8 @@ bool CCmpUnitMotion::RegeneratePath(CFixedVector2D pos, bool avoidMovingUnits)
m_ShortPath.m_Waypoints.clear();
// TODO: if it's close then just do a short path, not a long path
cmpPathfinder->SetDebugPath(pos.X, pos.Y, m_FinalGoal);
cmpPathfinder->ComputePath(pos.X, pos.Y, m_FinalGoal, m_Path);
cmpPathfinder->SetDebugPath(pos.X, pos.Y, m_FinalGoal, m_PassClass, m_CostClass);
cmpPathfinder->ComputePath(pos.X, pos.Y, m_FinalGoal, m_PassClass, m_CostClass, m_Path);
if (m_DebugOverlayEnabled)
RenderPath(m_Path, m_DebugOverlayLines, OVERLAY_COLOUR_PATH);
@ -829,7 +859,7 @@ bool CCmpUnitMotion::PickNextWaypoint(const CFixedVector2D& pos, bool avoidMovin
else
filter = &filterStationary;
cmpPathfinder->ComputeShortPath(*filter, pos.X, pos.Y, m_Radius, SHORT_PATH_SEARCH_RANGE, goal, m_ShortPath);
cmpPathfinder->ComputeShortPath(*filter, pos.X, pos.Y, m_Radius, SHORT_PATH_SEARCH_RANGE, goal, m_PassClass, m_ShortPath);
if (m_DebugOverlayEnabled)
RenderPath(m_ShortPath, m_DebugOverlayShortPathLines, OVERLAY_COLOUR_SHORT_PATH);

View File

@ -70,17 +70,34 @@ public:
std::vector<Waypoint> m_Waypoints;
};
/**
* Get the list of all known passability class names.
*/
virtual std::vector<std::string> GetPassabilityClasses() = 0;
/**
* Get the tag for a given passability class name.
* Logs an error and returns something acceptable if the name is unrecognised.
*/
virtual u8 GetPassabilityClass(const std::string& name) = 0;
/**
* Get the tag for a given movement cost class name.
* Logs an error and returns something acceptable if the name is unrecognised.
*/
virtual u8 GetCostClass(const std::string& name) = 0;
/**
* Compute a tile-based path from the given point to the goal, and return the set of waypoints.
* The waypoints correspond to the centers of horizontally/vertically adjacent tiles
* along the path.
*/
virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, Path& ret) = 0;
virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, u8 passClass, u8 costClass, Path& ret) = 0;
/**
* If the debug overlay is enabled, render the path that will computed by ComputePath.
*/
virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal) = 0;
virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, u8 passClass, u8 costClass) = 0;
/**
* Compute a precise path from the given point to the goal, and return the set of waypoints.
@ -88,7 +105,13 @@ public:
* a unit of radius 'r' will be able to follow the path with no collisions.
* The path is restricted to a box of radius 'range' from the starting point.
*/
virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, Path& ret) = 0;
virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, u8 passClass, Path& ret) = 0;
/**
* Find the speed factor (typically around 1.0) for a unit of the given cost class
* at the given position.
*/
virtual fixed GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, u8 costClass) = 0;
/**
* Toggle the storage and rendering of debug info.

View File

@ -28,9 +28,18 @@ class ICmpTerrain : public IComponent
{
public:
virtual CFixedVector3D CalcNormal(entity_pos_t x, entity_pos_t z) = 0;
virtual entity_pos_t GetGroundLevel(entity_pos_t x, entity_pos_t z) = 0;
virtual float GetExactGroundLevel(float x, float z) = 0;
/**
* Indicate that the terrain within the given region (inclusive lower bound,
* exclusive upper bound) has been changed. CMessageTerrainChanged will be
* sent to any components that care about terrain changes.
*/
virtual void MakeDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) = 0;
DECLARE_INTERFACE_TYPE(Terrain)
};

View File

@ -86,7 +86,7 @@ public:
ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, x1, z1 };
ICmpPathfinder::Path path;
cmp->ComputePath(x0, z0, goal, path);
cmp->ComputePath(x0, z0, goal, cmp->GetPassabilityClass("default"), cmp->GetCostClass("default"), path);
}
}
};

View File

@ -59,7 +59,7 @@ public:
m_Data[j*m_W + i] = value;
}
T& get(size_t i, size_t j)
T& get(size_t i, size_t j) const
{
#if GRID_BOUNDS_DEBUG
debug_assert(i < m_W && j < m_H);

View File

@ -172,6 +172,18 @@ CMessage* CMessageMotionChanged::FromJSVal(ScriptInterface& scriptInterface, jsv
return new CMessageMotionChanged(speed);
}
////////////////////////////////
jsval CMessageTerrainChanged::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
{
return JSVAL_VOID;
}
CMessage* CMessageTerrainChanged::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val))
{
return NULL;
}
////////////////////////////////////////////////////////////////
CMessage* CMessageFromJSVal(int mtid, ScriptInterface& scriptingInterface, jsval val)

View File

@ -176,5 +176,8 @@ public:
{
return 50.f;
}
};
virtual void MakeDirty(ssize_t UNUSED(i0), ssize_t UNUSED(j0), ssize_t UNUSED(i1), ssize_t UNUSED(j1))
{
}
};

View File

@ -129,6 +129,11 @@ const std::wstring& CParamNode::ToString() const
return m_Value;
}
const std::string CParamNode::ToASCIIString() const
{
return CStr8(m_Value);
}
int CParamNode::ToInt() const
{
int ret = 0;

View File

@ -144,6 +144,11 @@ public:
*/
const std::wstring& ToString() const;
/**
* Returns the content of this node as an 8-bit string
*/
const std::string ToASCIIString() const;
/**
* Parses the content of this node as an integer
*/

View File

@ -101,8 +101,8 @@ ActorViewer::ActorViewer()
for (ssize_t j = 0; j < PATCH_SIZE; ++j)
{
CMiniPatch& mp = patch->m_MiniPatches[i][j];
mp.Tex1 = tex->GetHandle();
mp.Tex1Priority = 0;
mp.Tex = tex;
mp.Priority = 0;
}
}
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -27,6 +27,8 @@
#include "ps/World.h"
#include "maths/MathUtil.h"
#include "graphics/RenderableObject.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpTerrain.h"
#include "../Brushes.h"
#include "../DeltaArray.h"
@ -98,12 +100,21 @@ protected:
BEGIN_COMMAND(AlterElevation)
{
TerrainArray m_TerrainDelta;
ssize_t m_i0, m_j0, m_i1, m_j1;
cAlterElevation()
{
m_TerrainDelta.Init();
}
void MakeDirty()
{
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (!cmpTerrain.null())
cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
}
void Do()
{
int amount = (int)msg->amount;
@ -126,6 +137,7 @@ BEGIN_COMMAND(AlterElevation)
g_CurrentBrush.GetBottomLeft(x0, y0);
for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
{
for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
{
// TODO: proper variable raise amount (store floats in terrain delta array?)
@ -133,25 +145,34 @@ BEGIN_COMMAND(AlterElevation)
if (b)
m_TerrainDelta.RaiseVertex(x0+dx, y0+dy, (int)(amount*b));
}
}
g_Game->GetWorld()->GetTerrain()->MakeDirty(x0, y0, x0+g_CurrentBrush.m_W, y0+g_CurrentBrush.m_H, RENDERDATA_UPDATE_VERTICES);
m_i0 = x0;
m_j0 = y0;
m_i1 = x0 + g_CurrentBrush.m_W;
m_j1 = y0 + g_CurrentBrush.m_H;
MakeDirty();
}
void Undo()
{
m_TerrainDelta.Undo();
g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_VERTICES);
MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_VERTICES);
MakeDirty();
}
void MergeIntoPrevious(cAlterElevation* prev)
{
prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
prev->m_i0 = std::min(prev->m_i0, m_i0);
prev->m_j0 = std::min(prev->m_j0, m_j0);
prev->m_i1 = std::max(prev->m_i1, m_i1);
prev->m_j1 = std::max(prev->m_j1, m_j1);
}
};
END_COMMAND(AlterElevation)
@ -161,12 +182,21 @@ END_COMMAND(AlterElevation)
BEGIN_COMMAND(FlattenElevation)
{
TerrainArray m_TerrainDelta;
ssize_t m_i0, m_j0, m_i1, m_j1;
cFlattenElevation()
{
m_TerrainDelta.Init();
}
void MakeDirty()
{
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (!cmpTerrain.null())
cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
}
void Do()
{
int amount = (int)msg->amount;
@ -190,24 +220,32 @@ BEGIN_COMMAND(FlattenElevation)
m_TerrainDelta.MoveVertexTowards(x0+dx, y0+dy, height, 1 + (int)(b*amount));
}
g_Game->GetWorld()->GetTerrain()->MakeDirty(x0, y0, x0+g_CurrentBrush.m_W, y0+g_CurrentBrush.m_H, RENDERDATA_UPDATE_VERTICES);
m_i0 = x0;
m_j0 = y0;
m_i1 = x0 + g_CurrentBrush.m_W;
m_j1 = y0 + g_CurrentBrush.m_H;
MakeDirty();
}
void Undo()
{
m_TerrainDelta.Undo();
g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_VERTICES);
MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_VERTICES);
MakeDirty();
}
void MergeIntoPrevious(cFlattenElevation* prev)
{
prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
prev->m_i0 = std::min(prev->m_i0, m_i0);
prev->m_j0 = std::min(prev->m_j0, m_j0);
prev->m_i1 = std::max(prev->m_i1, m_i1);
prev->m_j1 = std::max(prev->m_j1, m_j1);
}
};
END_COMMAND(FlattenElevation)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -134,6 +134,12 @@ MESSAGEHANDLER(SetViewParamC)
view->SetParam(*msg->name, msg->value);
}
MESSAGEHANDLER(SetViewParamS)
{
View* view = View::GetView(msg->view);
view->SetParam(*msg->name, *msg->value);
}
MESSAGEHANDLER(SetActorViewer)
{
if (msg->flushcache)

View File

@ -125,7 +125,6 @@ MESSAGEHANDLER(GenerateMap)
// Cover terrain with default texture
// TODO: split into fCoverWithTexture
CTextureEntry* texentry = g_TexMan.FindTexture("grass1_spring"); // TODO: make default customisable
Handle tex = texentry ? texentry->GetHandle() : 0;
int patchesPerSide = terrain->GetPatchesPerSide();
for (int pz = 0; pz < patchesPerSide; ++pz)
@ -138,8 +137,8 @@ MESSAGEHANDLER(GenerateMap)
{
for (ssize_t x = 0; x < PATCH_SIZE; ++x)
{
patch->m_MiniPatches[z][x].Tex1 = tex;
patch->m_MiniPatches[z][x].Tex1Priority = 0;
patch->m_MiniPatches[z][x].Tex = texentry;
patch->m_MiniPatches[z][x].Priority = 0;
}
}
}

View File

@ -278,9 +278,7 @@ END_COMMAND(SetObjectSettings);
//////////////////////////////////////////////////////////////////////////
static CStrW g_PreviewUnitName;
static entity_id_t g_PreviewEntityID = INVALID_ENTITY; // used if g_UseSimulation2
static size_t g_PreviewUnitID = invalidUnitId; // old simulation
static bool g_PreviewUnitFloating;
static entity_id_t g_PreviewEntityID = INVALID_ENTITY;
static CVector3D GetUnitPos(const Position& pos, bool floating)
{

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -29,9 +29,13 @@
#include "ps/World.h"
#include "lib/ogl.h"
#include "lib/res/graphics/ogl_tex.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpPathfinder.h"
#include "simulation2/components/ICmpTerrain.h"
#include "../Brushes.h"
#include "../DeltaArray.h"
#include "../View.h"
namespace AtlasMessage {
@ -115,14 +119,28 @@ QUERYHANDLER(GetTerrainGroupPreviews)
msg->previews = previews;
}
QUERYHANDLER(GetTerrainPassabilityClasses)
{
CmpPtr<ICmpPathfinder> cmpPathfinder(*View::GetView_Game()->GetSimulation2(), SYSTEM_ENTITY);
if (!cmpPathfinder.null())
{
std::vector<std::string> names = cmpPathfinder->GetPassabilityClasses();
std::vector<std::wstring> classnames;
for (std::vector<std::string>::iterator it = names.begin(); it != names.end(); ++it)
classnames.push_back(CStrW(*it));
msg->classnames = classnames;
}
}
//////////////////////////////////////////////////////////////////////////
BEGIN_COMMAND(PaintTerrain)
{
struct TerrainTile
{
TerrainTile(Handle t, ssize_t p) : tex(t), priority(p) {}
Handle tex;
TerrainTile(CTextureEntry* t, ssize_t p) : tex(t), priority(p) {}
CTextureEntry* tex;
ssize_t priority;
};
class TerrainArray : public DeltaArray2D<TerrainTile>
@ -134,7 +152,7 @@ BEGIN_COMMAND(PaintTerrain)
m_VertsPerSide = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
}
void PaintTile(ssize_t x, ssize_t y, Handle tex, ssize_t priority)
void PaintTile(ssize_t x, ssize_t y, CTextureEntry* tex, ssize_t priority)
{
// Ignore out-of-bounds tiles
if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
@ -147,7 +165,7 @@ BEGIN_COMMAND(PaintTerrain)
ssize_t greatest = 0;
ssize_t scale = (priority == ePaintTerrainPriority::HIGH ? +1 : -1);
CMiniPatch* tile;
#define TILE(dx, dy) tile = m_Terrain->GetTile(x dx, y dy); if (tile && tile->Tex1Priority*scale > greatest) greatest = tile->Tex1Priority*scale;
#define TILE(dx, dy) tile = m_Terrain->GetTile(x dx, y dy); if (tile && tile->GetPriority()*scale > greatest) greatest = tile->GetPriority()*scale;
TILE(-1, -1) TILE(+0, -1) TILE(+1, -1)
TILE(-1, +0) TILE(+1, +0)
TILE(-1, +1) TILE(+0, +1) TILE(+1, +1)
@ -160,14 +178,14 @@ BEGIN_COMMAND(PaintTerrain)
{
CMiniPatch* mp = m_Terrain->GetTile(x, y);
debug_assert(mp);
return TerrainTile(mp->Tex1, mp->Tex1Priority);
return TerrainTile(mp->Tex, mp->Priority);
}
void setNew(ssize_t x, ssize_t y, const TerrainTile& val)
{
CMiniPatch* mp = m_Terrain->GetTile(x, y);
debug_assert(mp);
mp->Tex1 = val.tex;
mp->Tex1Priority = val.priority;
mp->Tex = val.tex;
mp->Priority = val.priority;
}
CTerrain* m_Terrain;
@ -175,15 +193,23 @@ BEGIN_COMMAND(PaintTerrain)
};
TerrainArray m_TerrainDelta;
ssize_t m_i0, m_j0, m_i1, m_j1;
cPaintTerrain()
{
m_TerrainDelta.Init();
}
void MakeDirty()
{
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_INDICES);
CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (!cmpTerrain.null())
cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
}
void Do()
{
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
ssize_t x0, y0;
@ -195,35 +221,42 @@ BEGIN_COMMAND(PaintTerrain)
debug_warn(L"Can't find texentry"); // TODO: nicer error handling
return;
}
Handle texture = texentry->GetHandle();
for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
{
for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
{
if (g_CurrentBrush.Get(dx, dy) > 0.5f) // TODO: proper solid brushes
m_TerrainDelta.PaintTile(x0+dx, y0+dy, texture, msg->priority);
m_TerrainDelta.PaintTile(x0+dx, y0+dy, texentry, msg->priority);
}
}
g_Game->GetWorld()->GetTerrain()->MakeDirty(x0, y0, x0+g_CurrentBrush.m_W, y0+g_CurrentBrush.m_H, RENDERDATA_UPDATE_INDICES);
m_i0 = x0;
m_j0 = y0;
m_i1 = x0 + g_CurrentBrush.m_W;
m_j1 = y0 + g_CurrentBrush.m_H;
MakeDirty();
}
void Undo()
{
m_TerrainDelta.Undo();
g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_INDICES);
MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_INDICES);
MakeDirty();
}
void MergeIntoPrevious(cPaintTerrain* prev)
{
prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
prev->m_i0 = std::min(prev->m_i0, m_i0);
prev->m_j0 = std::min(prev->m_j0, m_j0);
prev->m_i1 = std::max(prev->m_i1, m_i1);
prev->m_j1 = std::max(prev->m_j1, m_j1);
}
};
END_COMMAND(PaintTerrain)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -53,6 +53,11 @@ MESSAGE(SetViewParamC,
((std::wstring, name))
((Colour, value))
);
MESSAGE(SetViewParamS,
((int, view)) // eRenderView
((std::wstring, name))
((std::wstring, value))
);
MESSAGE(JavaScript,
((std::wstring, command))
@ -179,6 +184,10 @@ QUERY(GetTerrainGroupPreviews,
((std::vector<sTerrainGroupPreview>, previews))
);
QUERY(GetTerrainPassabilityClasses,
, // no inputs
((std::vector<std::wstring>, classnames))
);
//////////////////////////////////////////////////////////////////////////

View File

@ -36,6 +36,8 @@
#include "renderer/Renderer.h"
#include "simulation/Simulation.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpObstructionManager.h"
#include "simulation2/components/ICmpPathfinder.h"
extern void (*Atlas_GLSwapBuffers)(void* context);
@ -51,6 +53,10 @@ void View::SetParam(const std::wstring& UNUSED(name), const AtlasMessage::Colour
{
}
void View::SetParam(const std::wstring& UNUSED(name), const std::wstring& UNUSED(value))
{
}
//////////////////////////////////////////////////////////////////////////
ViewActor::ViewActor()
@ -209,10 +215,47 @@ void ViewGame::Render()
camera.SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV);
camera.UpdateFrustum();
// Update the pathfinder display if necessary
if (!m_DisplayPassability.empty())
{
CmpPtr<ICmpObstructionManager> cmpObstructionMan(*GetSimulation2(), SYSTEM_ENTITY);
if (!cmpObstructionMan.null())
{
cmpObstructionMan->SetDebugOverlay(true);
}
CmpPtr<ICmpPathfinder> cmpPathfinder(*GetSimulation2(), SYSTEM_ENTITY);
if (!cmpPathfinder.null())
{
cmpPathfinder->SetDebugOverlay(true);
// Kind of a hack to make it update the terrain grid
ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, fixed::Zero(), fixed::Zero() };
u8 passClass = cmpPathfinder->GetPassabilityClass(m_DisplayPassability);
u8 costClass = cmpPathfinder->GetCostClass("default");
cmpPathfinder->SetDebugPath(fixed::Zero(), fixed::Zero(), goal, passClass, costClass);
}
}
::Render();
Atlas_GLSwapBuffers((void*)g_GameLoop->glCanvas);
}
void ViewGame::SetParam(const std::wstring& name, const std::wstring& value)
{
if (name == L"passability")
{
m_DisplayPassability = CStr(value);
CmpPtr<ICmpObstructionManager> cmpObstructionMan(*GetSimulation2(), SYSTEM_ENTITY);
if (!cmpObstructionMan.null())
cmpObstructionMan->SetDebugOverlay(!value.empty());
CmpPtr<ICmpPathfinder> cmpPathfinder(*GetSimulation2(), SYSTEM_ENTITY);
if (!cmpPathfinder.null())
cmpPathfinder->SetDebugOverlay(!value.empty());
}
}
CCamera& ViewGame::GetCamera()
{
return *g_Game->GetView()->GetCamera();

View File

@ -45,6 +45,7 @@ public:
virtual void SetParam(const std::wstring& name, bool value);
virtual void SetParam(const std::wstring& name, const AtlasMessage::Colour& value);
virtual void SetParam(const std::wstring& name, const std::wstring& value);
// These always return a valid (not NULL) object
static View* GetView(int /*eRenderView*/ view);
@ -80,6 +81,8 @@ public:
virtual CSimulation2* GetSimulation2();
virtual bool WantsHighFramerate();
virtual void SetParam(const std::wstring& name, const std::wstring& value);
void SetSpeedMultiplier(float speed);
void SaveState(const std::wstring& label);
void RestoreState(const std::wstring& label);
@ -88,6 +91,7 @@ public:
private:
float m_SpeedMultiplier;
std::map<std::wstring, SimState*> m_SavedStates;
std::string m_DisplayPassability;
};
class ActorViewer;