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)
|