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