Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
CCmpPosition.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2013 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 
21 #include "ICmpPosition.h"
22 
24 
25 #include "ICmpTerrain.h"
26 #include "ICmpWaterManager.h"
27 
28 #include "graphics/Terrain.h"
29 #include "lib/rand.h"
30 #include "maths/MathUtil.h"
31 #include "maths/Matrix3D.h"
32 #include "maths/Vector3D.h"
33 #include "maths/Vector2D.h"
34 #include "ps/CLogger.h"
35 
36 /**
37  * Basic ICmpPosition implementation.
38  */
39 class CCmpPosition : public ICmpPosition
40 {
41 public:
42  static void ClassInit(CComponentManager& componentManager)
43  {
44  componentManager.SubscribeToMessageType(MT_TurnStart);
45  componentManager.SubscribeToMessageType(MT_Interpolate);
46 
47  // TODO: if this component turns out to be a performance issue, it should
48  // be optimised by creating a new PositionStatic component that doesn't subscribe
49  // to messages and doesn't store LastX/LastZ, and that should be used for all
50  // entities that don't move
51  }
52 
54 
55  // Template state:
56 
57  enum
58  {
59  UPRIGHT = 0,
60  PITCH = 1,
62  ROLL=3,
63  } m_AnchorType;
64 
65  bool m_Floating;
66  float m_RotYSpeed; // maximum radians per second, used by InterpolatedRotY to follow RotY
67 
68  // Dynamic state:
69 
70  bool m_InWorld;
71  // m_LastX/Z contain the position from the start of the most recent turn
72  // m_PrevX/Z conatain the position from the turn before that
73  entity_pos_t m_X, m_Z, m_LastX, m_LastZ, m_PrevX, m_PrevZ; // these values contain undefined junk if !InWorld
75  bool m_RelativeToGround; // whether m_YOffset is relative to terrain/water plane, or an absolute height
76 
78 
79  // not serialized:
82 
84 
85  static std::string GetSchema()
86  {
87  return
88  "<a:help>Allows this entity to exist at a location (and orientation) in the world, and defines some details of the positioning.</a:help>"
89  "<a:example>"
90  "<Anchor>upright</Anchor>"
91  "<Altitude>0.0</Altitude>"
92  "<Floating>false</Floating>"
93  "<TurnRate>6.0</TurnRate>"
94  "</a:example>"
95  "<element name='Anchor' a:help='Automatic rotation to follow the slope of terrain'>"
96  "<choice>"
97  "<value a:help='Always stand straight up (e.g. humans)'>upright</value>"
98  "<value a:help='Rotate backwards and forwards to follow the terrain (e.g. animals)'>pitch</value>"
99  "<value a:help='Rotate sideways to follow the terrain'>roll</value>"
100  "<value a:help='Rotate in all directions to follow the terrain (e.g. carts)'>pitch-roll</value>"
101  "</choice>"
102  "</element>"
103  "<element name='Altitude' a:help='Height above terrain in metres'>"
104  "<data type='decimal'/>"
105  "</element>"
106  "<element name='Floating' a:help='Whether the entity floats on water'>"
107  "<data type='boolean'/>"
108  "</element>"
109  "<element name='TurnRate' a:help='Maximum graphical rotation speed around Y axis, in radians per second'>"
110  "<ref name='positiveDecimal'/>"
111  "</element>";
112  }
113 
114  virtual void Init(const CParamNode& paramNode)
115  {
116  std::wstring anchor = paramNode.GetChild("Anchor").ToString();
117  if (anchor == L"pitch")
119  else if (anchor == L"pitch-roll")
121  else if (anchor == L"roll")
122  m_AnchorType = ROLL;
123  else
125 
126  m_InWorld = false;
127 
128  m_LastYOffset = m_YOffset = paramNode.GetChild("Altitude").ToFixed();
129  m_RelativeToGround = true;
130  m_Floating = paramNode.GetChild("Floating").ToBool();
131 
132  m_RotYSpeed = paramNode.GetChild("TurnRate").ToFixed().ToFloat();
133 
137 
138  m_NeedInitialXZRotation = false;
139  }
140 
141  virtual void Deinit()
142  {
143  }
144 
145  virtual void Serialize(ISerializer& serialize)
146  {
147  serialize.Bool("in world", m_InWorld);
148  if (m_InWorld)
149  {
150  serialize.NumberFixed_Unbounded("x", m_X);
151  serialize.NumberFixed_Unbounded("z", m_Z);
152  serialize.NumberFixed_Unbounded("last x", m_LastX);
153  serialize.NumberFixed_Unbounded("last z", m_LastZ);
154  }
155  serialize.NumberFixed_Unbounded("rot x", m_RotX);
156  serialize.NumberFixed_Unbounded("rot y", m_RotY);
157  serialize.NumberFixed_Unbounded("rot z", m_RotZ);
158  serialize.NumberFixed_Unbounded("altitude", m_YOffset);
159  serialize.Bool("relative", m_RelativeToGround);
160 
161  if (serialize.IsDebug())
162  {
163  const char* anchor = "???";
164  switch (m_AnchorType)
165  {
166  case PITCH:
167  anchor = "pitch";
168  break;
169 
170  case PITCH_ROLL:
171  anchor = "pitch-roll";
172  break;
173 
174  case ROLL:
175  anchor = "roll";
176  break;
177 
178  case UPRIGHT: // upright is the default
179  default:
180  anchor = "upright";
181  break;
182  }
183  serialize.StringASCII("anchor", anchor, 0, 16);
184  serialize.Bool("floating", m_Floating);
185  }
186  }
187 
188  virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
189  {
190  Init(paramNode);
191 
192  deserialize.Bool("in world", m_InWorld);
193  if (m_InWorld)
194  {
195  deserialize.NumberFixed_Unbounded("x", m_X);
196  deserialize.NumberFixed_Unbounded("z", m_Z);
197  deserialize.NumberFixed_Unbounded("last x", m_LastX);
198  deserialize.NumberFixed_Unbounded("last z", m_LastZ);
199  }
200  deserialize.NumberFixed_Unbounded("rot x", m_RotX);
201  deserialize.NumberFixed_Unbounded("rot y", m_RotY);
202  deserialize.NumberFixed_Unbounded("rot z", m_RotZ);
203  deserialize.NumberFixed_Unbounded("altitude", m_YOffset);
204  deserialize.Bool("relative", m_RelativeToGround);
205  // TODO: should there be range checks on all these values?
206 
209 
210  if (m_InWorld)
212  }
213 
214  virtual bool IsInWorld()
215  {
216  return m_InWorld;
217  }
218 
219  virtual void MoveOutOfWorld()
220  {
221  m_InWorld = false;
222 
224  }
225 
226  virtual void MoveTo(entity_pos_t x, entity_pos_t z)
227  {
228  m_X = x;
229  m_Z = z;
230 
231  if (!m_InWorld)
232  {
233  m_InWorld = true;
234  m_LastX = m_PrevX = m_X;
235  m_LastZ = m_PrevZ = m_Z;
237  }
238 
240  }
241 
242  virtual void JumpTo(entity_pos_t x, entity_pos_t z)
243  {
244  m_LastX = m_PrevX = m_X = x;
245  m_LastZ = m_PrevZ = m_Z = z;
246  m_InWorld = true;
247 
249 
252 
254  }
255 
256  virtual void SetHeightOffset(entity_pos_t dy)
257  {
258  if (m_RelativeToGround)
259  {
261  m_YOffset = dy;
262  }
263  else
264  {
265  m_LastYOffset = m_YOffset = dy;
266  m_RelativeToGround = true;
267  }
268 
270  }
271 
273  {
274  return m_YOffset;
275  }
276 
277  virtual void SetHeightFixed(entity_pos_t y)
278  {
279  if (m_RelativeToGround)
280  {
281  m_LastYOffset = m_YOffset = y;
282  m_RelativeToGround = false;
283  }
284  else
285  {
287  m_YOffset = y;
288  }
289  }
290 
291  virtual bool IsFloating()
292  {
293  return m_Floating;
294  }
295 
297  {
298  if (!m_InWorld)
299  {
300  LOGERROR(L"CCmpPosition::GetPosition called on entity when IsInWorld is false");
301  return CFixedVector3D();
302  }
303 
304  entity_pos_t baseY;
305  if (m_RelativeToGround)
306  {
307  CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
308  if (cmpTerrain)
309  baseY = cmpTerrain->GetGroundLevel(m_X, m_Z);
310 
311  if (m_Floating)
312  {
313  CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
314  if (cmpWaterManager)
315  baseY = std::max(baseY, cmpWaterManager->GetWaterLevel(m_X, m_Z));
316  }
317  }
318 
319  return CFixedVector3D(m_X, baseY + m_YOffset, m_Z);
320  }
321 
323  {
324  if (!m_InWorld)
325  {
326  LOGERROR(L"CCmpPosition::GetPosition2D called on entity when IsInWorld is false");
327  return CFixedVector2D();
328  }
329 
330  return CFixedVector2D(m_X, m_Z);
331  }
332 
334  {
335  if (!m_InWorld)
336  {
337  LOGERROR(L"CCmpPosition::GetPreviousPosition called on entity when IsInWorld is false");
338  return CFixedVector3D();
339  }
340 
341  entity_pos_t baseY;
342  if (m_RelativeToGround)
343  {
344  CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
345  if (cmpTerrain)
346  baseY = cmpTerrain->GetGroundLevel(m_PrevX, m_PrevZ);
347 
348  if (m_Floating)
349  {
351  if (cmpWaterMan)
352  baseY = std::max(baseY, cmpWaterMan->GetWaterLevel(m_PrevX, m_PrevZ));
353  }
354  }
355 
356  return CFixedVector3D(m_PrevX, baseY + m_YOffset, m_PrevZ);
357  }
358 
360  {
361  if (!m_InWorld)
362  {
363  LOGERROR(L"CCmpPosition::GetPreviousPosition2D called on entity when IsInWorld is false");
364  return CFixedVector2D();
365  }
366 
367  return CFixedVector2D(m_PrevX, m_PrevZ);
368  }
369 
370  virtual void TurnTo(entity_angle_t y)
371  {
372  m_RotY = y;
373 
375  }
376 
377  virtual void SetYRotation(entity_angle_t y)
378  {
379  m_RotY = y;
381 
382  if (m_InWorld)
383  {
385 
388  }
389 
391  }
392 
394  {
395  m_RotX = x;
396  m_RotZ = z;
397 
398  if (m_InWorld)
399  {
401 
404  }
405 
407  }
408 
410  {
412  }
413 
415  {
416  if (!m_InWorld)
417  {
418  LOGERROR(L"CCmpPosition::GetDistanceTravelled called on entity when IsInWorld is false");
419  return fixed::Zero();
420  }
421 
422  return CFixedVector2D(m_X - m_LastX, m_Z - m_LastZ).Length();
423  }
424 
425  virtual void GetInterpolatedPosition2D(float frameOffset, float& x, float& z, float& rotY)
426  {
427  if (!m_InWorld)
428  {
429  LOGERROR(L"CCmpPosition::GetInterpolatedPosition2D called on entity when IsInWorld is false");
430  return;
431  }
432 
433  x = Interpolate(m_LastX.ToFloat(), m_X.ToFloat(), frameOffset);
434  z = Interpolate(m_LastZ.ToFloat(), m_Z.ToFloat(), frameOffset);
435 
436  rotY = m_InterpolatedRotY;
437  }
438 
439  virtual CMatrix3D GetInterpolatedTransform(float frameOffset, bool forceFloating)
440  {
441  if (!m_InWorld)
442  {
443  LOGERROR(L"CCmpPosition::GetInterpolatedTransform called on entity when IsInWorld is false");
444  CMatrix3D m;
445  m.SetIdentity();
446  return m;
447  }
448 
449  float x, z, rotY;
450  GetInterpolatedPosition2D(frameOffset, x, z, rotY);
451 
452  float baseY = 0;
453  if (m_RelativeToGround)
454  {
455  CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
456  if (cmpTerrain)
457  baseY = cmpTerrain->GetExactGroundLevel(x, z);
458 
459  if (m_Floating || forceFloating)
460  {
461  CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
462  if (cmpWaterManager)
463  baseY = std::max(baseY, cmpWaterManager->GetExactWaterLevel(x, z));
464  }
465  }
466 
467  float y = baseY + Interpolate(m_LastYOffset.ToFloat(), m_YOffset.ToFloat(), frameOffset);
468 
469  CMatrix3D m;
470 
471  // linear interpolation is good enough (for RotX/Z).
472  // As you always stay close to zero angle.
474  m.RotateZ(Interpolate(m_LastInterpolatedRotZ, m_InterpolatedRotZ, frameOffset));
475 
476  m.RotateY(rotY + (float)M_PI);
477  m.Translate(CVector3D(x, y, z));
478 
479  return m;
480  }
481 
482  virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
483  {
484  switch (msg.GetType())
485  {
486  case MT_Interpolate:
487  {
488  const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
489 
490  float rotY = m_RotY.ToFloat();
491 
492  if (rotY != m_InterpolatedRotY)
493  {
494  float delta = rotY - m_InterpolatedRotY;
495  // Wrap delta to -M_PI..M_PI
496  delta = fmodf(delta + (float)M_PI, 2*(float)M_PI); // range -2PI..2PI
497  if (delta < 0) delta += 2*(float)M_PI; // range 0..2PI
498  delta -= (float)M_PI; // range -M_PI..M_PI
499  // Clamp to max rate
500  float deltaClamped = clamp(delta, -m_RotYSpeed*msgData.deltaSimTime, +m_RotYSpeed*msgData.deltaSimTime);
501  // Calculate new orientation, in a peculiar way in order to make sure the
502  // result gets close to m_orientation (rather than being n*2*M_PI out)
503  m_InterpolatedRotY = rotY + deltaClamped - delta;
504 
505  // update the visual XZ rotation
506  if (m_InWorld)
507  {
510 
512  }
513  }
514 
516  {
517  // the terrain probably wasn't loaded last time we tried, so update the XZ rotation without interpolation
519 
522  }
523 
524  break;
525  }
526  case MT_TurnStart:
527  {
530 
531  if (m_InWorld && (m_LastX != m_X || m_LastZ != m_Z))
533 
534  // Store the positions from the turn before
535  m_PrevX = m_LastX;
536  m_PrevZ = m_LastZ;
537 
538  m_LastX = m_X;
539  m_LastZ = m_Z;
541 
542  break;
543  }
544  }
545 };
546 
547 private:
549  {
550  if (m_InWorld)
551  {
554  }
555  else
556  {
559  }
560  }
561 
563  {
564  if (!m_InWorld)
565  {
566  LOGERROR(L"CCmpPosition::UpdateXZRotation called on entity when IsInWorld is false");
567  return;
568  }
569 
570  if (m_AnchorType == UPRIGHT || !m_RotZ.IsZero() || !m_RotX.IsZero())
571  {
572  // set the visual rotations to the ones fixed by the interface
575  return;
576  }
577 
578  CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
579  if (!cmpTerrain || !cmpTerrain->IsLoaded())
580  {
581  // try again when terrain is loaded
583  return;
584  }
585 
586  // TODO: average normal (average all the tiles?) for big units or for buildings?
587  CVector3D normal = cmpTerrain->CalcExactNormal(m_X.ToFloat(), m_Z.ToFloat());
588 
589  // rotate the normal so the positive x direction is in the direction of the unit
590  CVector2D projected = CVector2D(normal.X, normal.Z);
591  projected.Rotate(m_InterpolatedRotY);
592 
593  normal.X = projected.X;
594  normal.Z = projected.Y;
595 
596  // project and calculate the angles
598  m_InterpolatedRotX = -atan2(normal.Z, normal.Y);
599 
601  m_InterpolatedRotZ = atan2(normal.X, normal.Y);
602 
603  m_NeedInitialXZRotation = false;
604 
605  return;
606  }
607 };
608 
609 REGISTER_COMPONENT_TYPE(Position)
#define M_PI
Definition: wposix.h:64
An entity initialisation parameter node.
Definition: ParamNode.h:112
virtual CFixedVector3D GetPreviousPosition()
Returns the previous turn&#39;s x,y,z position (no interpolation).
float m_InterpolatedRotX
void SubscribeToMessageType(MessageTypeId mtid)
Subscribe the current component type to the given message type.
A simple fixed-point number class.
Definition: Fixed.h:115
virtual entity_pos_t GetGroundLevel(entity_pos_t x, entity_pos_t z)=0
bool m_NeedInitialXZRotation
entity_pos_t m_LastYOffset
virtual entity_pos_t GetWaterLevel(entity_pos_t x, entity_pos_t z)=0
Get the current water level at the given point.
#define REGISTER_COMPONENT_TYPE(cname)
Definition: Component.h:30
virtual CFixedVector3D GetPosition()
Returns the current x,y,z position (no interpolation).
void NumberFixed_Unbounded(const char *name, fixed value)
Serialize a number.
Definition: ISerializer.h:191
entity_pos_t m_LastX
virtual void Bool(const char *name, bool &out)
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
bool ToBool() const
Parses the content of this node as a boolean (&quot;true&quot; == true, anything else == false) ...
Definition: ParamNode.cpp:236
T Interpolate(const T &a, const T &b, float l)
Definition: MathUtil.h:26
float X
Definition: Vector2D.h:157
static CFixed Zero()
Definition: Fixed.h:127
const std::wstring & ToString() const
Returns the content of this node as a string.
Definition: ParamNode.cpp:198
entity_pos_t m_LastZ
static std::string GetSchema()
#define LOGERROR
Definition: CLogger.h:35
virtual bool IsDebug() const
Returns true if the serializer is being used in debug mode.
void Rotate(float angle)
Rotates this vector counterclockwise by angle radians.
Definition: Vector2D.h:146
Serialization interface; see serialization overview.
Definition: ISerializer.h:120
virtual float GetExactGroundLevel(float x, float z)=0
virtual CMatrix3D GetInterpolatedTransform(float frameOffset, bool forceFloating)
Get the current interpolated transform matrix, for rendering.
virtual void SetHeightFixed(entity_pos_t y)
Set the vertical position as a fixed, absolute value.
Represents an entity&#39;s position in the world (plus its orientation).
Definition: ICmpPosition.h:58
virtual bool IsInWorld()
Returns true if the entity currently exists at a defined position in the world.
virtual void NumberFixed_Unbounded(const char *name, fixed &out)
float deltaSimTime
Elapsed simulation time since previous interpolate, in seconds.
Definition: MessageTypes.h:134
virtual void Deserialize(const CParamNode &paramNode, IDeserializer &deserialize)
fixed ToFixed() const
Parses the content of this node as a fixed-point number.
Definition: ParamNode.cpp:222
virtual CFixedVector3D GetRotation()
Returns the current rotation (relative to the upwards axis), as Euler angles with X=pitch...
entity_id_t GetEntityId() const
Definition: IComponent.h:48
virtual int GetType() const =0
const CParamNode & GetChild(const char *name) const
Returns the (unique) child node with the given name, or a node with IsOk() == false if there is none...
Definition: ParamNode.cpp:185
void UpdateXZRotation()
virtual CVector3D CalcExactNormal(float x, float z)=0
virtual void Init(const CParamNode &paramNode)
float X
Definition: Vector3D.h:31
void StringASCII(const char *name, const std::string &value, uint32_t minlength, uint32_t maxlength)
Serialize an ASCII string.
Definition: ISerializer.cpp:70
virtual void JumpTo(entity_pos_t x, entity_pos_t z)
Move immediately to the given location, with no interpolation.
virtual void SetXZRotation(entity_angle_t x, entity_angle_t z)
Rotate immediately to the given angles around the X (pitch) and Z (roll) axes.
float Y
Definition: Vector3D.h:31
float ToFloat() const
Convert to float. May be lossy - float can&#39;t represent all values.
Definition: Fixed.h:161
float m_InterpolatedRotZ
virtual void TurnTo(entity_angle_t y)
Rotate smoothly to the given angle around the upwards axis.
static void ClassInit(CComponentManager &componentManager)
float m_InterpolatedRotY
void SetIdentity()
Definition: Matrix3D.cpp:30
virtual float GetExactWaterLevel(float x, float z)=0
Get the current water level at the given point.
Basic ICmpPosition implementation.
void Bool(const char *name, bool value)
Serialize a boolean.
Definition: ISerializer.h:199
#define DEFAULT_COMPONENT_ALLOCATOR(cname)
Definition: Component.h:44
virtual void SetHeightOffset(entity_pos_t dy)
Set the vertical offset above the terrain/water surface.
const CSimContext & GetSimContext() const
Definition: IComponent.h:52
Sent during TurnStart.
Definition: MessageTypes.h:245
A simplified syntax for accessing entity components.
Definition: CmpPtr.h:55
float Y
Definition: Vector2D.h:157
CEntityHandle GetSystemEntity() const
Definition: IComponent.h:50
static CFixed FromInt(int n)
Definition: Fixed.h:136
virtual void Serialize(ISerializer &serialize)
entity_angle_t m_RotY
void PostMessage(entity_id_t ent, const CMessage &msg) const
Send a message, targeted at a particular entity.
fixed Length() const
Returns the length of the vector.
Definition: FixedVector2D.h:95
virtual void SetYRotation(entity_angle_t y)
Rotate immediately to the given angle around the upwards axis.
virtual CFixedVector2D GetPreviousPosition2D()
Returns the previous turn&#39;s x,z position (no interpolation).
virtual void MoveOutOfWorld()
Causes IsInWorld to return false.
virtual void MoveTo(entity_pos_t x, entity_pos_t z)
Move smoothly to the given location.
entity_angle_t m_RotX
virtual void HandleMessage(const CMessage &msg, bool global)
bool IsZero() const
Returns true if the number is precisely 0.
Definition: Fixed.h:199
entity_pos_t m_YOffset
entity_angle_t m_RotZ
virtual bool IsFloating()
Returns whether the entity floats on water.
virtual CFixedVector2D GetPosition2D()
Returns the current x,z position (no interpolation).
float m_LastInterpolatedRotZ
entity_pos_t m_PrevZ
void SetXRotation(float angle)
Definition: Matrix3D.cpp:61
virtual void GetInterpolatedPosition2D(float frameOffset, float &x, float &z, float &rotY)
Get the current interpolated 2D position and orientation, for rendering.
virtual bool IsLoaded()=0
float Z
Definition: Vector3D.h:31
bool m_RelativeToGround
virtual fixed GetDistanceTravelled()
Returns the distance that the unit will be interpolated over, i.e.
entity_pos_t m_PrevX
Prepare for rendering a new frame (set up model positions etc).
Definition: MessageTypes.h:122
entity_pos_t m_Z
T clamp(T value, T min, T max)
Definition: MathUtil.h:32
entity_pos_t m_X
float m_LastInterpolatedRotX
CComponentManager & GetComponentManager() const
Definition: SimContext.cpp:35
virtual void Deinit()
void AdvertisePositionChanges()
Deserialization interface; see serialization overview.
Definition: IDeserializer.h:34
virtual entity_pos_t GetHeightOffset()
Returns the current vertical offset above the terrain/water surface.
enum CCmpPosition::@56 m_AnchorType