Browse Source

Added demo that shows how to walk on a planet with CharacterVirtual.

Also shows how to apply custom gravity so that objects fall to the surface of the planet.
Jorrit Rouwe 1 year ago
parent
commit
83a431bf7e

+ 2 - 0
Samples/Samples.cmake

@@ -15,6 +15,8 @@ set(SAMPLES_SRC_FILES
 	${SAMPLES_ROOT}/Tests/BroadPhase/BroadPhaseTest.h
 	${SAMPLES_ROOT}/Tests/Character/CharacterBaseTest.cpp
 	${SAMPLES_ROOT}/Tests/Character/CharacterBaseTest.h
+	${SAMPLES_ROOT}/Tests/Character/CharacterPlanetTest.cpp
+	${SAMPLES_ROOT}/Tests/Character/CharacterPlanetTest.h
 	${SAMPLES_ROOT}/Tests/Character/CharacterTest.cpp
 	${SAMPLES_ROOT}/Tests/Character/CharacterTest.h
 	${SAMPLES_ROOT}/Tests/Character/CharacterVirtualTest.cpp

+ 2 - 0
Samples/SamplesApp.cpp

@@ -290,12 +290,14 @@ static TestNameAndRTTI sRigTests[] =
 JPH_DECLARE_RTTI_FOR_FACTORY(JPH_NO_EXPORT, CharacterTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(JPH_NO_EXPORT, CharacterVirtualTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(JPH_NO_EXPORT, CharacterSpaceShipTest)
+JPH_DECLARE_RTTI_FOR_FACTORY(JPH_NO_EXPORT, CharacterPlanetTest)
 
 static TestNameAndRTTI sCharacterTests[] =
 {
 	{ "Character",							JPH_RTTI(CharacterTest) },
 	{ "Character Virtual",					JPH_RTTI(CharacterVirtualTest) },
 	{ "Character Virtual vs Space Ship",	JPH_RTTI(CharacterSpaceShipTest) },
+	{ "Character Virtual vs Planet",		JPH_RTTI(CharacterPlanetTest) },
 };
 
 JPH_DECLARE_RTTI_FOR_FACTORY(JPH_NO_EXPORT, WaterShapeTest)

+ 194 - 0
Samples/Tests/Character/CharacterPlanetTest.cpp

@@ -0,0 +1,194 @@
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
+// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#include <TestFramework.h>
+
+#include <Tests/Character/CharacterPlanetTest.h>
+#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
+#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
+#include <Jolt/Physics/Collision/Shape/SphereShape.h>
+#include <Jolt/Physics/Body/BodyCreationSettings.h>
+#include <Layers.h>
+
+JPH_IMPLEMENT_RTTI_VIRTUAL(CharacterPlanetTest)
+{
+	JPH_ADD_BASE_CLASS(CharacterPlanetTest, Test)
+}
+
+void CharacterPlanetTest::Initialize()
+{
+	// Create planet
+	mBodyInterface->CreateAndAddBody(BodyCreationSettings(new SphereShape(cPlanetRadius), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
+
+	// Create spheres
+	BodyCreationSettings sphere(new SphereShape(0.5f), RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
+	sphere.mGravityFactor = 0.0f; // We do our own gravity
+	sphere.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
+	sphere.mMassPropertiesOverride.mMass = 10.0f;
+	sphere.mAngularDamping = 0.5f;
+	default_random_engine random;
+	for (int i = 0; i < 200; ++i)
+	{
+		uniform_real_distribution<float> theta(0, JPH_PI);
+		uniform_real_distribution<float> phi(0, 2 * JPH_PI);
+		sphere.mPosition = RVec3(1.1f * cPlanetRadius * Vec3::sUnitSpherical(theta(random), phi(random)));
+		mBodyInterface->CreateAndAddBody(sphere, EActivation::Activate);
+	}
+
+	// Register to receive OnStep callbacks to apply gravity
+	mPhysicsSystem->AddStepListener(this);
+
+	// Create 'player' character
+	Ref<CharacterVirtualSettings> settings = new CharacterVirtualSettings();
+	settings->mShape = RotatedTranslatedShapeSettings(Vec3(0, 0.5f * cCharacterHeightStanding + cCharacterRadiusStanding, 0), Quat::sIdentity(), new CapsuleShape(0.5f * cCharacterHeightStanding, cCharacterRadiusStanding)).Create().Get();
+	settings->mSupportingVolume = Plane(Vec3::sAxisY(), -cCharacterRadiusStanding); // Accept contacts that touch the lower sphere of the capsule
+	mCharacter = new CharacterVirtual(settings, RVec3(0, cPlanetRadius, 0), Quat::sIdentity(), 0, mPhysicsSystem);
+	mCharacter->SetListener(this);
+}
+
+void CharacterPlanetTest::ProcessInput(const ProcessInputParams &inParams)
+{
+	// Determine controller input
+	Vec3 control_input = Vec3::sZero();
+	if (inParams.mKeyboard->IsKeyPressed(DIK_LEFT))		control_input.SetZ(-1);
+	if (inParams.mKeyboard->IsKeyPressed(DIK_RIGHT))	control_input.SetZ(1);
+	if (inParams.mKeyboard->IsKeyPressed(DIK_UP))		control_input.SetX(1);
+	if (inParams.mKeyboard->IsKeyPressed(DIK_DOWN))		control_input.SetX(-1);
+	if (control_input != Vec3::sZero())
+		control_input = control_input.Normalized();
+
+	// Smooth the player input
+	mDesiredVelocity = 0.25f * control_input * cCharacterSpeed + 0.75f * mDesiredVelocity;
+
+	// Check actions
+	mJump = inParams.mKeyboard->IsKeyPressedAndTriggered(DIK_RCONTROL, mWasJump);
+}
+
+void CharacterPlanetTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
+{
+	// Calculate up vector based on position on planet
+	Vec3 up = Vec3(mCharacter->GetPosition()).Normalized();
+	mCharacter->SetUp(up);
+
+	// Rotate capsule so it points up.
+	// Note that you probably want to define the rotation around the up vector based on the camera
+	// forward, but this creates a feedback loop in the demo frame work camera system so we don't do that.
+	mCharacter->SetRotation(Quat::sFromTo(Vec3::sAxisY(), up));
+
+	// Draw character pre update (the sim is also drawn pre update)
+#ifdef JPH_DEBUG_RENDERER
+	mCharacter->GetShape()->Draw(mDebugRenderer, mCharacter->GetCenterOfMassTransform(), Vec3::sReplicate(1.0f), Color::sGreen, false, true);
+#endif // JPH_DEBUG_RENDERER
+
+	// Determine new character velocity
+	Vec3 current_vertical_velocity = mCharacter->GetLinearVelocity().Dot(up) * up;
+	Vec3 ground_velocity = mCharacter->GetGroundVelocity();
+	Vec3 new_velocity;
+	if (mCharacter->GetGroundState() == CharacterVirtual::EGroundState::OnGround // If on ground
+		&& (current_vertical_velocity - ground_velocity).Dot(up) < 0.1f) // And not moving away from ground
+	{
+		// Assume velocity of ground when on ground
+		new_velocity = ground_velocity;
+
+		// Jump
+		if (mJump)
+			new_velocity += cJumpSpeed * up;
+	}
+	else
+		new_velocity = current_vertical_velocity;
+
+	// Apply gravity
+	Vec3 gravity = -up * mPhysicsSystem->GetGravity().Length();
+	new_velocity += gravity * inParams.mDeltaTime;
+
+	// Apply player input relative to the camera
+	Vec3 right = inParams.mCameraState.mForward.Cross(up).NormalizedOr(Vec3::sAxisZ());
+	Vec3 forward = up.Cross(right).NormalizedOr(Vec3::sAxisX());
+	new_velocity += right * mDesiredVelocity.GetZ() + forward * mDesiredVelocity.GetX();
+
+	// Update character velocity
+	mCharacter->SetLinearVelocity(new_velocity);
+
+	// Update the character position
+	CharacterVirtual::ExtendedUpdateSettings update_settings;
+	mCharacter->ExtendedUpdate(inParams.mDeltaTime,
+		gravity,
+		update_settings,
+		mPhysicsSystem->GetDefaultBroadPhaseLayerFilter(Layers::MOVING),
+		mPhysicsSystem->GetDefaultLayerFilter(Layers::MOVING),
+		{ },
+		{ },
+		*mTempAllocator);
+}
+
+void CharacterPlanetTest::GetInitialCamera(CameraState& ioState) const
+{
+	ioState.mPos = RVec3(0, 0.5f, 0);
+	ioState.mForward = Vec3(1, -0.3f, 0).Normalized();
+}
+
+RMat44 CharacterPlanetTest::GetCameraPivot(float inCameraHeading, float inCameraPitch) const
+{
+	// Pivot is center of character + distance behind based on the heading and pitch of the camera.
+	// Note that this has a singularity on the south pole of the planet which causes the camera to behave erratically as you get close.
+	// The demo framework camera system wasn't really made to deal with cameras that are upside down.
+	Vec3 fwd = Vec3(Cos(inCameraPitch) * Cos(inCameraHeading), Sin(inCameraPitch), Cos(inCameraPitch) * Sin(inCameraHeading));
+	RVec3 cam_pos = mCharacter->GetPosition() - 5.0f * (mCharacter->GetRotation() * fwd);
+	return RMat44::sRotationTranslation(mCharacter->GetRotation(), cam_pos);
+}
+
+void CharacterPlanetTest::SaveState(StateRecorder &inStream) const
+{
+	mCharacter->SaveState(inStream);
+}
+
+void CharacterPlanetTest::RestoreState(StateRecorder &inStream)
+{
+	mCharacter->RestoreState(inStream);
+}
+
+void CharacterPlanetTest::SaveInputState(StateRecorder &inStream) const
+{
+	inStream.Write(mDesiredVelocity);
+	inStream.Write(mJump);
+}
+
+void CharacterPlanetTest::RestoreInputState(StateRecorder &inStream)
+{
+	inStream.Read(mDesiredVelocity);
+	inStream.Read(mJump);
+}
+
+void CharacterPlanetTest::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem)
+{
+	// Use the length of the global gravity vector
+	float gravity = inPhysicsSystem.GetGravity().Length();
+
+	// We don't need to lock the bodies since they're already locked in the OnStep callback.
+	// Note that this means we're responsible for avoiding race conditions with other step listeners while accessing bodies.
+	// We know that this is safe because in this demo there's only one step listener.
+	const BodyLockInterface &body_interface = inPhysicsSystem.GetBodyLockInterfaceNoLock();
+
+	// Loop over all active bodies
+	BodyIDVector body_ids;
+	inPhysicsSystem.GetActiveBodies(EBodyType::RigidBody, body_ids);
+	for (const BodyID &id : body_ids)
+	{
+		BodyLockWrite lock(body_interface, id);
+		if (lock.Succeeded())
+		{
+			// Apply gravity towards the center of the planet
+			Body &body = lock.GetBody();
+			RVec3 position = body.GetPosition();
+			float mass = 1.0f / body.GetMotionProperties()->GetInverseMass();
+			body.AddForce(-gravity * mass * Vec3(position).Normalized());
+		}
+	}
+}
+
+void CharacterPlanetTest::OnContactAdded(const CharacterVirtual *inCharacter, const BodyID &inBodyID2, const SubShapeID &inSubShapeID2, RVec3Arg inContactPosition, Vec3Arg inContactNormal, CharacterContactSettings &ioSettings)
+{
+	// We don't want the spheres to push the player character
+	ioSettings.mCanPushCharacter = false;
+}

+ 63 - 0
Samples/Tests/Character/CharacterPlanetTest.h

@@ -0,0 +1,63 @@
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
+// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Tests/Test.h>
+#include <Jolt/Physics/Character/CharacterVirtual.h>
+#include <Jolt/Physics/PhysicsStepListener.h>
+
+// Demonstrates how to do custom gravity to simulate a character walking on a planet
+class CharacterPlanetTest : public Test, public PhysicsStepListener, public CharacterContactListener
+{
+public:
+	JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, CharacterPlanetTest)
+
+	// Initialize the test
+	virtual void			Initialize() override;
+
+	// Process input
+	virtual void			ProcessInput(const ProcessInputParams &inParams) override;
+
+	// Update the test, called before the physics update
+	virtual void			PrePhysicsUpdate(const PreUpdateParams &inParams) override;
+
+	// Override to specify the initial camera state (local to GetCameraPivot)
+	virtual void			GetInitialCamera(CameraState &ioState) const override;
+
+	// Override to specify a camera pivot point and orientation (world space)
+	virtual RMat44			GetCameraPivot(float inCameraHeading, float inCameraPitch) const override;
+
+	// Saving / restoring state for replay
+	virtual void			SaveState(StateRecorder &inStream) const override;
+	virtual void			RestoreState(StateRecorder &inStream) override;
+
+	// Saving / restoring controller input state for replay
+	virtual void			SaveInputState(StateRecorder &inStream) const override;
+	virtual void			RestoreInputState(StateRecorder &inStream) override;
+
+	// See: PhysicsStepListener
+	virtual void			OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem) override;
+
+	// See: CharacterContactListener
+	virtual void			OnContactAdded(const CharacterVirtual *inCharacter, const BodyID &inBodyID2, const SubShapeID &inSubShapeID2, RVec3Arg inContactPosition, Vec3Arg inContactNormal, CharacterContactSettings &ioSettings) override;
+
+private:
+	// Planet size
+	static constexpr float	cPlanetRadius = 20.0f;
+
+	// Character size
+	static constexpr float	cCharacterHeightStanding = 1.35f;
+	static constexpr float	cCharacterRadiusStanding = 0.3f;
+	static constexpr float	cCharacterSpeed = 6.0f;
+	static constexpr float	cJumpSpeed = 4.0f;
+
+	// The 'player' character
+	Ref<CharacterVirtual>	mCharacter;
+
+	// Player input
+	Vec3					mDesiredVelocity = Vec3::sZero();
+	bool					mJump = false;
+	bool					mWasJump = false;
+};