2010-01-09 20:20:14 +01:00
|
|
|
/* 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
|
|
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "precompiled.h"
|
|
|
|
|
|
|
|
#include "simulation2/system/Component.h"
|
|
|
|
#include "ICmpPosition.h"
|
|
|
|
|
2010-01-29 22:13:18 +01:00
|
|
|
#include "simulation2/MessageTypes.h"
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
#include "ICmpTerrain.h"
|
2010-05-28 01:23:53 +02:00
|
|
|
#include "ICmpWaterManager.h"
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
#include "graphics/Terrain.h"
|
|
|
|
#include "lib/rand.h"
|
|
|
|
#include "maths/MathUtil.h"
|
|
|
|
#include "maths/Matrix3D.h"
|
|
|
|
#include "maths/Vector3D.h"
|
|
|
|
#include "ps/CLogger.h"
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Basic ICmpPosition implementation.
|
|
|
|
*/
|
|
|
|
class CCmpPosition : public ICmpPosition
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
static void ClassInit(CComponentManager& componentManager)
|
|
|
|
{
|
2010-01-22 21:03:14 +01:00
|
|
|
componentManager.SubscribeToMessageType(MT_TurnStart);
|
2010-02-10 20:28:46 +01:00
|
|
|
componentManager.SubscribeToMessageType(MT_Interpolate);
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
// TODO: if this component turns out to be a performance issue, it should
|
|
|
|
// be optimised by creating a new PositionStatic component that doesn't subscribe
|
|
|
|
// to messages and doesn't store LastX/LastZ, and that should be used for all
|
|
|
|
// entities that don't move
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFAULT_COMPONENT_ALLOCATOR(Position)
|
|
|
|
|
|
|
|
// Template state:
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
UPRIGHT = 0,
|
|
|
|
PITCH = 1,
|
|
|
|
PITCH_ROLL = 2,
|
|
|
|
} m_AnchorType;
|
|
|
|
|
|
|
|
entity_pos_t m_YOffset;
|
|
|
|
bool m_Floating;
|
2010-02-10 20:28:46 +01:00
|
|
|
float m_RotYSpeed; // maximum radians per second, used by InterpolatedRotY to follow RotY
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
// Dynamic state:
|
|
|
|
|
|
|
|
bool m_InWorld;
|
|
|
|
entity_pos_t m_X, m_Z, m_LastX, m_LastZ; // these values contain undefined junk if !InWorld
|
|
|
|
|
|
|
|
entity_angle_t m_RotX, m_RotY, m_RotZ;
|
2010-02-10 20:28:46 +01:00
|
|
|
float m_InterpolatedRotY; // not serialized
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-04-09 21:02:39 +02:00
|
|
|
static std::string GetSchema()
|
|
|
|
{
|
|
|
|
return
|
2010-04-23 18:09:03 +02:00
|
|
|
"<a:help>Allows this entity to exist at a location (and orientation) in the world, and defines some details of the positioning.</a:help>"
|
|
|
|
"<a:example>"
|
|
|
|
"<Anchor>upright</Anchor>"
|
|
|
|
"<Altitude>0.0</Altitude>"
|
|
|
|
"<Floating>false</Floating>"
|
|
|
|
"</a:example>"
|
2010-04-09 21:02:39 +02:00
|
|
|
"<element name='Anchor' a:help='Automatic rotation to follow the slope of terrain'>"
|
|
|
|
"<choice>"
|
|
|
|
"<value a:help='Always stand straight up'>upright</value>"
|
|
|
|
"<value a:help='Rotate backwards and forwards to follow the terrain'>pitch</value>"
|
|
|
|
"<value a:help='Rotate in all direction to follow the terrain'>pitch-roll</value>"
|
|
|
|
"</choice>"
|
|
|
|
"</element>"
|
|
|
|
"<element name='Altitude' a:help='Height above terrain in metres'>"
|
|
|
|
"<data type='decimal'/>"
|
|
|
|
"</element>"
|
|
|
|
"<element name='Floating' a:help='Whether the entity floats on water'>"
|
|
|
|
"<data type='boolean'/>"
|
|
|
|
"</element>";
|
|
|
|
}
|
|
|
|
|
2010-05-01 11:48:39 +02:00
|
|
|
virtual void Init(const CSimContext& UNUSED(context), const CParamNode& paramNode)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-02-03 00:01:17 +01:00
|
|
|
std::wstring anchor = paramNode.GetChild("Anchor").ToString();
|
2010-01-09 20:20:14 +01:00
|
|
|
if (anchor == L"pitch")
|
|
|
|
m_AnchorType = PITCH;
|
|
|
|
else if (anchor == L"pitch-roll")
|
|
|
|
m_AnchorType = PITCH_ROLL;
|
|
|
|
else
|
|
|
|
m_AnchorType = UPRIGHT;
|
|
|
|
|
|
|
|
m_InWorld = false;
|
|
|
|
|
2010-02-03 00:01:17 +01:00
|
|
|
m_YOffset = paramNode.GetChild("Altitude").ToFixed();
|
|
|
|
m_Floating = paramNode.GetChild("Floating").ToBool();
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-02-10 20:28:46 +01:00
|
|
|
m_RotYSpeed = 6.f; // TODO: should get from template
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
m_RotX = m_RotY = m_RotZ = entity_angle_t::FromInt(0);
|
2010-02-10 20:28:46 +01:00
|
|
|
m_InterpolatedRotY = 0;
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual void Deinit(const CSimContext& UNUSED(context))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void Serialize(ISerializer& serialize)
|
|
|
|
{
|
|
|
|
serialize.Bool("in world", m_InWorld);
|
|
|
|
if (m_InWorld)
|
|
|
|
{
|
|
|
|
serialize.NumberFixed_Unbounded("x", m_X);
|
|
|
|
serialize.NumberFixed_Unbounded("z", m_Z);
|
|
|
|
serialize.NumberFixed_Unbounded("last x", m_LastX);
|
|
|
|
serialize.NumberFixed_Unbounded("last z", m_LastZ);
|
2010-02-10 20:28:46 +01:00
|
|
|
// TODO: for efficiency, we probably shouldn't actually store the last position - it doesn't
|
|
|
|
// matter if we don't have smooth interpolation after reloading a game
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
serialize.NumberFixed_Unbounded("rot x", m_RotX);
|
|
|
|
serialize.NumberFixed_Unbounded("rot y", m_RotY);
|
|
|
|
serialize.NumberFixed_Unbounded("rot z", m_RotZ);
|
|
|
|
serialize.NumberFixed_Unbounded("altitude", m_YOffset);
|
2010-01-29 22:13:18 +01:00
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
if (serialize.IsDebug())
|
|
|
|
{
|
|
|
|
const char* anchor = "???";
|
|
|
|
switch (m_AnchorType)
|
|
|
|
{
|
|
|
|
case UPRIGHT: anchor = "upright"; break;
|
|
|
|
case PITCH: anchor = "pitch"; break;
|
|
|
|
case PITCH_ROLL: anchor = "pitch-roll"; break;
|
|
|
|
}
|
|
|
|
serialize.StringASCII("anchor", anchor, 0, 16);
|
|
|
|
serialize.Bool("floating", m_Floating);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& deserialize)
|
|
|
|
{
|
|
|
|
Init(context, paramNode);
|
|
|
|
|
|
|
|
deserialize.Bool(m_InWorld);
|
|
|
|
if (m_InWorld)
|
|
|
|
{
|
|
|
|
deserialize.NumberFixed_Unbounded(m_X);
|
|
|
|
deserialize.NumberFixed_Unbounded(m_Z);
|
|
|
|
deserialize.NumberFixed_Unbounded(m_LastX);
|
|
|
|
deserialize.NumberFixed_Unbounded(m_LastZ);
|
|
|
|
}
|
|
|
|
deserialize.NumberFixed_Unbounded(m_RotX);
|
|
|
|
deserialize.NumberFixed_Unbounded(m_RotY);
|
|
|
|
deserialize.NumberFixed_Unbounded(m_RotZ);
|
|
|
|
deserialize.NumberFixed_Unbounded(m_YOffset);
|
|
|
|
// TODO: should there be range checks on all these values?
|
2010-02-10 20:28:46 +01:00
|
|
|
|
|
|
|
m_InterpolatedRotY = m_RotY.ToFloat();
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool IsInWorld()
|
|
|
|
{
|
|
|
|
return m_InWorld;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void MoveOutOfWorld()
|
|
|
|
{
|
|
|
|
m_InWorld = false;
|
2010-01-29 22:13:18 +01:00
|
|
|
|
2010-04-30 01:36:05 +02:00
|
|
|
AdvertisePositionChanges();
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual void MoveTo(entity_pos_t x, entity_pos_t z)
|
|
|
|
{
|
|
|
|
m_X = x;
|
|
|
|
m_Z = z;
|
|
|
|
|
|
|
|
if (!m_InWorld)
|
|
|
|
{
|
|
|
|
m_InWorld = true;
|
|
|
|
m_LastX = m_X;
|
|
|
|
m_LastZ = m_Z;
|
|
|
|
}
|
2010-01-29 22:13:18 +01:00
|
|
|
|
2010-04-30 01:36:05 +02:00
|
|
|
AdvertisePositionChanges();
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual void JumpTo(entity_pos_t x, entity_pos_t z)
|
|
|
|
{
|
|
|
|
m_LastX = m_X = x;
|
|
|
|
m_LastZ = m_Z = z;
|
|
|
|
m_InWorld = true;
|
2010-01-29 22:13:18 +01:00
|
|
|
|
2010-04-30 01:36:05 +02:00
|
|
|
AdvertisePositionChanges();
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual void SetHeightOffset(entity_pos_t dy)
|
|
|
|
{
|
|
|
|
m_YOffset = dy;
|
2010-01-29 22:13:18 +01:00
|
|
|
|
2010-04-30 01:36:05 +02:00
|
|
|
AdvertisePositionChanges();
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual entity_pos_t GetHeightOffset()
|
|
|
|
{
|
|
|
|
return m_YOffset;
|
|
|
|
}
|
|
|
|
|
2010-05-28 01:23:53 +02:00
|
|
|
virtual bool IsFloating()
|
|
|
|
{
|
|
|
|
return m_Floating;
|
|
|
|
}
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
virtual CFixedVector3D GetPosition()
|
|
|
|
{
|
|
|
|
if (!m_InWorld)
|
|
|
|
{
|
|
|
|
LOGERROR(L"CCmpPosition::GetPosition called on entity when IsInWorld is false");
|
|
|
|
return CFixedVector3D();
|
|
|
|
}
|
|
|
|
|
2010-05-28 01:23:53 +02:00
|
|
|
entity_pos_t baseY;
|
2010-05-01 11:48:39 +02:00
|
|
|
CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
|
2010-01-09 20:20:14 +01:00
|
|
|
if (!cmpTerrain.null())
|
2010-05-28 01:23:53 +02:00
|
|
|
baseY = cmpTerrain->GetGroundLevel(m_X, m_Z);
|
|
|
|
|
|
|
|
if (m_Floating)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-05-28 01:23:53 +02:00
|
|
|
CmpPtr<ICmpWaterManager> cmpWaterMan(GetSimContext(), SYSTEM_ENTITY);
|
|
|
|
if (!cmpWaterMan.null())
|
|
|
|
baseY = std::max(baseY, cmpWaterMan->GetWaterLevel(m_X, m_Z));
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: most callers don't actually care about Y; if this is a performance
|
|
|
|
// issue then we could add a new method that simply returns X/Z
|
|
|
|
|
2010-05-28 01:23:53 +02:00
|
|
|
return CFixedVector3D(m_X, baseY + m_YOffset, m_Z);
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
2010-02-10 20:28:46 +01:00
|
|
|
virtual void TurnTo(entity_angle_t y)
|
|
|
|
{
|
|
|
|
m_RotY = y;
|
|
|
|
|
2010-04-30 01:36:05 +02:00
|
|
|
AdvertisePositionChanges();
|
2010-02-10 20:28:46 +01:00
|
|
|
}
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
virtual void SetYRotation(entity_angle_t y)
|
|
|
|
{
|
|
|
|
m_RotY = y;
|
2010-02-10 20:28:46 +01:00
|
|
|
m_InterpolatedRotY = m_RotY.ToFloat();
|
2010-01-29 22:13:18 +01:00
|
|
|
|
2010-04-30 01:36:05 +02:00
|
|
|
AdvertisePositionChanges();
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual void SetXZRotation(entity_angle_t x, entity_angle_t z)
|
|
|
|
{
|
|
|
|
m_RotX = x;
|
|
|
|
m_RotZ = z;
|
2010-01-29 22:13:18 +01:00
|
|
|
|
2010-04-30 01:36:05 +02:00
|
|
|
AdvertisePositionChanges();
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual CFixedVector3D GetRotation()
|
|
|
|
{
|
|
|
|
return CFixedVector3D(m_RotX, m_RotY, m_RotZ);
|
|
|
|
}
|
|
|
|
|
2010-04-23 18:57:18 +02:00
|
|
|
virtual void GetInterpolatedPosition2D(float frameOffset, float& x, float& z, float& rotY)
|
|
|
|
{
|
|
|
|
if (!m_InWorld)
|
|
|
|
{
|
|
|
|
LOGERROR(L"CCmpPosition::GetInterpolatedPosition2D called on entity when IsInWorld is false");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
x = Interpolate(m_LastX.ToFloat(), m_X.ToFloat(), frameOffset);
|
|
|
|
z = Interpolate(m_LastZ.ToFloat(), m_Z.ToFloat(), frameOffset);
|
|
|
|
|
|
|
|
rotY = m_InterpolatedRotY;
|
|
|
|
}
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
virtual CMatrix3D GetInterpolatedTransform(float frameOffset)
|
|
|
|
{
|
|
|
|
if (!m_InWorld)
|
|
|
|
{
|
|
|
|
LOGERROR(L"CCmpPosition::GetInterpolatedTransform called on entity when IsInWorld is false");
|
|
|
|
CMatrix3D m;
|
|
|
|
m.SetIdentity();
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
|
2010-04-23 18:57:18 +02:00
|
|
|
float x, z, rotY;
|
|
|
|
GetInterpolatedPosition2D(frameOffset, x, z, rotY);
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-05-28 01:23:53 +02:00
|
|
|
float baseY = 0;
|
2010-05-01 11:48:39 +02:00
|
|
|
CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
|
2010-01-09 20:20:14 +01:00
|
|
|
if (!cmpTerrain.null())
|
2010-05-28 01:23:53 +02:00
|
|
|
baseY = cmpTerrain->GetExactGroundLevel(x, z);
|
|
|
|
|
|
|
|
if (m_Floating)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-05-28 01:23:53 +02:00
|
|
|
CmpPtr<ICmpWaterManager> cmpWaterMan(GetSimContext(), SYSTEM_ENTITY);
|
|
|
|
if (!cmpWaterMan.null())
|
|
|
|
baseY = std::max(baseY, cmpWaterMan->GetExactWaterLevel(x, z));
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
2010-05-28 01:23:53 +02:00
|
|
|
float y = baseY + m_YOffset.ToFloat();
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
// TODO: do something with m_AnchorType
|
|
|
|
|
|
|
|
CMatrix3D m;
|
|
|
|
CMatrix3D mXZ;
|
2010-04-23 18:57:18 +02:00
|
|
|
float Cos = cosf(rotY);
|
|
|
|
float Sin = sinf(rotY);
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
m.SetIdentity();
|
|
|
|
m._11 = -Cos;
|
|
|
|
m._13 = -Sin;
|
|
|
|
m._31 = Sin;
|
|
|
|
m._33 = -Cos;
|
|
|
|
|
|
|
|
mXZ.SetIdentity();
|
|
|
|
mXZ.SetXRotation(m_RotX.ToFloat());
|
|
|
|
mXZ.RotateZ(m_RotZ.ToFloat());
|
2010-04-23 18:57:18 +02:00
|
|
|
// TODO: is this all done in the correct order?
|
2010-01-09 20:20:14 +01:00
|
|
|
mXZ = m * mXZ;
|
|
|
|
mXZ.Translate(CVector3D(x, y, z));
|
|
|
|
|
|
|
|
return mXZ;
|
|
|
|
}
|
|
|
|
|
2010-05-01 11:48:39 +02:00
|
|
|
virtual void HandleMessage(const CSimContext& UNUSED(context), const CMessage& msg, bool UNUSED(global))
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
|
|
|
switch (msg.GetType())
|
|
|
|
{
|
2010-02-10 20:28:46 +01:00
|
|
|
case MT_Interpolate:
|
|
|
|
{
|
|
|
|
const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
|
|
|
|
|
|
|
|
float rotY = m_RotY.ToFloat();
|
|
|
|
float delta = rotY - m_InterpolatedRotY;
|
2010-03-20 21:54:03 +01:00
|
|
|
// Wrap delta to -M_PI..M_PI
|
2010-03-26 20:04:40 +01:00
|
|
|
delta = fmod(delta + (float)M_PI, 2*(float)M_PI); // range -2PI..2PI
|
|
|
|
if (delta < 0) delta += 2*(float)M_PI; // range 0..2PI
|
|
|
|
delta -= (float)M_PI; // range -M_PI..M_PI
|
2010-02-10 20:28:46 +01:00
|
|
|
// Clamp to max rate
|
|
|
|
float deltaClamped = clamp(delta, -m_RotYSpeed*msgData.frameTime, +m_RotYSpeed*msgData.frameTime);
|
|
|
|
// Calculate new orientation, in a peculiar way in order to make sure the
|
2010-03-20 21:54:03 +01:00
|
|
|
// result gets close to m_orientation (rather than being n*2*M_PI out)
|
2010-02-10 20:28:46 +01:00
|
|
|
m_InterpolatedRotY = rotY + deltaClamped - delta;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2010-01-09 20:20:14 +01:00
|
|
|
case MT_TurnStart:
|
2010-02-10 20:28:46 +01:00
|
|
|
{
|
2010-01-09 20:20:14 +01:00
|
|
|
m_LastX = m_X;
|
|
|
|
m_LastZ = m_Z;
|
2010-01-29 22:13:18 +01:00
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
break;
|
|
|
|
}
|
2010-02-10 20:28:46 +01:00
|
|
|
}
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
2010-04-30 01:36:05 +02:00
|
|
|
|
|
|
|
private:
|
|
|
|
void AdvertisePositionChanges()
|
|
|
|
{
|
|
|
|
if (m_InWorld)
|
|
|
|
{
|
|
|
|
CMessagePositionChanged msg(true, m_X, m_Z, m_RotY);
|
2010-05-01 11:48:39 +02:00
|
|
|
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
|
2010-04-30 01:36:05 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CMessagePositionChanged msg(false, entity_pos_t(), entity_pos_t(), entity_angle_t());
|
2010-05-01 11:48:39 +02:00
|
|
|
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
|
2010-04-30 01:36:05 +02:00
|
|
|
}
|
|
|
|
}
|
2010-01-09 20:20:14 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
REGISTER_COMPONENT_TYPE(Position)
|