Merge pull request #59 from antopilo/feature/NK-148-character-autostepping

Feature/NK-148 character autostepping
This commit is contained in:
Antoine Pilote
2023-08-12 19:48:57 -04:00
committed by GitHub
11 changed files with 263 additions and 69 deletions

View File

@@ -45,6 +45,7 @@ class Scene {
// Transform
foreign static GetScript_(e)
foreign static GetLocalTranslation_(e)
foreign static GetTranslation_(e)
foreign static SetTranslation_(e, x, y, z)
foreign static SetRotation_(e, x, y, z)
@@ -123,11 +124,17 @@ class TransformComponent {
_entityId = id
}
GetLocalTranslation() {
var result = Scene.GetLocalTranslation_(_entityId)
return Vector3.new(result[0], result[1], result[2])
}
GetTranslation() {
var result = Scene.GetTranslation_(_entityId)
return Vector3.new(result[0], result[1], result[2])
}
SetTranslation(t) {
Scene.SetTranslation_(_entityId, t.x, t.y, t.z)
}

View File

@@ -12,6 +12,7 @@ public:
{
if (!entity.HasComponent<Nuake::CharacterControllerComponent>())
return;
using namespace Nuake;
auto& component = entity.GetComponent<Nuake::CharacterControllerComponent>();
BeginComponentTable(CHARACTER CONTROLLER, Nuake::CharacterControllerComponent);
@@ -33,6 +34,59 @@ public:
ImGui::TableNextColumn();
ComponentTableReset(component.MaxSlopeAngle, 0.45f)
}
ImGui::TableNextColumn();
{
ImGui::Text("Auto stepping");
ImGui::TableNextColumn();
ImGui::Checkbox("##AutoStepping", &component.AutoStepping);
ImGui::TableNextColumn();
ComponentTableReset(component.AutoStepping, true)
}
if (component.AutoStepping)
{
ImGui::TableNextColumn();
{
ImGui::Text("Stick to floor step down");
ImGui::TableNextColumn();
ImGui::DragFloat("##StickToFloorStepDown", &component.StickToFloorStepDown.y, -10.0f, 0.01, 0.0f);
ImGui::TableNextColumn();
ComponentTableReset(component.StickToFloorStepDown.y, -0.5f);
}
ImGui::TableNextColumn();
{
ImGui::Text("Step down extra");
ImGui::TableNextColumn();
ImGui::DragFloat("##StepDownExtra", &component.StepDownExtra.y, -10.0f, 0.01, 0.0f);
ImGui::TableNextColumn();
ComponentTableReset(component.StepDownExtra.y, 0.0f);
}
ImGui::TableNextColumn();
{
ImGui::Text("Step up");
ImGui::TableNextColumn();
ImGui::DragFloat("##StepUp", &component.SteppingStepUp.y, 0.0f, 0.01, 10.0f);
ImGui::TableNextColumn();
ComponentTableReset(component.SteppingStepUp.y, 0.4f);
}
ImGui::TableNextColumn();
{
ImGui::Text("Step distance");
ImGui::TableNextColumn();
ImGui::DragFloat("##StepDistance", &component.SteppingForwardDistance, 0.0f, 0.01f, 10.0f);
ImGui::TableNextColumn();
ComponentTableReset(component.SteppingForwardDistance, 0.250f)
}
ImGui::TableNextColumn();
{
ImGui::Text("Step min distance");
ImGui::TableNextColumn();
ImGui::DragFloat("##StepMinDistance", &component.SteppingMinDistance, 0.0f, 0.01f, component.SteppingForwardDistance);
ImGui::TableNextColumn();
ComponentTableReset(component.SteppingMinDistance, 0.125f)
}
}
}
EndComponentTable()
}

View File

