CharacterPlanetTest.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2024 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include <TestFramework.h>
  5. #include <Tests/Character/CharacterPlanetTest.h>
  6. #include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
  7. #include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
  8. #include <Jolt/Physics/Collision/Shape/SphereShape.h>
  9. #include <Jolt/Physics/Body/BodyCreationSettings.h>
  10. #include <Layers.h>
  11. JPH_IMPLEMENT_RTTI_VIRTUAL(CharacterPlanetTest)
  12. {
  13. JPH_ADD_BASE_CLASS(CharacterPlanetTest, Test)
  14. }
  15. void CharacterPlanetTest::Initialize()
  16. {
  17. // Create planet
  18. mBodyInterface->CreateAndAddBody(BodyCreationSettings(new SphereShape(cPlanetRadius), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
  19. // Create spheres
  20. BodyCreationSettings sphere(new SphereShape(0.5f), RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
  21. sphere.mGravityFactor = 0.0f; // We do our own gravity
  22. sphere.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
  23. sphere.mMassPropertiesOverride.mMass = 10.0f;
  24. sphere.mAngularDamping = 0.5f;
  25. default_random_engine random;
  26. for (int i = 0; i < 200; ++i)
  27. {
  28. uniform_real_distribution<float> theta(0, JPH_PI);
  29. uniform_real_distribution<float> phi(0, 2 * JPH_PI);
  30. sphere.mPosition = RVec3(1.1f * cPlanetRadius * Vec3::sUnitSpherical(theta(random), phi(random)));
  31. mBodyInterface->CreateAndAddBody(sphere, EActivation::Activate);
  32. }
  33. // Register to receive OnStep callbacks to apply gravity
  34. mPhysicsSystem->AddStepListener(this);
  35. // Create 'player' character
  36. Ref<CharacterVirtualSettings> settings = new CharacterVirtualSettings();
  37. settings->mShape = RotatedTranslatedShapeSettings(Vec3(0, 0.5f * cCharacterHeightStanding + cCharacterRadiusStanding, 0), Quat::sIdentity(), new CapsuleShape(0.5f * cCharacterHeightStanding, cCharacterRadiusStanding)).Create().Get();
  38. settings->mSupportingVolume = Plane(Vec3::sAxisY(), -cCharacterRadiusStanding); // Accept contacts that touch the lower sphere of the capsule
  39. mCharacter = new CharacterVirtual(settings, RVec3(0, cPlanetRadius, 0), Quat::sIdentity(), 0, mPhysicsSystem);
  40. mCharacter->SetListener(this);
  41. }
  42. void CharacterPlanetTest::ProcessInput(const ProcessInputParams &inParams)
  43. {
  44. // Determine controller input
  45. Vec3 control_input = Vec3::sZero();
  46. if (inParams.mKeyboard->IsKeyPressed(DIK_LEFT)) control_input.SetZ(-1);
  47. if (inParams.mKeyboard->IsKeyPressed(DIK_RIGHT)) control_input.SetZ(1);
  48. if (inParams.mKeyboard->IsKeyPressed(DIK_UP)) control_input.SetX(1);
  49. if (inParams.mKeyboard->IsKeyPressed(DIK_DOWN)) control_input.SetX(-1);
  50. if (control_input != Vec3::sZero())
  51. control_input = control_input.Normalized();
  52. // Smooth the player input
  53. mDesiredVelocity = 0.25f * control_input * cCharacterSpeed + 0.75f * mDesiredVelocity;
  54. // Check actions
  55. mJump = inParams.mKeyboard->IsKeyPressedAndTriggered(DIK_RCONTROL, mWasJump);
  56. }
  57. void CharacterPlanetTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
  58. {
  59. // Calculate up vector based on position on planet
  60. Vec3 up = Vec3(mCharacter->GetPosition()).Normalized();
  61. mCharacter->SetUp(up);
  62. // Rotate capsule so it points up.
  63. // Note that you probably want to define the rotation around the up vector based on the camera
  64. // forward, but this creates a feedback loop in the demo frame work camera system so we don't do that.
  65. mCharacter->SetRotation(Quat::sFromTo(Vec3::sAxisY(), up));
  66. // Draw character pre update (the sim is also drawn pre update)
  67. #ifdef JPH_DEBUG_RENDERER
  68. mCharacter->GetShape()->Draw(mDebugRenderer, mCharacter->GetCenterOfMassTransform(), Vec3::sReplicate(1.0f), Color::sGreen, false, true);
  69. #endif // JPH_DEBUG_RENDERER
  70. // Determine new character velocity
  71. Vec3 current_vertical_velocity = mCharacter->GetLinearVelocity().Dot(up) * up;
  72. Vec3 ground_velocity = mCharacter->GetGroundVelocity();
  73. Vec3 new_velocity;
  74. if (mCharacter->GetGroundState() == CharacterVirtual::EGroundState::OnGround // If on ground
  75. && (current_vertical_velocity - ground_velocity).Dot(up) < 0.1f) // And not moving away from ground
  76. {
  77. // Assume velocity of ground when on ground
  78. new_velocity = ground_velocity;
  79. // Jump
  80. if (mJump)
  81. new_velocity += cJumpSpeed * up;
  82. }
  83. else
  84. new_velocity = current_vertical_velocity;
  85. // Apply gravity
  86. Vec3 gravity = -up * mPhysicsSystem->GetGravity().Length();
  87. new_velocity += gravity * inParams.mDeltaTime;
  88. // Apply player input relative to the camera
  89. Vec3 right = inParams.mCameraState.mForward.Cross(up).NormalizedOr(Vec3::sAxisZ());
  90. Vec3 forward = up.Cross(right).NormalizedOr(Vec3::sAxisX());
  91. new_velocity += right * mDesiredVelocity.GetZ() + forward * mDesiredVelocity.GetX();
  92. // Update character velocity
  93. mCharacter->SetLinearVelocity(new_velocity);
  94. // Update the character position
  95. CharacterVirtual::ExtendedUpdateSettings update_settings;
  96. mCharacter->ExtendedUpdate(inParams.mDeltaTime,
  97. gravity,
  98. update_settings,
  99. mPhysicsSystem->GetDefaultBroadPhaseLayerFilter(Layers::MOVING),
  100. mPhysicsSystem->GetDefaultLayerFilter(Layers::MOVING),
  101. { },
  102. { },
  103. *mTempAllocator);
  104. }
  105. void CharacterPlanetTest::GetInitialCamera(CameraState& ioState) const
  106. {
  107. ioState.mPos = RVec3(0, 0.5f, 0);
  108. ioState.mForward = Vec3(1, -0.3f, 0).Normalized();
  109. }
  110. RMat44 CharacterPlanetTest::GetCameraPivot(float inCameraHeading, float inCameraPitch) const
  111. {
  112. // Pivot is center of character + distance behind based on the heading and pitch of the camera.
  113. // Note that this has a singularity on the south pole of the planet which causes the camera to behave erratically as you get close.
  114. // The demo framework camera system wasn't really made to deal with cameras that are upside down.
  115. Vec3 fwd = Vec3(Cos(inCameraPitch) * Cos(inCameraHeading), Sin(inCameraPitch), Cos(inCameraPitch) * Sin(inCameraHeading));
  116. RVec3 cam_pos = mCharacter->GetPosition() - 5.0f * (mCharacter->GetRotation() * fwd);
  117. return RMat44::sRotationTranslation(mCharacter->GetRotation(), cam_pos);
  118. }
  119. void CharacterPlanetTest::SaveState(StateRecorder &inStream) const
  120. {
  121. mCharacter->SaveState(inStream);
  122. }
  123. void CharacterPlanetTest::RestoreState(StateRecorder &inStream)
  124. {
  125. mCharacter->RestoreState(inStream);
  126. }
  127. void CharacterPlanetTest::SaveInputState(StateRecorder &inStream) const
  128. {
  129. inStream.Write(mDesiredVelocity);
  130. inStream.Write(mJump);
  131. }
  132. void CharacterPlanetTest::RestoreInputState(StateRecorder &inStream)
  133. {
  134. inStream.Read(mDesiredVelocity);
  135. inStream.Read(mJump);
  136. }
  137. void CharacterPlanetTest::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem)
  138. {
  139. // Use the length of the global gravity vector
  140. float gravity = inPhysicsSystem.GetGravity().Length();
  141. // We don't need to lock the bodies since they're already locked in the OnStep callback.
  142. // Note that this means we're responsible for avoiding race conditions with other step listeners while accessing bodies.
  143. // We know that this is safe because in this demo there's only one step listener.
  144. const BodyLockInterface &body_interface = inPhysicsSystem.GetBodyLockInterfaceNoLock();
  145. // Loop over all active bodies
  146. BodyIDVector body_ids;
  147. inPhysicsSystem.GetActiveBodies(EBodyType::RigidBody, body_ids);
  148. for (const BodyID &id : body_ids)
  149. {
  150. BodyLockWrite lock(body_interface, id);
  151. if (lock.Succeeded())
  152. {
  153. // Apply gravity towards the center of the planet
  154. Body &body = lock.GetBody();
  155. RVec3 position = body.GetPosition();
  156. float mass = 1.0f / body.GetMotionProperties()->GetInverseMass();
  157. body.AddForce(-gravity * mass * Vec3(position).Normalized());
  158. }
  159. }
  160. }
  161. void CharacterPlanetTest::OnContactAdded(const CharacterVirtual *inCharacter, const BodyID &inBodyID2, const SubShapeID &inSubShapeID2, RVec3Arg inContactPosition, Vec3Arg inContactNormal, CharacterContactSettings &ioSettings)
  162. {
  163. // We don't want the spheres to push the player character
  164. ioSettings.mCanPushCharacter = false;
  165. }