/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpFootprint.h"
#include "simulation2/components/ICmpObstruction.h"
#include "simulation2/components/ICmpObstructionManager.h"
#include "simulation2/components/ICmpPathfinder.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpUnitMotion.h"
#include "simulation2/MessageTypes.h"
#include "graphics/Terrain.h" // For TERRAIN_TILE_SIZE
#include "maths/FixedVector2D.h"
class CCmpFootprint : public ICmpFootprint
{
public:
static void ClassInit(CComponentManager& UNUSED(componentManager))
{
}
DEFAULT_COMPONENT_ALLOCATOR(Footprint)
EShape m_Shape;
entity_pos_t m_Size0; // width/radius
entity_pos_t m_Size1; // height/radius
entity_pos_t m_Height;
static std::string GetSchema()
{
return
"Approximation of the entity's shape, for collision detection and outline rendering. "
"Shapes are flat horizontal squares or circles, extended vertically to a given height."
""
""
"0.0"
""
""
""
"0.0"
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
"";
}
virtual void Init(const CParamNode& paramNode)
{
if (paramNode.GetChild("Square").IsOk())
{
m_Shape = SQUARE;
m_Size0 = paramNode.GetChild("Square").GetChild("@width").ToFixed();
m_Size1 = paramNode.GetChild("Square").GetChild("@depth").ToFixed();
}
else if (paramNode.GetChild("Circle").IsOk())
{
m_Shape = CIRCLE;
m_Size0 = m_Size1 = paramNode.GetChild("Circle").GetChild("@radius").ToFixed();
}
else
{
// Error - pick some default
m_Shape = CIRCLE;
m_Size0 = m_Size1 = entity_pos_t::FromInt(1);
}
m_Height = paramNode.GetChild("Height").ToFixed();
}
virtual void Deinit()
{
}
virtual void Serialize(ISerializer& UNUSED(serialize))
{
// No dynamic state to serialize
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
{
Init(paramNode);
}
virtual void GetShape(EShape& shape, entity_pos_t& size0, entity_pos_t& size1, entity_pos_t& height)
{
shape = m_Shape;
size0 = m_Size0;
size1 = m_Size1;
height = m_Height;
}
virtual CFixedVector3D PickSpawnPoint(entity_id_t spawned)
{
// Try to find a free space around the building's footprint.
// (Note that we use the footprint, not the obstruction shape - this might be a bit dodgy
// because the footprint might be inside the obstruction, but it hopefully gives us a nicer
// shape.)
const CFixedVector3D error(fixed::FromInt(-1), fixed::FromInt(-1), fixed::FromInt(-1));
CmpPtr cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return error;
CmpPtr cmpObstructionManager(GetSystemEntity());
if (!cmpObstructionManager)
return error;
entity_pos_t spawnedRadius;
ICmpObstructionManager::tag_t spawnedTag;
CmpPtr cmpSpawnedObstruction(GetSimContext(), spawned);
if (cmpSpawnedObstruction)
{
spawnedRadius = cmpSpawnedObstruction->GetUnitRadius();
spawnedTag = cmpSpawnedObstruction->GetObstruction();
}
// else use zero radius
// Get passability class from UnitMotion
CmpPtr cmpUnitMotion(GetSimContext(), spawned);
if (!cmpUnitMotion)
return error;
pass_class_t spawnedPass = cmpUnitMotion->GetPassabilityClass();
CmpPtr cmpPathfinder(GetSystemEntity());
if (!cmpPathfinder)
return error;
CFixedVector2D initialPos = cmpPosition->GetPosition2D();
entity_angle_t initialAngle = cmpPosition->GetRotation().Y;
// Max spawning distance in tiles
const i32 maxSpawningDistance = 4;
if (m_Shape == CIRCLE)
{
// Expand outwards from foundation
for (i32 dist = 0; dist <= maxSpawningDistance; ++dist)
{
// The spawn point should be far enough from this footprint to fit the unit, plus a little gap
entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + (int)TERRAIN_TILE_SIZE*dist);
entity_pos_t radius = m_Size0 + clearance;
// Try equally-spaced points around the circle in alternating directions, starting from the front
const i32 numPoints = 31 + 2*dist;
for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
{
entity_angle_t angle = initialAngle + (entity_angle_t::Pi()*2).Multiply(entity_angle_t::FromInt(i)/(int)numPoints);
fixed s, c;
sincos_approx(angle, s, c);
CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius));
SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS)
return pos; // this position is okay, so return it
}
}
}
else
{
fixed s, c;
sincos_approx(initialAngle, s, c);
// Expand outwards from foundation
for (i32 dist = 0; dist <= maxSpawningDistance; ++dist)
{
// The spawn point should be far enough from this footprint to fit the unit, plus a little gap
entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + (int)TERRAIN_TILE_SIZE*dist);
for (i32 edge = 0; edge < 4; ++edge)
{
// Try equally-spaced points along the edge in alternating directions, starting from the middle
const i32 numPoints = 9 + 2*dist;
// Compute the direction and length of the current edge
CFixedVector2D dir;
fixed sx, sy;
switch (edge)
{
case 0:
dir = CFixedVector2D(c, -s);
sx = m_Size0;
sy = m_Size1;
break;
case 1:
dir = CFixedVector2D(-s, -c);
sx = m_Size1;
sy = m_Size0;
break;
case 2:
dir = CFixedVector2D(s, c);
sx = m_Size1;
sy = m_Size0;
break;
case 3:
dir = CFixedVector2D(-c, s);
sx = m_Size0;
sy = m_Size1;
break;
}
CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy/2 + clearance);
dir = dir.Multiply((sx + clearance*2) / (int)(numPoints-1));
for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
{
CFixedVector2D pos (center + dir*i);
SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS)
return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it
}
}
}
}
return error;
}
virtual CFixedVector3D PickSpawnPointBothPass(entity_id_t spawned)
{
// Try to find a free space inside and around this footprint
// at the intersection between the footprint passability and the unit passability.
// (useful for example for destroyed ships where the spawning point should be in the intersection
// of the unit and ship passabilities).
// As the overlap between these passabilities regions may be narrow, we need a small step (1 meter)
const CFixedVector3D error(fixed::FromInt(-1), fixed::FromInt(-1), fixed::FromInt(-1));
CmpPtr cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return error;
CmpPtr cmpObstructionManager(GetSystemEntity());
if (!cmpObstructionManager)
return error;
entity_pos_t spawnedRadius;
ICmpObstructionManager::tag_t spawnedTag;
CmpPtr cmpSpawnedObstruction(GetSimContext(), spawned);
if (cmpSpawnedObstruction)
{
spawnedRadius = cmpSpawnedObstruction->GetUnitRadius();
spawnedTag = cmpSpawnedObstruction->GetObstruction();
}
// else use zero radius
// Get passability class from UnitMotion
CmpPtr cmpUnitMotion(GetSimContext(), spawned);
if (!cmpUnitMotion)
return error;
pass_class_t spawnedPass = cmpUnitMotion->GetPassabilityClass();
CmpPtr cmpPathfinder(GetSystemEntity());
if (!cmpPathfinder)
return error;
// Get the Footprint entity passability
CmpPtr cmpEntityMotion(GetEntityHandle());
if (!cmpEntityMotion)
return error;
pass_class_t entityPass = cmpEntityMotion->GetPassabilityClass();
CFixedVector2D initialPos = cmpPosition->GetPosition2D();
entity_angle_t initialAngle = cmpPosition->GetRotation().Y;
// Max spawning distance + 1 (in meters)
const i32 maxSpawningDistance = 13;
if (m_Shape == CIRCLE)
{
// Expand outwards from foundation with a fixed step of 1 meter
for (i32 dist = 0; dist <= maxSpawningDistance; ++dist)
{
// The spawn point should be far enough from this footprint to fit the unit, plus a little gap
entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(1+dist);
entity_pos_t radius = m_Size0 + clearance;
// Try equally-spaced points around the circle in alternating directions, starting from the front
const i32 numPoints = 31 + 2*dist;
for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
{
entity_angle_t angle = initialAngle + (entity_angle_t::Pi()*2).Multiply(entity_angle_t::FromInt(i)/(int)numPoints);
fixed s, c;
sincos_approx(angle, s, c);
CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius));
SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS &&
cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, entityPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS)
return pos; // this position is okay, so return it
}
}
}
else
{
fixed s, c;
sincos_approx(initialAngle, s, c);
// Expand outwards from foundation with a fixed step of 1 meter
for (i32 dist = 0; dist <= maxSpawningDistance; ++dist)
{
// The spawn point should be far enough from this footprint to fit the unit, plus a little gap
entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(1+dist);
for (i32 edge = 0; edge < 4; ++edge)
{
// Compute the direction and length of the current edge
CFixedVector2D dir;
fixed sx, sy;
switch (edge)
{
case 0:
dir = CFixedVector2D(c, -s);
sx = m_Size0;
sy = m_Size1;
break;
case 1:
dir = CFixedVector2D(-s, -c);
sx = m_Size1;
sy = m_Size0;
break;
case 2:
dir = CFixedVector2D(s, c);
sx = m_Size1;
sy = m_Size0;
break;
case 3:
dir = CFixedVector2D(-c, s);
sx = m_Size0;
sy = m_Size1;
break;
}
sx = sx/2 + clearance;
sy = sy/2 + clearance;
// Try equally-spaced (1 meter) points along the edge in alternating directions, starting from the middle
i32 numPoints = 1 + 2*sx.ToInt_RoundToNearest();
CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy);
for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
{
CFixedVector2D pos (center + dir*i);
SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS &&
cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, entityPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS)
return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it
}
}
}
}
return error;
}
};
REGISTER_COMPONENT_TYPE(Footprint)