@@ -8,11 +8,17 @@ namespace Nuake
{
namespace Physics
{
CharacterController::CharacterController(const Ref<PhysicShape>& shape, float friction, float maxSlopeAngle)
CharacterController::CharacterController(const CharacterControllerSettings& settings)
{
Shape = shape;
Friction = friction;
MaxSlopeAngle = maxSlopeAngle;
Shape = settings.Shape;
Friction = settings.Friction;
MaxSlopeAngle = settings.MaxSlopeAngle;
AutoStepping = settings.AutoStepping;
StepDown = settings.StepDown;
StepDownExtra = settings.StepDownExtra;
StepUp = settings.StepUp;
StepDistance = settings.StepDistance;
StepMinDistance = settings.StepMinDistance;
}
void CharacterController::SetEntity(Entity& ent)

View File

@@ -10,35 +10,49 @@ namespace Nuake
namespace Physics
{
struct CharacterControllerSettings
{
Ref<Physics::PhysicShape> Shape;
float Friction;
float MaxSlopeAngle;
bool AutoStepping;
Vector3 StepDown;
Vector3 StepDownExtra;
Vector3 StepUp;
float StepMinDistance;
float StepDistance;
};
class CharacterController
{
public:
Vector3 Position = Vector3(0, 0, 0);
Quat Rotation = Quat(1, 0, 0, 0);
Entity Owner;
bool IsOnGround = false;
bool m_hittingWall;
float m_stepHeight = 0.35f;
float MaxSlopeAngle = 45.0f;
float Friction = 0.5f;
// Settings
Ref<PhysicShape> Shape;
float Friction = 0.5f;
float MaxSlopeAngle = 45.0f;
bool AutoStepping;
Vector3 StepDown;
Vector3 StepDownExtra;
Vector3 StepUp;
float StepDistance;
float StepMinDistance;
float m_bottomYOffset;
float m_bottomRoundedRegionYOffset;
// State
Vector3 Position = Vector3(0, 0, 0);
Quat Rotation = Quat(1, 0, 0, 0);
bool IsOnGround = false;
Vector3 m_manualVelocity;
std::vector<Vector3> m_surfaceHitNormals;
public:
CharacterController(const CharacterControllerSettings& settings);
CharacterController(const Ref<PhysicShape>& shape, float friction, float maxSlopeAngle);
void MoveAndSlide(const Vector3& velocity);
void SetEntity(Entity& ent);
Entity GetEntity() const;
void MoveAndSlide(const Vector3& velocity);
bool IsOnFloor()
bool IsOnFloor() const
{
return IsOnGround;
}

View File

@@ -27,9 +27,10 @@
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Body/BodyActivationListener.h>
#include <Jolt/Physics/Character/Character.h>
#include <Jolt/Physics/Character/CharacterVirtual.h>
#include <Jolt/Physics/Collision/RayCast.h>
#include <Jolt/Physics/Collision/CastResult.h>
#include <Jolt/Core/TempAllocator.h>
#include <dependencies/JoltPhysics/Jolt/Physics/Collision/CollisionCollectorImpl.h>
@@ -245,7 +246,7 @@ namespace Nuake
{
DynamicWorld::DynamicWorld() : _stepCount(0)
{
_registeredCharacters = std::map<uint32_t, JPH::Character*>();
_registeredCharacters = std::map<uint32_t, JPH::CharacterVirtual*>();
// Initialize Jolt Physics
const uint32_t MaxBodies = 2048;
@@ -329,26 +330,21 @@ namespace Nuake
void DynamicWorld::AddCharacterController(Ref<CharacterController> cc)
{
JPH::Ref<JPH::CharacterSettings> settings = new JPH::CharacterSettings();
JPH::Ref<JPH::CharacterVirtualSettings> settings = new JPH::CharacterVirtualSettings();
settings->mMaxSlopeAngle = JPH::DegreesToRadians(cc->MaxSlopeAngle);
settings->mLayer = Layers::MOVING;
settings->mFriction = cc->Friction;
settings->mMaxStrength = 1.0f;
settings->mCharacterPadding = 0.05f;
settings->mPenetrationRecoverySpeed = 1.0f;
settings->mPredictiveContactDistance = 0.01f;
settings->mShape = GetJoltShape(cc->Shape);
settings->mGravityFactor = 0.0f;
settings->mSupportingVolume = JPH::Plane(JPH::Vec3::sAxisY(), -0.5f);
auto& joltPosition = JPH::Vec3(cc->Position.x, cc->Position.y, cc->Position.z);
Quat& bodyRotation = cc->Rotation;
// We need to add 180 degrees because our forward is -Z.
const auto& yOffset = Vector3(0.0f, Rad(180.0), 0.0f);
//bodyRotation = glm::normalize(bodyRotation * Quat(yOffset));
const Quat& bodyRotation = cc->Rotation;
const auto& joltRotation = JPH::Quat(bodyRotation.x, bodyRotation.y, bodyRotation.z, bodyRotation.w);
JPH::Character* character = new JPH::Character(settings, joltPosition, joltRotation, cc->GetEntity().GetID() , _JoltPhysicsSystem.get());
auto character = new JPH::CharacterVirtual(settings, std::move(joltPosition), std::move(joltRotation), _JoltPhysicsSystem.get());
character->AddToPhysicsSystem(JPH::EActivation::Activate);
//character->AddToPhysicsSystem(JPH::EActivation::Activate);
// To get the jolt character control from a scene entity.
_registeredCharacters[cc->Owner.GetHandle()] = character;
@@ -452,7 +448,7 @@ namespace Nuake
{
Entity entity { (entt::entity)e.first, Engine::GetCurrentScene().get()};
JPH::Character* characterController = e.second;
JPH::CharacterVirtual* characterController = e.second;
JPH::Mat44 joltTransform = characterController->GetWorldTransform();
const auto bodyRotation = characterController->GetRotation();
@@ -506,19 +502,50 @@ namespace Nuake
// Step the world
try
{
_JoltPhysicsSystem->Update(ts, collisionSteps, new JPH::TempAllocatorMalloc(), _JoltJobSystem);
// TODO: Potential memory leak with new keyword.
auto joltTempAllocator = CreateRef<JPH::TempAllocatorMalloc>();
JPH::CharacterVirtual::ExtendedUpdateSettings joltUpdateSettings;
for (auto& c : _registeredCharacters)
{
c.second->PostSimulation(0.05f);
//c.second->PostSimulation(0.05f);
Entity entity{ (entt::entity)c.first, Engine::GetCurrentScene().get() };
if (entity.HasComponent<CharacterControllerComponent>())
{
auto& characterControllerComponent = entity.GetComponent<CharacterControllerComponent>();
auto characterController = characterControllerComponent.GetCharacterController();
const auto& broadPhaseLayerFilter = _JoltPhysicsSystem->GetDefaultBroadPhaseLayerFilter(Layers::MOVING);
const auto& LayerFilter = _JoltPhysicsSystem->GetDefaultLayerFilter(Layers::MOVING);
const auto& joltGravity = _JoltPhysicsSystem->GetGravity();
auto& tempAllocatorPtr = *(joltTempAllocator);
if (characterController->AutoStepping)
{
// Create update settings from character controller
joltUpdateSettings.mStickToFloorStepDown = CreateJoltVec3(characterController->StepDown);
joltUpdateSettings.mWalkStairsStepDownExtra = CreateJoltVec3(characterController->StepDownExtra);
joltUpdateSettings.mWalkStairsStepUp = CreateJoltVec3(characterController->StepUp);
joltUpdateSettings.mWalkStairsStepForwardTest = characterController->StepDistance;
joltUpdateSettings.mWalkStairsMinStepForward = characterController->StepMinDistance;
c.second->ExtendedUpdate(ts, joltGravity, joltUpdateSettings, broadPhaseLayerFilter, LayerFilter, { }, { }, tempAllocatorPtr);
}
else
{
c.second->Update(ts, joltGravity, broadPhaseLayerFilter, LayerFilter, {}, {}, tempAllocatorPtr);
}
}
}
_JoltPhysicsSystem->Update(ts, collisionSteps, joltTempAllocator.get(), _JoltJobSystem);
}
catch (...)
{
Logger::Log("Failed to run simulation update", "physics", CRITICAL);
}
SyncEntitiesTranforms();
SyncCharactersTransforms();
}
@@ -538,7 +565,7 @@ namespace Nuake
{
for (auto& character : _registeredCharacters)
{
character.second->RemoveFromPhysicsSystem();
//character.second->RemoveFromPhysicsSystem();
}
_registeredCharacters.clear();

View File

@@ -12,6 +12,7 @@
#include <Jolt/Jolt.h>
namespace JPH
{
class PhysicsSystem;
@@ -20,20 +21,19 @@ namespace JPH
class BodyActivationListener;
class BodyInterface;
class Shape;
class Character;
class CharacterVirtual;
template<class T>
class Ref;
}
namespace Nuake
{
class BPLayerInterfaceImpl;
class MyContactListener;
class MyBodyActivationListener;
namespace Physics
namespace Physics
{
class DynamicWorld
{
@@ -48,7 +48,7 @@ namespace Nuake
BPLayerInterfaceImpl* _JoltBroadphaseLayerInterface;
std::vector<uint32_t> _registeredBodies;
std::map<uint32_t, JPH::Character*> _registeredCharacters;
std::map<uint32_t, JPH::CharacterVirtual*> _registeredCharacters;
public:
DynamicWorld();
@@ -73,6 +73,11 @@ namespace Nuake
JPH::Ref<JPH::Shape> GetJoltShape(const Ref<PhysicShape> shape);
void SyncEntitiesTranforms();
void SyncCharactersTransforms();
JPH::Vec3 CreateJoltVec3(const Vector3& input) const
{
return JPH::Vec3(input.x, input.y, input.z);
}
};
}
}

View File

@@ -19,6 +19,12 @@ using json = nlohmann::json;
SERIALIZE_VEC3(v) \
j[#v]["w"] = this->v.w;
#define DESERIALIZE_VAL(p) \
if(j.contains(#p)) \
{ \
p = j[#p]; \
}
#define DESERIALIZE_VEC4(v, p) \
p = Vector4(v["x"], v["y"], v["z"], v["w"]);

View File

@@ -1,40 +1,83 @@
#pragma once
#include "src/Core/Physics/CharacterController.h"
namespace Nuake {
namespace Nuake
{
class CharacterControllerComponent
{
public:
Ref<Physics::CharacterController> CharacterController;
private:
Ref<Physics::CharacterController> m_CharacterController;
public:
// Auto stepping settings
bool AutoStepping = false;
Vector3 StickToFloorStepDown = Vector3(0.f, -0.5f, 0.f);
Vector3 StepDownExtra = Vector3(0, 0, 0); // ??
Vector3 SteppingStepUp = Vector3(0.f, 0.4f, 0.f);
float SteppingMinDistance = 0.125f;
float SteppingForwardDistance = 0.250f;
float Friction = 0.5f;
float MaxSlopeAngle = 0.45f;
float MaxSlopeAngle = 45.0f;
float MaxStrength = 1.0f;
CharacterControllerComponent()
{
}
void SetCharacterController(const Ref<Physics::CharacterController>& charController)
{
m_CharacterController = charController;
}
Ref<Physics::CharacterController> GetCharacterController() const
{
return m_CharacterController;
}
json Serialize()
{
BEGIN_SERIALIZE();
SERIALIZE_VAL(AutoStepping);
SERIALIZE_VEC3(StickToFloorStepDown);
SERIALIZE_VEC3(StepDownExtra);
SERIALIZE_VEC3(SteppingStepUp);
SERIALIZE_VAL(SteppingMinDistance);
SERIALIZE_VAL(SteppingForwardDistance);
SERIALIZE_VAL(Friction);
SERIALIZE_VAL(MaxSlopeAngle);
SERIALIZE_VAL(MaxStrength);
END_SERIALIZE();
}
bool Deserialize(const json& j)
{
if (j.contains("Friction"))
{
Friction = j["Friction"];
}
DESERIALIZE_VAL(AutoStepping);
DESERIALIZE_VAL(Friction);
DESERIALIZE_VAL(MaxSlopeAngle);
if (j.contains("MaxSlopeAngle"))
if (j.contains("StickToFloorStepDown"))
{
MaxSlopeAngle = j["MaxSlopeAngle"];
DESERIALIZE_VEC3(j["StickToFloorStepDown"], StickToFloorStepDown);
}
if (j.contains("StepDownExtra"))
{
DESERIALIZE_VEC3(j["StepDownExtra"], StepDownExtra);
}
if (j.contains("SteppingStepUp"))
{
DESERIALIZE_VEC3(j["SteppingStepUp"], SteppingStepUp);
}
DESERIALIZE_VAL(SteppingMinDistance);
DESERIALIZE_VAL(SteppingForwardDistance);
DESERIALIZE_VAL(Friction);
DESERIALIZE_VAL(MaxSlopeAngle);
DESERIALIZE_VAL(MaxStrength);
return true;
}
};

View File

@@ -45,9 +45,9 @@ namespace Nuake
m_Environement = CreateRef<Environment>();
// Adding systems - Order is important
m_Systems.push_back(CreateRef<PhysicsSystem>(this));
m_Systems.push_back(CreateRef<ScriptingSystem>(this));
m_Systems.push_back(CreateRef<TransformSystem>(this));
m_Systems.push_back(CreateRef<PhysicsSystem>(this));
m_Systems.push_back(CreateRef<ParticleSystem>(this));
m_SceneRenderer = new SceneRenderer();
@@ -105,7 +105,6 @@ namespace Nuake
{
for (auto& system : m_Systems)
{
Logger::Log("Init system");
if (!system->Init())
{
return false;

View File

@@ -315,23 +315,37 @@ namespace Nuake
Entity entity = Entity({ e, m_Scene });
auto [transformComponent, characterControllerComponent] = characterControllerView.get<TransformComponent, CharacterControllerComponent>(e);
Ref<Physics::PhysicShape> shape;
if (entity.HasComponent<CapsuleColliderComponent>())
{
const auto& capsuleColliderComponent = entity.GetComponent<CapsuleColliderComponent>();
auto& capsule = capsuleColliderComponent.Capsule;
const float friction = characterControllerComponent.Friction;
const float maxSlopeAngle = characterControllerComponent.MaxSlopeAngle;
auto characterController = CreateRef<Physics::CharacterController>(capsule, friction, maxSlopeAngle);
characterController->SetEntity(entity); // Used to link back to the entity
characterController->Position = transformComponent.GetGlobalPosition();
characterController->Rotation = transformComponent.GetGlobalRotation();
characterControllerComponent.CharacterController = characterController;
PhysicsManager::Get().RegisterCharacterController(characterControllerComponent.CharacterController);
shape = capsuleColliderComponent.Capsule;
}
else if (entity.HasComponent<BoxColliderComponent>())
{
const auto& capsuleColliderComponent = entity.GetComponent<BoxColliderComponent>();
shape = capsuleColliderComponent.Box;
}
// TODO: Other types of collider supported for character controller?
Physics::CharacterControllerSettings settings
{
shape,
characterControllerComponent.Friction,
characterControllerComponent.MaxSlopeAngle,
characterControllerComponent.AutoStepping,
characterControllerComponent.StickToFloorStepDown,
characterControllerComponent.StepDownExtra,
characterControllerComponent.SteppingStepUp,
characterControllerComponent.SteppingMinDistance,
characterControllerComponent.SteppingForwardDistance
};
auto characterController = CreateRef<Physics::CharacterController>(std::move(settings));
characterController->SetEntity(entity); // Used to link back to the entity
characterController->Position = transformComponent.GetGlobalPosition();
characterController->Rotation = transformComponent.GetGlobalRotation();
characterControllerComponent.SetCharacterController(characterController);
PhysicsManager::Get().RegisterCharacterController(characterController);
}
}

View File

@@ -42,6 +42,7 @@ namespace Nuake
RegisterMethod("EntityHasComponent(_,_)", (void*)EntityHasComponent);
RegisterMethod("GetTranslation_(_)", (void*)GetTranslation);
RegisterMethod("GetLocalTranslation_(_)", (void*)GetLocalTranslation);
RegisterMethod("SetTranslation_(_,_,_,_)", (void*)SetTranslation);
RegisterMethod("GetRotation_(_)", (void*)GetRotation);
RegisterMethod("SetRotation_(_,_,_,_)", (void*)SetRotation);
@@ -290,7 +291,7 @@ namespace Nuake
Entity ent = Entity((entt::entity)handle, Engine::GetCurrentScene().get());
auto& characterController = ent.GetComponent<CharacterControllerComponent>();
characterController.CharacterController->MoveAndSlide(Vector3(x, y, z));
characterController.GetCharacterController()->MoveAndSlide(Vector3(x, y, z));
}
static void AddForce(WrenVM* vm)
@@ -323,6 +324,24 @@ namespace Nuake
wrenInsertInList(vm, 0, 2, 3);
}
static void GetLocalTranslation(WrenVM* vm)
{
double handle = wrenGetSlotDouble(vm, 1);
Entity ent = Entity((entt::entity)handle, Engine::GetCurrentScene().get());
auto& transform = ent.GetComponent<TransformComponent>();
// set the slots
Vector3 position = transform.GetLocalPosition();
wrenSetSlotDouble(vm, 1, position.x);
wrenSetSlotDouble(vm, 2, position.y);
wrenSetSlotDouble(vm, 3, position.z);
// Fill the list
wrenSetSlotNewList(vm, 0);
wrenInsertInList(vm, 0, 0, 1);
wrenInsertInList(vm, 0, 1, 2);
wrenInsertInList(vm, 0, 2, 3);
}
static void GetRotation(WrenVM* vm)
{
double handle = wrenGetSlotDouble(vm, 1);