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