LCOV - code coverage report
Current view: top level - source/simulation2/components - CCmpPosition.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 294 504 58.3 %
Date: 2023-01-19 00:18:29 Functions: 40 55 72.7 %

          Line data    Source code
       1             : /* Copyright (C) 2022 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             : 
      20             : #include "simulation2/system/Component.h"
      21             : #include "ICmpPosition.h"
      22             : 
      23             : #include "simulation2/MessageTypes.h"
      24             : #include "simulation2/serialization/SerializedTypes.h"
      25             : 
      26             : #include "ICmpTerrain.h"
      27             : #include "ICmpTerritoryManager.h"
      28             : #include "ICmpVisual.h"
      29             : #include "ICmpWaterManager.h"
      30             : 
      31             : #include "graphics/Terrain.h"
      32             : #include "lib/rand.h"
      33             : #include "maths/MathUtil.h"
      34             : #include "maths/Matrix3D.h"
      35             : #include "maths/Vector3D.h"
      36             : #include "maths/Vector2D.h"
      37             : #include "ps/CLogger.h"
      38             : #include "ps/Profile.h"
      39             : 
      40             : /**
      41             :  * Basic ICmpPosition implementation.
      42             :  */
      43          18 : class CCmpPosition final : public ICmpPosition
      44             : {
      45             : public:
      46         116 :     static void ClassInit(CComponentManager& componentManager)
      47             :     {
      48         116 :         componentManager.SubscribeToMessageType(MT_TurnStart);
      49         116 :         componentManager.SubscribeToMessageType(MT_TerrainChanged);
      50         116 :         componentManager.SubscribeToMessageType(MT_WaterChanged);
      51         116 :         componentManager.SubscribeToMessageType(MT_Deserialized);
      52             : 
      53             :         // TODO: if this component turns out to be a performance issue, it should
      54             :         // be optimised by creating a new PositionStatic component that doesn't subscribe
      55             :         // to messages and doesn't store LastX/LastZ, and that should be used for all
      56             :         // entities that don't move
      57         116 :     }
      58             : 
      59          12 :     DEFAULT_COMPONENT_ALLOCATOR(Position)
      60             : 
      61             :     // Template state:
      62             : 
      63             :     enum
      64             :     {
      65             :         UPRIGHT = 0,
      66             :         PITCH = 1,
      67             :         PITCH_ROLL = 2,
      68             :         ROLL = 3,
      69             :     } m_AnchorType;
      70             : 
      71             :     bool m_Floating;
      72             :     entity_pos_t m_FloatDepth;
      73             : 
      74             :     // Maximum radians per second, used by InterpolatedRotY to follow RotY and the unitMotion.
      75             :     fixed m_RotYSpeed;
      76             : 
      77             :     // Dynamic state:
      78             : 
      79             :     bool m_InWorld;
      80             :     // m_LastX/Z contain the position from the start of the most recent turn
      81             :     // m_PrevX/Z conatain the position from the turn before that
      82             :     entity_pos_t m_X, m_Z, m_LastX, m_LastZ, m_PrevX, m_PrevZ; // these values contain undefined junk if !InWorld
      83             : 
      84             :     entity_pos_t m_Y, m_LastYDifference; // either the relative or the absolute Y coordinate
      85             :     bool m_RelativeToGround; // whether m_Y is relative to terrain/water plane, or an absolute height
      86             : 
      87             :     fixed m_ConstructionProgress;
      88             : 
      89             :     // when the entity is a turret, only m_RotY is used, and this is the rotation
      90             :     // relative to the parent entity
      91             :     entity_angle_t m_RotX, m_RotY, m_RotZ;
      92             : 
      93             :     player_id_t m_Territory;
      94             : 
      95             :     entity_id_t m_TurretParent;
      96             :     CFixedVector3D m_TurretPosition;
      97             :     std::set<entity_id_t> m_Turrets;
      98             : 
      99             :     // Not serialized:
     100             :     float m_InterpolatedRotX, m_InterpolatedRotY, m_InterpolatedRotZ;
     101             :     float m_LastInterpolatedRotX, m_LastInterpolatedRotZ;
     102             :     bool m_ActorFloating;
     103             : 
     104             :     bool m_EnabledMessageInterpolate;
     105             : 
     106         116 :     static std::string GetSchema()
     107             :     {
     108             :         return
     109             :             "<a:help>Allows this entity to exist at a location (and orientation) in the world, and defines some details of the positioning.</a:help>"
     110             :             "<a:example>"
     111             :                 "<Anchor>upright</Anchor>"
     112             :                 "<Altitude>0.0</Altitude>"
     113             :                 "<Floating>false</Floating>"
     114             :                 "<FloatDepth>0.0</FloatDepth>"
     115             :                 "<TurnRate>6.0</TurnRate>"
     116             :             "</a:example>"
     117             :             "<element name='Anchor' a:help='Automatic rotation to follow the slope of terrain'>"
     118             :                 "<choice>"
     119             :                     "<value a:help='Always stand straight up (e.g. humans)'>upright</value>"
     120             :                     "<value a:help='Rotate backwards and forwards to follow the terrain (e.g. animals)'>pitch</value>"
     121             :                     "<value a:help='Rotate sideways to follow the terrain'>roll</value>"
     122             :                     "<value a:help='Rotate in all directions to follow the terrain (e.g. carts)'>pitch-roll</value>"
     123             :                 "</choice>"
     124             :             "</element>"
     125             :             "<element name='Altitude' a:help='Height above terrain in metres'>"
     126             :                 "<data type='decimal'/>"
     127             :             "</element>"
     128             :             "<element name='Floating' a:help='Whether the entity floats on water'>"
     129             :                 "<data type='boolean'/>"
     130             :             "</element>"
     131             :             "<element name='FloatDepth' a:help='The depth at which an entity floats on water (needs Floating to be true)'>"
     132             :                 "<ref name='nonNegativeDecimal'/>"
     133             :             "</element>"
     134             :             "<element name='TurnRate' a:help='Maximum rotation speed around Y axis, in radians per second. Used for all graphical rotations and some real unitMotion driven rotations.'>"
     135             :                 "<ref name='positiveDecimal'/>"
     136         116 :             "</element>";
     137             :     }
     138             : 
     139           6 :     void Init(const CParamNode& paramNode) override
     140             :     {
     141           6 :         const std::string& anchor = paramNode.GetChild("Anchor").ToString();
     142           6 :         if (anchor == "pitch")
     143           0 :             m_AnchorType = PITCH;
     144           6 :         else if (anchor == "pitch-roll")
     145           0 :             m_AnchorType = PITCH_ROLL;
     146           6 :         else if (anchor == "roll")
     147           0 :             m_AnchorType = ROLL;
     148             :         else
     149           6 :             m_AnchorType = UPRIGHT;
     150             : 
     151           6 :         m_InWorld = false;
     152             : 
     153           6 :         m_LastYDifference = entity_pos_t::Zero();
     154           6 :         m_Y = paramNode.GetChild("Altitude").ToFixed();
     155           6 :         m_RelativeToGround = true;
     156           6 :         m_Floating = paramNode.GetChild("Floating").ToBool();
     157           6 :         m_FloatDepth = paramNode.GetChild("FloatDepth").ToFixed();
     158             : 
     159           6 :         m_RotYSpeed = paramNode.GetChild("TurnRate").ToFixed();
     160             : 
     161           6 :         m_RotX = m_RotY = m_RotZ = entity_angle_t::FromInt(0);
     162           6 :         m_InterpolatedRotX = m_InterpolatedRotY = m_InterpolatedRotZ = 0.f;
     163           6 :         m_LastInterpolatedRotX = m_LastInterpolatedRotZ = 0.f;
     164           6 :         m_Territory = INVALID_PLAYER;
     165             : 
     166           6 :         m_TurretParent = INVALID_ENTITY;
     167           6 :         m_TurretPosition = CFixedVector3D();
     168             : 
     169           6 :         m_ActorFloating = false;
     170             : 
     171           6 :         m_EnabledMessageInterpolate = false;
     172           6 :     }
     173             : 
     174           6 :     void Deinit() override
     175             :     {
     176           6 :     }
     177             : 
     178          18 :     void Serialize(ISerializer& serialize) override
     179             :     {
     180          18 :         serialize.Bool("in world", m_InWorld);
     181          18 :         if (m_InWorld)
     182             :         {
     183           6 :             serialize.NumberFixed_Unbounded("x", m_X);
     184           6 :             serialize.NumberFixed_Unbounded("y", m_Y);
     185           6 :             serialize.NumberFixed_Unbounded("z", m_Z);
     186           6 :             serialize.NumberFixed_Unbounded("last x", m_LastX);
     187           6 :             serialize.NumberFixed_Unbounded("last y diff", m_LastYDifference);
     188           6 :             serialize.NumberFixed_Unbounded("last z", m_LastZ);
     189             :         }
     190          18 :         serialize.NumberI32_Unbounded("territory", m_Territory);
     191          18 :         serialize.NumberFixed_Unbounded("rot x", m_RotX);
     192          18 :         serialize.NumberFixed_Unbounded("rot y", m_RotY);
     193          18 :         serialize.NumberFixed_Unbounded("rot z", m_RotZ);
     194          18 :         serialize.NumberFixed_Unbounded("rot y speed", m_RotYSpeed);
     195          18 :         serialize.NumberFixed_Unbounded("altitude", m_Y);
     196          18 :         serialize.Bool("relative", m_RelativeToGround);
     197          18 :         serialize.Bool("floating", m_Floating);
     198          18 :         serialize.NumberFixed_Unbounded("float depth", m_FloatDepth);
     199          18 :         serialize.NumberFixed_Unbounded("constructionprogress", m_ConstructionProgress);
     200             : 
     201          18 :         if (serialize.IsDebug())
     202             :         {
     203           6 :             const char* anchor = "???";
     204           6 :             switch (m_AnchorType)
     205             :             {
     206           0 :             case PITCH:
     207           0 :                 anchor = "pitch";
     208           0 :                 break;
     209             : 
     210           0 :             case PITCH_ROLL:
     211           0 :                 anchor = "pitch-roll";
     212           0 :                 break;
     213             : 
     214           0 :             case ROLL:
     215           0 :                 anchor = "roll";
     216           0 :                 break;
     217             : 
     218           6 :             case UPRIGHT: // upright is the default
     219             :             default:
     220           6 :                 anchor = "upright";
     221           6 :                 break;
     222             :             }
     223           6 :             serialize.StringASCII("anchor", anchor, 0, 16);
     224             :         }
     225          18 :         serialize.NumberU32_Unbounded("turret parent", m_TurretParent);
     226          18 :         if (m_TurretParent != INVALID_ENTITY)
     227             :         {
     228           0 :             serialize.NumberFixed_Unbounded("x", m_TurretPosition.X);
     229           0 :             serialize.NumberFixed_Unbounded("y", m_TurretPosition.Y);
     230           0 :             serialize.NumberFixed_Unbounded("z", m_TurretPosition.Z);
     231             :         }
     232          18 :         Serializer(serialize, "turrets", m_Turrets);
     233          18 :     }
     234             : 
     235           3 :     void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) override
     236             :     {
     237           3 :         Init(paramNode);
     238             : 
     239           3 :         deserialize.Bool("in world", m_InWorld);
     240           3 :         if (m_InWorld)
     241             :         {
     242           1 :             deserialize.NumberFixed_Unbounded("x", m_X);
     243           1 :             deserialize.NumberFixed_Unbounded("y", m_Y);
     244           1 :             deserialize.NumberFixed_Unbounded("z", m_Z);
     245           1 :             deserialize.NumberFixed_Unbounded("last x", m_LastX);
     246           1 :             deserialize.NumberFixed_Unbounded("last y diff", m_LastYDifference);
     247           1 :             deserialize.NumberFixed_Unbounded("last z", m_LastZ);
     248             :         }
     249           3 :         deserialize.NumberI32_Unbounded("territory", m_Territory);
     250           3 :         deserialize.NumberFixed_Unbounded("rot x", m_RotX);
     251           3 :         deserialize.NumberFixed_Unbounded("rot y", m_RotY);
     252           3 :         deserialize.NumberFixed_Unbounded("rot z", m_RotZ);
     253           3 :         deserialize.NumberFixed_Unbounded("rot y speed", m_RotYSpeed);
     254           3 :         deserialize.NumberFixed_Unbounded("altitude", m_Y);
     255           3 :         deserialize.Bool("relative", m_RelativeToGround);
     256           3 :         deserialize.Bool("floating", m_Floating);
     257           3 :         deserialize.NumberFixed_Unbounded("float depth", m_FloatDepth);
     258           3 :         deserialize.NumberFixed_Unbounded("constructionprogress", m_ConstructionProgress);
     259             :         // TODO: should there be range checks on all these values?
     260             : 
     261           3 :         m_InterpolatedRotY = m_RotY.ToFloat();
     262             : 
     263           3 :         deserialize.NumberU32_Unbounded("turret parent", m_TurretParent);
     264           3 :         if (m_TurretParent != INVALID_ENTITY)
     265             :         {
     266           0 :             deserialize.NumberFixed_Unbounded("x", m_TurretPosition.X);
     267           0 :             deserialize.NumberFixed_Unbounded("y", m_TurretPosition.Y);
     268           0 :             deserialize.NumberFixed_Unbounded("z", m_TurretPosition.Z);
     269             :         }
     270           3 :         Serializer(deserialize, "turrets", m_Turrets);
     271             : 
     272           3 :         if (m_InWorld)
     273           1 :             UpdateXZRotation();
     274             : 
     275           3 :         UpdateMessageSubscriptions();
     276           3 :     }
     277             : 
     278           0 :     void Deserialized()
     279             :     {
     280           0 :         AdvertiseInterpolatedPositionChanges();
     281           0 :     }
     282             : 
     283           0 :     void UpdateTurretPosition() override
     284             :     {
     285           0 :         if (m_TurretParent == INVALID_ENTITY)
     286           0 :             return;
     287           0 :         CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
     288           0 :         if (!cmpPosition)
     289             :         {
     290           0 :             LOGERROR("Turret with parent without position component");
     291           0 :             return;
     292             :         }
     293           0 :         if (!cmpPosition->IsInWorld())
     294           0 :             MoveOutOfWorld();
     295             :         else
     296             :         {
     297           0 :             CFixedVector2D rotatedPosition = CFixedVector2D(m_TurretPosition.X, m_TurretPosition.Z);
     298           0 :             rotatedPosition = rotatedPosition.Rotate(cmpPosition->GetRotation().Y);
     299           0 :             CFixedVector2D rootPosition = cmpPosition->GetPosition2D();
     300           0 :             entity_pos_t x = rootPosition.X + rotatedPosition.X;
     301           0 :             entity_pos_t z = rootPosition.Y + rotatedPosition.Y;
     302           0 :             if (!m_InWorld || m_X != x || m_Z != z)
     303           0 :                 MoveTo(x, z);
     304           0 :             entity_pos_t y = cmpPosition->GetHeightOffset() + m_TurretPosition.Y;
     305           0 :             if (!m_InWorld || GetHeightOffset() != y)
     306           0 :                 SetHeightOffset(y);
     307           0 :             m_InWorld = true;
     308             :         }
     309             :     }
     310             : 
     311           0 :     std::set<entity_id_t>* GetTurrets() override
     312             :     {
     313           0 :         return &m_Turrets;
     314             :     }
     315             : 
     316           0 :     void SetTurretParent(entity_id_t id, const CFixedVector3D& offset) override
     317             :     {
     318           0 :         entity_angle_t angle = GetRotation().Y;
     319           0 :         if (m_TurretParent != INVALID_ENTITY)
     320             :         {
     321           0 :             CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
     322           0 :             if (cmpPosition)
     323           0 :                 cmpPosition->GetTurrets()->erase(GetEntityId());
     324             :         }
     325             : 
     326           0 :         m_TurretParent = id;
     327           0 :         m_TurretPosition = offset;
     328             : 
     329           0 :         if (m_TurretParent != INVALID_ENTITY)
     330             :         {
     331           0 :             CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
     332           0 :             if (cmpPosition)
     333           0 :                 cmpPosition->GetTurrets()->insert(GetEntityId());
     334             :         }
     335           0 :         SetYRotation(angle);
     336           0 :         UpdateTurretPosition();
     337           0 :     }
     338             : 
     339           0 :     entity_id_t GetTurretParent() const override
     340             :     {
     341           0 :         return m_TurretParent;
     342             :     }
     343             : 
     344           6 :     bool IsInWorld() const override
     345             :     {
     346           6 :         return m_InWorld;
     347             :     }
     348             : 
     349           2 :     void MoveOutOfWorld() override
     350             :     {
     351           2 :         m_InWorld = false;
     352             : 
     353           2 :         AdvertisePositionChanges();
     354           2 :         AdvertiseInterpolatedPositionChanges();
     355           2 :     }
     356             : 
     357           6 :     void MoveTo(entity_pos_t x, entity_pos_t z) override
     358             :     {
     359           6 :         m_X = x;
     360           6 :         m_Z = z;
     361             : 
     362           6 :         if (!m_InWorld)
     363             :         {
     364           1 :             m_InWorld = true;
     365           1 :             m_LastX = m_PrevX = m_X;
     366           1 :             m_LastZ = m_PrevZ = m_Z;
     367           1 :             m_LastYDifference = entity_pos_t::Zero();
     368             :         }
     369             : 
     370           6 :         AdvertisePositionChanges();
     371           6 :         AdvertiseInterpolatedPositionChanges();
     372           6 :     }
     373             : 
     374           0 :     void MoveAndTurnTo(entity_pos_t x, entity_pos_t z, entity_angle_t ry) override
     375             :     {
     376           0 :         m_X = x;
     377           0 :         m_Z = z;
     378             : 
     379           0 :         if (!m_InWorld)
     380             :         {
     381           0 :             m_InWorld = true;
     382           0 :             m_LastX = m_PrevX = m_X;
     383           0 :             m_LastZ = m_PrevZ = m_Z;
     384           0 :             m_LastYDifference = entity_pos_t::Zero();
     385             :         }
     386             : 
     387             :         // TurnTo will advertise the position changes
     388           0 :         TurnTo(ry);
     389             : 
     390           0 :         AdvertiseInterpolatedPositionChanges();
     391           0 :     }
     392             : 
     393           4 :     void JumpTo(entity_pos_t x, entity_pos_t z) override
     394             :     {
     395           4 :         m_LastX = m_PrevX = m_X = x;
     396           4 :         m_LastZ = m_PrevZ = m_Z = z;
     397           4 :         m_InWorld = true;
     398             : 
     399           4 :         UpdateXZRotation();
     400             : 
     401           4 :         m_LastInterpolatedRotX = m_InterpolatedRotX;
     402           4 :         m_LastInterpolatedRotZ = m_InterpolatedRotZ;
     403             : 
     404           4 :         AdvertisePositionChanges();
     405           4 :         AdvertiseInterpolatedPositionChanges();
     406           4 :     }
     407             : 
     408           4 :     void SetHeightOffset(entity_pos_t dy) override
     409             :     {
     410             :         // subtract the offset and replace with a new offset
     411           4 :         m_LastYDifference = dy - GetHeightOffset();
     412           4 :         m_Y += m_LastYDifference;
     413           4 :         AdvertiseInterpolatedPositionChanges();
     414           4 :     }
     415             : 
     416          14 :     entity_pos_t GetHeightOffset() const override
     417             :     {
     418          14 :         if (m_RelativeToGround)
     419          10 :             return m_Y;
     420             :         // not relative to the ground, so the height offset is m_Y - ground height
     421             :         // except when floating, when the height offset is m_Y - water level + float depth
     422           4 :         entity_pos_t baseY;
     423           4 :         CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
     424           4 :         if (cmpTerrain)
     425           4 :             baseY = cmpTerrain->GetGroundLevel(m_X, m_Z);
     426             : 
     427           4 :         if (m_Floating)
     428             :         {
     429           3 :             CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
     430           3 :             if (cmpWaterManager)
     431           3 :                 baseY = std::max(baseY, cmpWaterManager->GetWaterLevel(m_X, m_Z) - m_FloatDepth);
     432             :         }
     433           4 :         return m_Y - baseY;
     434             :     }
     435             : 
     436           2 :     void SetHeightFixed(entity_pos_t y) override
     437             :     {
     438             :         // subtract the absolute height and replace it with a new absolute height
     439           2 :         m_LastYDifference = y - GetHeightFixed();
     440           2 :         m_Y += m_LastYDifference;
     441           2 :         AdvertiseInterpolatedPositionChanges();
     442           2 :     }
     443             : 
     444          21 :     entity_pos_t GetHeightFixed() const override
     445             :     {
     446          21 :         return GetHeightAtFixed(m_X, m_Z);
     447             :     }
     448             : 
     449          21 :     entity_pos_t GetHeightAtFixed(entity_pos_t x, entity_pos_t z) const override
     450             :     {
     451          21 :         if (!m_RelativeToGround)
     452           7 :             return m_Y;
     453             :         // relative to the ground, so the fixed height = ground height + m_Y
     454             :         // except when floating, when the fixed height = water level - float depth + m_Y
     455          14 :         entity_pos_t baseY;
     456          14 :         CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
     457          14 :         if (cmpTerrain)
     458          14 :             baseY = cmpTerrain->GetGroundLevel(x, z);
     459             : 
     460          14 :         if (m_Floating)
     461             :         {
     462           9 :             CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
     463           9 :             if (cmpWaterManager)
     464           9 :                 baseY = std::max(baseY, cmpWaterManager->GetWaterLevel(x, z) - m_FloatDepth);
     465             :         }
     466          14 :         return m_Y + baseY;
     467             :     }
     468             : 
     469           2 :     bool IsHeightRelative() const override
     470             :     {
     471           2 :         return m_RelativeToGround;
     472             :     }
     473             : 
     474           1 :     void SetHeightRelative(bool relative) override
     475             :     {
     476             :         // move y to use the right offset (from terrain or from map origin)
     477           1 :         m_Y = relative ? GetHeightOffset() : GetHeightFixed();
     478           1 :         m_RelativeToGround = relative;
     479           1 :         m_LastYDifference = entity_pos_t::Zero();
     480           1 :         AdvertiseInterpolatedPositionChanges();
     481           1 :     }
     482             : 
     483           3 :     bool CanFloat() const override
     484             :     {
     485           3 :         return m_Floating;
     486             :     }
     487             : 
     488           3 :     void SetFloating(bool flag) override
     489             :     {
     490           3 :         m_Floating = flag;
     491           3 :         AdvertiseInterpolatedPositionChanges();
     492           3 :     }
     493             : 
     494           0 :     void SetActorFloating(bool flag) override
     495             :     {
     496           0 :         m_ActorFloating = flag;
     497           0 :         AdvertiseInterpolatedPositionChanges();
     498           0 :     }
     499             : 
     500           0 :     void SetConstructionProgress(fixed progress) override
     501             :     {
     502           0 :         m_ConstructionProgress = progress;
     503           0 :         AdvertiseInterpolatedPositionChanges();
     504           0 :     }
     505             : 
     506          10 :     CFixedVector3D GetPosition() const override
     507             :     {
     508          10 :         if (!m_InWorld)
     509             :         {
     510           0 :             LOGERROR("CCmpPosition::GetPosition called on entity when IsInWorld is false");
     511           0 :             return CFixedVector3D();
     512             :         }
     513             : 
     514          10 :         return CFixedVector3D(m_X, GetHeightFixed(), m_Z);
     515             :     }
     516             : 
     517           0 :     CFixedVector2D GetPosition2D() const override
     518             :     {
     519           0 :         if (!m_InWorld)
     520             :         {
     521           0 :             LOGERROR("CCmpPosition::GetPosition2D called on entity when IsInWorld is false");
     522           0 :             return CFixedVector2D();
     523             :         }
     524             : 
     525           0 :         return CFixedVector2D(m_X, m_Z);
     526             :     }
     527             : 
     528           0 :     CFixedVector3D GetPreviousPosition() const override
     529             :     {
     530           0 :         if (!m_InWorld)
     531             :         {
     532           0 :             LOGERROR("CCmpPosition::GetPreviousPosition called on entity when IsInWorld is false");
     533           0 :             return CFixedVector3D();
     534             :         }
     535             : 
     536           0 :         return CFixedVector3D(m_PrevX, GetHeightAtFixed(m_PrevX, m_PrevZ), m_PrevZ);
     537             :     }
     538             : 
     539           0 :     CFixedVector2D GetPreviousPosition2D() const override
     540             :     {
     541           0 :         if (!m_InWorld)
     542             :         {
     543           0 :             LOGERROR("CCmpPosition::GetPreviousPosition2D called on entity when IsInWorld is false");
     544           0 :             return CFixedVector2D();
     545             :         }
     546             : 
     547           0 :         return CFixedVector2D(m_PrevX, m_PrevZ);
     548             :     }
     549             : 
     550           0 :     fixed GetTurnRate() const override
     551             :     {
     552           0 :         return m_RotYSpeed;
     553             :     }
     554             : 
     555           0 :     void TurnTo(entity_angle_t y) override
     556             :     {
     557           0 :         if (m_TurretParent != INVALID_ENTITY)
     558             :         {
     559           0 :             CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
     560           0 :             if (cmpPosition)
     561           0 :                 y -= cmpPosition->GetRotation().Y;
     562             :         }
     563           0 :         m_RotY = y;
     564             : 
     565           0 :         AdvertisePositionChanges();
     566           0 :         UpdateMessageSubscriptions();
     567           0 :     }
     568             : 
     569           1 :     void SetYRotation(entity_angle_t y) override
     570             :     {
     571           1 :         if (m_TurretParent != INVALID_ENTITY)
     572             :         {
     573           0 :             CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
     574           0 :             if (cmpPosition)
     575           0 :                 y -= cmpPosition->GetRotation().Y;
     576             :         }
     577           1 :         m_RotY = y;
     578           1 :         m_InterpolatedRotY = m_RotY.ToFloat();
     579             : 
     580           1 :         if (m_InWorld)
     581             :         {
     582           0 :             UpdateXZRotation();
     583             : 
     584           0 :             m_LastInterpolatedRotX = m_InterpolatedRotX;
     585           0 :             m_LastInterpolatedRotZ = m_InterpolatedRotZ;
     586             :         }
     587             : 
     588           1 :         AdvertisePositionChanges();
     589           1 :         UpdateMessageSubscriptions();
     590           1 :     }
     591             : 
     592           1 :     void SetXZRotation(entity_angle_t x, entity_angle_t z) override
     593             :     {
     594           1 :         m_RotX = x;
     595           1 :         m_RotZ = z;
     596             : 
     597           1 :         if (m_InWorld)
     598             :         {
     599           0 :             UpdateXZRotation();
     600             : 
     601           0 :             m_LastInterpolatedRotX = m_InterpolatedRotX;
     602           0 :             m_LastInterpolatedRotZ = m_InterpolatedRotZ;
     603             :         }
     604           1 :     }
     605             : 
     606           1 :     CFixedVector3D GetRotation() const override
     607             :     {
     608           1 :         entity_angle_t y = m_RotY;
     609           1 :         if (m_TurretParent != INVALID_ENTITY)
     610             :         {
     611           0 :             CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
     612           0 :             if (cmpPosition)
     613           0 :                 y += cmpPosition->GetRotation().Y;
     614             :         }
     615           1 :         return CFixedVector3D(m_RotX, y, m_RotZ);
     616             :     }
     617             : 
     618           0 :     fixed GetDistanceTravelled() const override
     619             :     {
     620           0 :         if (!m_InWorld)
     621             :         {
     622           0 :             LOGERROR("CCmpPosition::GetDistanceTravelled called on entity when IsInWorld is false");
     623           0 :             return fixed::Zero();
     624             :         }
     625             : 
     626           0 :         return CFixedVector2D(m_X - m_LastX, m_Z - m_LastZ).Length();
     627             :     }
     628             : 
     629          51 :     float GetConstructionProgressOffset(const CVector3D& pos) const
     630             :     {
     631          51 :         if (m_ConstructionProgress.IsZero())
     632          51 :             return 0.0f;
     633             : 
     634           0 :         CmpPtr<ICmpVisual> cmpVisual(GetEntityHandle());
     635           0 :         if (!cmpVisual)
     636           0 :             return 0.0f;
     637             : 
     638             :         // We use selection boxes to calculate the model size, since the model could be offset
     639             :         // TODO: this annoyingly shows decals, would be nice to hide them
     640           0 :         CBoundingBoxOriented bounds = cmpVisual->GetSelectionBox();
     641           0 :         if (bounds.IsEmpty())
     642           0 :             return 0.0f;
     643             : 
     644           0 :         float dy = 2.0f * bounds.m_HalfSizes.Y;
     645             : 
     646             :         // If this is a floating unit, we want it to start all the way under the terrain,
     647             :         // so find the difference between its current position and the terrain
     648             : 
     649           0 :         CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
     650           0 :         if (cmpTerrain && (m_Floating || m_ActorFloating))
     651             :         {
     652           0 :             float ground = cmpTerrain->GetExactGroundLevel(pos.X, pos.Z);
     653           0 :             dy += std::max(0.f, pos.Y - ground);
     654             :         }
     655             : 
     656           0 :         return (m_ConstructionProgress.ToFloat() - 1.0f) * dy;
     657             :     }
     658             : 
     659          15 :     void GetInterpolatedPosition2D(float frameOffset, float& x, float& z, float& rotY) const override
     660             :     {
     661          15 :         if (!m_InWorld)
     662             :         {
     663           0 :             LOGERROR("CCmpPosition::GetInterpolatedPosition2D called on entity when IsInWorld is false");
     664           0 :             return;
     665             :         }
     666             : 
     667          15 :         x = Interpolate(m_LastX.ToFloat(), m_X.ToFloat(), frameOffset);
     668          15 :         z = Interpolate(m_LastZ.ToFloat(), m_Z.ToFloat(), frameOffset);
     669             : 
     670          15 :         rotY = m_InterpolatedRotY;
     671             :     }
     672             : 
     673          15 :     CMatrix3D GetInterpolatedTransform(float frameOffset) const override
     674             :     {
     675          15 :         if (m_TurretParent != INVALID_ENTITY)
     676             :         {
     677           0 :             CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
     678           0 :             if (!cmpPosition)
     679             :             {
     680           0 :                 LOGERROR("Turret with parent without position component");
     681           0 :                 CMatrix3D m;
     682           0 :                 m.SetIdentity();
     683           0 :                 return m;
     684             :             }
     685           0 :             if (!cmpPosition->IsInWorld())
     686             :             {
     687           0 :                 LOGERROR("CCmpPosition::GetInterpolatedTransform called on turret entity when IsInWorld is false");
     688           0 :                 CMatrix3D m;
     689           0 :                 m.SetIdentity();
     690           0 :                 return m;
     691             :             }
     692             :             else
     693             :             {
     694           0 :                 CMatrix3D parentTransformMatrix = cmpPosition->GetInterpolatedTransform(frameOffset);
     695           0 :                 CMatrix3D ownTransformation = CMatrix3D();
     696           0 :                 ownTransformation.SetYRotation(m_InterpolatedRotY);
     697           0 :                 ownTransformation.Translate(-m_TurretPosition.X.ToFloat(), m_TurretPosition.Y.ToFloat(), -m_TurretPosition.Z.ToFloat());
     698           0 :                 return parentTransformMatrix * ownTransformation;
     699             :             }
     700             :         }
     701          15 :         if (!m_InWorld)
     702             :         {
     703           0 :             LOGERROR("CCmpPosition::GetInterpolatedTransform called on entity when IsInWorld is false");
     704           0 :             CMatrix3D m;
     705           0 :             m.SetIdentity();
     706           0 :             return m;
     707             :         }
     708             : 
     709             :         float x, z, rotY;
     710          15 :         GetInterpolatedPosition2D(frameOffset, x, z, rotY);
     711             : 
     712             : 
     713          15 :         float baseY = 0;
     714          15 :         if (m_RelativeToGround)
     715             :         {
     716          15 :             CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
     717          15 :             if (cmpTerrain)
     718          15 :                 baseY = cmpTerrain->GetExactGroundLevel(x, z);
     719             : 
     720          15 :             if (m_Floating || m_ActorFloating)
     721             :             {
     722           3 :                 CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
     723           3 :                 if (cmpWaterManager)
     724           3 :                     baseY = std::max(baseY, cmpWaterManager->GetExactWaterLevel(x, z) - m_FloatDepth.ToFloat());
     725             :             }
     726             :         }
     727             : 
     728          15 :         float y = baseY + m_Y.ToFloat() + Interpolate(-1 * m_LastYDifference.ToFloat(), 0.f, frameOffset);
     729             : 
     730          15 :         CMatrix3D m;
     731             : 
     732             :         // linear interpolation is good enough (for RotX/Z).
     733             :         // As you always stay close to zero angle.
     734          15 :         m.SetXRotation(Interpolate(m_LastInterpolatedRotX, m_InterpolatedRotX, frameOffset));
     735          15 :         m.RotateZ(Interpolate(m_LastInterpolatedRotZ, m_InterpolatedRotZ, frameOffset));
     736             : 
     737          15 :         CVector3D pos(x, y, z);
     738             : 
     739          15 :         pos.Y += GetConstructionProgressOffset(pos);
     740             : 
     741          15 :         m.RotateY(rotY + (float)M_PI);
     742          15 :         m.Translate(pos);
     743             : 
     744          15 :         return m;
     745             :     }
     746             : 
     747          18 :     void GetInterpolatedPositions(CVector3D& pos0, CVector3D& pos1) const
     748             :     {
     749          18 :         float baseY0 = 0;
     750          18 :         float baseY1 = 0;
     751          18 :         float x0 = m_LastX.ToFloat();
     752          18 :         float z0 = m_LastZ.ToFloat();
     753          18 :         float x1 = m_X.ToFloat();
     754          18 :         float z1 = m_Z.ToFloat();
     755          18 :         if (m_RelativeToGround)
     756             :         {
     757          14 :             CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
     758          14 :             if (cmpTerrain)
     759             :             {
     760          14 :                 baseY0 = cmpTerrain->GetExactGroundLevel(x0, z0);
     761          14 :                 baseY1 = cmpTerrain->GetExactGroundLevel(x1, z1);
     762             :             }
     763             : 
     764          14 :             if (m_Floating || m_ActorFloating)
     765             :             {
     766           5 :                 CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
     767           5 :                 if (cmpWaterManager)
     768             :                 {
     769           5 :                     baseY0 = std::max(baseY0, cmpWaterManager->GetExactWaterLevel(x0, z0) - m_FloatDepth.ToFloat());
     770           5 :                     baseY1 = std::max(baseY1, cmpWaterManager->GetExactWaterLevel(x1, z1) - m_FloatDepth.ToFloat());
     771             :                 }
     772             :             }
     773             :         }
     774             : 
     775          18 :         float y0 = baseY0 + m_Y.ToFloat() + m_LastYDifference.ToFloat();
     776          18 :         float y1 = baseY1 + m_Y.ToFloat();
     777             : 
     778          18 :         pos0 = CVector3D(x0, y0, z0);
     779          18 :         pos1 = CVector3D(x1, y1, z1);
     780             : 
     781          18 :         pos0.Y += GetConstructionProgressOffset(pos0);
     782          18 :         pos1.Y += GetConstructionProgressOffset(pos1);
     783          18 :     }
     784             : 
     785           1 :     void HandleMessage(const CMessage& msg, bool UNUSED(global)) override
     786             :     {
     787           1 :         switch (msg.GetType())
     788             :         {
     789           0 :         case MT_Interpolate:
     790             :         {
     791           0 :             PROFILE("Position::Interpolate");
     792             : 
     793           0 :             const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
     794             : 
     795           0 :             float rotY = m_RotY.ToFloat();
     796             : 
     797           0 :             if (rotY != m_InterpolatedRotY)
     798             :             {
     799           0 :                 float rotYSpeed = m_RotYSpeed.ToFloat();
     800           0 :                 float delta = rotY - m_InterpolatedRotY;
     801             :                 // Wrap delta to -M_PI..M_PI
     802           0 :                 delta = fmodf(delta + (float)M_PI, 2*(float)M_PI); // range -2PI..2PI
     803           0 :                 if (delta < 0) delta += 2*(float)M_PI; // range 0..2PI
     804           0 :                 delta -= (float)M_PI; // range -M_PI..M_PI
     805             :                 // Clamp to max rate
     806           0 :                 float deltaClamped = Clamp(delta, -rotYSpeed*msgData.deltaSimTime, +rotYSpeed*msgData.deltaSimTime);
     807             :                 // Calculate new orientation, in a peculiar way in order to make sure the
     808             :                 // result gets close to m_orientation (rather than being n*2*M_PI out)
     809           0 :                 m_InterpolatedRotY = rotY + deltaClamped - delta;
     810             : 
     811             :                 // update the visual XZ rotation
     812           0 :                 if (m_InWorld)
     813             :                 {
     814           0 :                     m_LastInterpolatedRotX = m_InterpolatedRotX;
     815           0 :                     m_LastInterpolatedRotZ = m_InterpolatedRotZ;
     816             : 
     817           0 :                     UpdateXZRotation();
     818             :                 }
     819             : 
     820           0 :                 UpdateMessageSubscriptions();
     821             :             }
     822             : 
     823           0 :             break;
     824             :         }
     825           1 :         case MT_TurnStart:
     826             :         {
     827             : 
     828           1 :             m_LastInterpolatedRotX = m_InterpolatedRotX;
     829           1 :             m_LastInterpolatedRotZ = m_InterpolatedRotZ;
     830             : 
     831           1 :             if (m_InWorld && (m_LastX != m_X || m_LastZ != m_Z))
     832           1 :                 UpdateXZRotation();
     833             : 
     834             :             // Store the positions from the turn before
     835           1 :             m_PrevX = m_LastX;
     836           1 :             m_PrevZ = m_LastZ;
     837             : 
     838           1 :             m_LastX = m_X;
     839           1 :             m_LastZ = m_Z;
     840           1 :             m_LastYDifference = entity_pos_t::Zero();
     841             : 
     842             : 
     843             :             // warn when a position change also causes a territory change under the entity
     844           1 :             if (m_InWorld)
     845             :             {
     846             :                 player_id_t newTerritory;
     847           1 :                 CmpPtr<ICmpTerritoryManager> cmpTerritoryManager(GetSystemEntity());
     848           1 :                 if (cmpTerritoryManager)
     849           0 :                     newTerritory = cmpTerritoryManager->GetOwner(m_X, m_Z);
     850             :                 else
     851           1 :                     newTerritory = INVALID_PLAYER;
     852           1 :                 if (newTerritory != m_Territory)
     853             :                 {
     854           0 :                     m_Territory = newTerritory;
     855           0 :                     CMessageTerritoryPositionChanged posMsg(GetEntityId(), m_Territory);
     856           0 :                     GetSimContext().GetComponentManager().PostMessage(GetEntityId(), posMsg);
     857             :                 }
     858             :             }
     859           0 :             else if (m_Territory != INVALID_PLAYER)
     860             :             {
     861           0 :                 m_Territory = INVALID_PLAYER;
     862           0 :                 CMessageTerritoryPositionChanged posMsg(GetEntityId(), m_Territory);
     863           0 :                 GetSimContext().GetComponentManager().PostMessage(GetEntityId(), posMsg);
     864             :             }
     865           1 :             break;
     866             :         }
     867           0 :         case MT_TerrainChanged:
     868             :         case MT_WaterChanged:
     869             :         {
     870           0 :             AdvertiseInterpolatedPositionChanges();
     871           0 :             break;
     872             :         }
     873           0 :         case MT_Deserialized:
     874             :         {
     875           0 :             Deserialized();
     876           0 :             break;
     877             :         }
     878             :         }
     879           1 :     }
     880             : 
     881             : private:
     882             : 
     883             :     /*
     884             :      * Must be called whenever m_RotY or m_InterpolatedRotY change,
     885             :      * to determine whether we need to call Interpolate to make the unit rotate.
     886             :      */
     887           4 :     void UpdateMessageSubscriptions()
     888             :     {
     889           4 :         bool needInterpolate = false;
     890             : 
     891           4 :         float rotY = m_RotY.ToFloat();
     892           4 :         if (rotY != m_InterpolatedRotY)
     893           0 :             needInterpolate = true;
     894             : 
     895           4 :         if (needInterpolate != m_EnabledMessageInterpolate)
     896             :         {
     897           0 :             GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_Interpolate, this, needInterpolate);
     898           0 :             m_EnabledMessageInterpolate = needInterpolate;
     899             :         }
     900           4 :     }
     901             : 
     902             :     /**
     903             :      * This must be called after changing anything that will affect the
     904             :      * return value of GetPosition2D() or GetRotation().Y:
     905             :      *  - m_InWorld
     906             :      *  - m_X, m_Z
     907             :      *  - m_RotY
     908             :      */
     909          13 :     void AdvertisePositionChanges() const
     910             :     {
     911          13 :         for (std::set<entity_id_t>::const_iterator it = m_Turrets.begin(); it != m_Turrets.end(); ++it)
     912             :         {
     913           0 :             CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), *it);
     914           0 :             if (cmpPosition)
     915           0 :                 cmpPosition->UpdateTurretPosition();
     916             :         }
     917          13 :         if (m_InWorld)
     918             :         {
     919          20 :             CMessagePositionChanged msg(GetEntityId(), true, m_X, m_Z, m_RotY);
     920          10 :             GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
     921             :         }
     922             :         else
     923             :         {
     924           6 :             CMessagePositionChanged msg(GetEntityId(), false, entity_pos_t::Zero(), entity_pos_t::Zero(), entity_angle_t::Zero());
     925           3 :             GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
     926             :         }
     927          13 :     }
     928             : 
     929             :     /**
     930             :      * This must be called after changing anything that will affect the
     931             :      * return value of GetInterpolatedPositions():
     932             :      *  - m_InWorld
     933             :      *  - m_X, m_Z
     934             :      *  - m_LastX, m_LastZ
     935             :      *  - m_Y, m_LastYDifference, m_RelativeToGround
     936             :      *  - If m_RelativeToGround, then the ground under this unit
     937             :      *  - If m_RelativeToGround && m_Float, then the water level
     938             :      */
     939          22 :     void AdvertiseInterpolatedPositionChanges() const
     940             :     {
     941          22 :         if (m_InWorld)
     942             :         {
     943          18 :             CVector3D pos0, pos1;
     944          18 :             GetInterpolatedPositions(pos0, pos1);
     945             : 
     946          36 :             CMessageInterpolatedPositionChanged msg(GetEntityId(), true, pos0, pos1);
     947          18 :             GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
     948             :         }
     949             :         else
     950             :         {
     951           8 :             CMessageInterpolatedPositionChanged msg(GetEntityId(), false, CVector3D(), CVector3D());
     952           4 :             GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
     953             :         }
     954          22 :     }
     955             : 
     956           6 :     void UpdateXZRotation()
     957             :     {
     958           6 :         if (!m_InWorld)
     959             :         {
     960           0 :             LOGERROR("CCmpPosition::UpdateXZRotation called on entity when IsInWorld is false");
     961           6 :             return;
     962             :         }
     963             : 
     964           6 :         if (m_AnchorType == UPRIGHT || !m_RotZ.IsZero() || !m_RotX.IsZero())
     965             :         {
     966             :             // set the visual rotations to the ones fixed by the interface
     967           6 :             m_InterpolatedRotX = m_RotX.ToFloat();
     968           6 :             m_InterpolatedRotZ = m_RotZ.ToFloat();
     969           6 :             return;
     970             :         }
     971             : 
     972           0 :         CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
     973           0 :         if (!cmpTerrain || !cmpTerrain->IsLoaded())
     974             :         {
     975           0 :             LOGERROR("Terrain not loaded");
     976           0 :             return;
     977             :         }
     978             : 
     979             :         // TODO: average normal (average all the tiles?) for big units or for buildings?
     980           0 :         CVector3D normal = cmpTerrain->CalcExactNormal(m_X.ToFloat(), m_Z.ToFloat());
     981             : 
     982             :         // rotate the normal so the positive x direction is in the direction of the unit
     983           0 :         CVector2D projected = CVector2D(normal.X, normal.Z);
     984           0 :         projected.Rotate(m_InterpolatedRotY);
     985             : 
     986           0 :         normal.X = projected.X;
     987           0 :         normal.Z = projected.Y;
     988             : 
     989             :         // project and calculate the angles
     990           0 :         if (m_AnchorType == PITCH || m_AnchorType == PITCH_ROLL)
     991           0 :             m_InterpolatedRotX = -atan2(normal.Z, normal.Y);
     992             : 
     993           0 :         if (m_AnchorType == ROLL || m_AnchorType == PITCH_ROLL)
     994           0 :             m_InterpolatedRotZ = atan2(normal.X, normal.Y);
     995             :     }
     996             : };
     997             : 
     998         119 : REGISTER_COMPONENT_TYPE(Position)

Generated by: LCOV version 1.13