Added character physics but it does not work yet

This commit is contained in:
2026-04-19 23:45:00 +03:00
parent 529476d8cd
commit fb6881998c
12 changed files with 484 additions and 0 deletions

View File

@@ -27,6 +27,7 @@ set(EDITSCENE_SOURCES
systems/FurnitureLibrary.cpp
systems/CharacterSlotSystem.cpp
systems/AnimationTreeSystem.cpp
systems/CharacterSystem.cpp
ui/TransformEditor.cpp
ui/RenderableEditor.cpp
ui/PhysicsColliderEditor.cpp
@@ -43,6 +44,7 @@ set(EDITSCENE_SOURCES
ui/TriangleBufferEditor.cpp
ui/CharacterSlotsEditor.cpp
ui/AnimationTreeEditor.cpp
ui/CharacterEditor.cpp
ui/CellGridEditor.cpp
ui/LotEditor.cpp
ui/DistrictEditor.cpp
@@ -65,6 +67,7 @@ set(EDITSCENE_SOURCES
components/CharacterSlotsModule.cpp
components/AnimationTreeModule.cpp
components/AnimationTree.cpp
components/CharacterModule.cpp
components/CellGridModule.cpp
components/CellGridEditorsModule.cpp
components/CellGrid.cpp
@@ -93,6 +96,7 @@ set(EDITSCENE_HEADERS
components/TriangleBuffer.hpp
components/CharacterSlots.hpp
components/AnimationTree.hpp
components/Character.hpp
components/CellGrid.hpp
systems/EditorUISystem.hpp
systems/CellGridSystem.hpp
@@ -102,6 +106,7 @@ set(EDITSCENE_HEADERS
systems/ProceduralMeshSystem.hpp
systems/CharacterSlotSystem.hpp
systems/AnimationTreeSystem.hpp
systems/CharacterSystem.hpp
systems/ProceduralTextureSystem.hpp
systems/StaticGeometrySystem.hpp
systems/SceneSerializer.hpp
@@ -128,6 +133,7 @@ set(EDITSCENE_HEADERS
ui/TriangleBufferEditor.hpp
ui/CharacterSlotsEditor.hpp
ui/AnimationTreeEditor.hpp
ui/CharacterEditor.hpp
ui/CellGridEditor.hpp
ui/LotEditor.hpp
ui/DistrictEditor.hpp

View File

@@ -11,6 +11,7 @@
#include "systems/ProceduralMeshSystem.hpp"
#include "systems/CharacterSlotSystem.hpp"
#include "systems/AnimationTreeSystem.hpp"
#include "systems/CharacterSystem.hpp"
#include "systems/CellGridSystem.hpp"
#include "systems/RoomLayoutSystem.hpp"
#include "camera/EditorCamera.hpp"
@@ -33,6 +34,7 @@
#include "components/TriangleBuffer.hpp"
#include "components/CharacterSlots.hpp"
#include "components/AnimationTree.hpp"
#include "components/Character.hpp"
#include "components/CellGrid.hpp"
#include "components/CellGridModule.hpp"
#include <OgreRTShaderSystem.h>
@@ -125,6 +127,7 @@ EditorApp::~EditorApp()
// Release all systems
m_characterSlotSystem.reset();
m_animationTreeSystem.reset();
m_characterSystem.reset();
m_proceduralMeshSystem.reset();
m_proceduralMaterialSystem.reset();
m_proceduralTextureSystem.reset();
@@ -239,6 +242,11 @@ void EditorApp::setup()
m_world, m_sceneMgr);
m_animationTreeSystem->initialize();
// Setup Character physics system
m_characterSystem = std::make_unique<CharacterSystem>(
m_world, m_sceneMgr);
m_characterSystem->initialize();
// Setup CellGrid system
m_cellGridSystem =
std::make_unique<CellGridSystem>(m_world, m_sceneMgr);
@@ -311,6 +319,9 @@ void EditorApp::setupECS()
// Register AnimationTree component
m_world.component<AnimationTreeComponent>();
// Register Character component
m_world.component<CharacterComponent>();
// Register CellGrid/Town components
CellGridModule::registerComponents(m_world);
}
@@ -436,6 +447,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
m_camera->update(evt.timeSinceLastFrame);
}
// Update character physics (before main physics step)
if (m_characterSystem) {
m_characterSystem->update(evt.timeSinceLastFrame);
}
// Update physics
if (m_physicsSystem) {
m_physicsSystem->update(evt.timeSinceLastFrame);

View File

@@ -23,6 +23,7 @@ class ProceduralMaterialSystem;
class ProceduralMeshSystem;
class CharacterSlotSystem;
class AnimationTreeSystem;
class CharacterSystem;
class CellGridSystem;
class RoomLayoutSystem;
@@ -120,6 +121,7 @@ private:
std::unique_ptr<ProceduralMeshSystem> m_proceduralMeshSystem;
std::unique_ptr<CharacterSlotSystem> m_characterSlotSystem;
std::unique_ptr<AnimationTreeSystem> m_animationTreeSystem;
std::unique_ptr<CharacterSystem> m_characterSystem;
std::unique_ptr<CellGridSystem> m_cellGridSystem;
std::unique_ptr<RoomLayoutSystem> m_roomLayoutSystem;

View File

@@ -0,0 +1,37 @@
#ifndef EDITSCENE_CHARACTER_HPP
#define EDITSCENE_CHARACTER_HPP
#pragma once
#include <Ogre.h>
/**
* Character physics component
*
* Attaches a Jolt JPH::Character (kinematic capsule) to the entity.
* The entity may also have CharacterSlotsComponent; the character
* physics lives on the same entity as the visual character.
*
* Child entities can add extra collision shapes via PhysicsColliderComponent.
*/
struct CharacterComponent {
/* Capsule dimensions */
float radius = 0.3f;
float height = 1.8f; /* cylinder height (excluding spherical caps) */
/* Offset from the entity's scene node */
Ogre::Vector3 offset = Ogre::Vector3::ZERO;
/* Current linear velocity (m/s), applied each frame by CharacterSystem */
Ogre::Vector3 linearVelocity = Ogre::Vector3::ZERO;
/* Enable/disable physics character */
bool enabled = true;
/* Dirty flag — triggers rebuild of the Jolt character */
bool dirty = true;
float getHalfHeight() const { return height * 0.5f; }
float getTotalHeight() const { return height + 2.0f * radius; }
};
#endif // EDITSCENE_CHARACTER_HPP

View File

@@ -0,0 +1,20 @@
#include "../ui/ComponentRegistration.hpp"
#include "Character.hpp"
#include "../ui/CharacterEditor.hpp"
REGISTER_COMPONENT_GROUP("Character Physics", "Physics",
CharacterComponent, CharacterEditor)
{
registry.registerComponent<CharacterComponent>(
"Character Physics", "Physics", std::make_unique<CharacterEditor>(),
// Adder
[](flecs::entity e) {
if (!e.has<CharacterComponent>())
e.set<CharacterComponent>({});
},
// Remover
[](flecs::entity e) {
if (e.has<CharacterComponent>())
e.remove<CharacterComponent>();
});
}

View File

@@ -446,6 +446,10 @@ Ogre::Vector3 convert(const JPH::Vec3Arg &vec)
{
return { vec[0], vec[1], vec[2] };
}
Ogre::Vector3 convert(const JPH::RVec3Arg &vec)
{
return { (float)vec[0], (float)vec[1], (float)vec[2] };
}
JPH::RVec3 convert(const Ogre::Vector3 &vec)
{
return { vec.x, vec.y, vec.z };
@@ -1002,6 +1006,26 @@ public:
characters.insert(ch);
return ch;
}
JPH::CharacterBase *createCharacter(Ogre::SceneNode *node,
JPH::ShapeRefC shape)
{
JPH::CharacterSettings settings;
settings.mLayer = Layers::MOVING;
settings.mShape = shape;
settings.mSupportingVolume =
JPH::Plane(JPH::Vec3::sAxisY(), -0.2f);
JPH::Character *ch = new JPH::Character(
&settings,
JoltPhysics::convert(node->_getDerivedPosition()),
JoltPhysics::convert(node->_getDerivedOrientation()), 0,
&physics_system);
JPH::BodyID id = ch->GetBodyID();
id2node[id] = node;
node2id[node] = id;
characterBodies.insert(id);
characters.insert(ch);
return ch;
}
JPH::ShapeRefC createBoxShape(Ogre::Vector3 extents)
{
JPH::Vec3 h(extents.x, extents.y, extents.z);
@@ -1742,6 +1766,11 @@ JPH::CharacterBase *JoltPhysicsWrapper::createCharacter(Ogre::SceneNode *node,
{
return phys->createCharacter(node, characterHeight, characterRadius);
}
JPH::CharacterBase *JoltPhysicsWrapper::createCharacter(Ogre::SceneNode *node,
JPH::ShapeRefC shape)
{
return phys->createCharacter(node, shape);
}
void JoltPhysicsWrapper::addShapeToCompound(JPH::Ref<JPH::Shape> compoundShape,
JPH::ShapeRefC childShape,
const Ogre::Vector3 &position,

View File

@@ -54,6 +54,8 @@ template<class T> T convert(const Ogre::Vector3 &vec)
{
return { vec.x, vec.y, vec.z };
}
Ogre::Vector3 convert(const JPH::RVec3Arg &vec);
JPH::RVec3 convert(const Ogre::Vector3 &vec);
Ogre::Quaternion convert(const JPH::QuatArg &rot);
JPH::Quat convert(const Ogre::Quaternion &rot);
struct ShapeData;
@@ -166,6 +168,8 @@ public:
JPH::CharacterBase *createCharacter(Ogre::SceneNode *node,
float characterHeight,
float characterRadius);
JPH::CharacterBase *createCharacter(Ogre::SceneNode *node,
JPH::ShapeRefC shape);
void addShapeToCompound(JPH::Ref<JPH::Shape> compoundShape,
JPH::ShapeRefC childShape,
const Ogre::Vector3 &position,

View File

@@ -0,0 +1,242 @@
#include "CharacterSystem.hpp"
#include <Jolt/Physics/Character/Character.h>
#include <OgreLogManager.h>
CharacterSystem::CharacterSystem(flecs::world &world,
Ogre::SceneManager *sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
{
m_world.observer<CharacterComponent>("CharacterCleanup")
.event(flecs::OnRemove)
.each([this](flecs::entity e, CharacterComponent &) {
teardownEntity(e);
});
}
CharacterSystem::~CharacterSystem()
{
std::vector<flecs::entity_t> toRemove;
for (auto &pair : m_states)
toRemove.push_back(pair.first);
for (auto id : toRemove)
teardownEntity(m_world.entity(id));
}
void CharacterSystem::initialize()
{
m_physics = JoltPhysicsWrapper::getSingletonPtr();
m_initialized = true;
}
JPH::ShapeRefC CharacterSystem::createColliderShape(
PhysicsColliderComponent &collider)
{
if (!collider.shapeDirty && collider.shape)
return collider.shape;
JPH::ShapeRefC result;
switch (collider.shapeType) {
case PhysicsColliderComponent::ShapeType::Box:
result = m_physics->createBoxShape(collider.parameters);
break;
case PhysicsColliderComponent::ShapeType::Sphere:
result = m_physics->createSphereShape(collider.radius);
break;
case PhysicsColliderComponent::ShapeType::Capsule:
result = m_physics->createCapsuleShape(collider.halfHeight,
collider.radius);
break;
case PhysicsColliderComponent::ShapeType::Cylinder:
result = m_physics->createCylinderShape(collider.halfHeight,
collider.radius);
break;
case PhysicsColliderComponent::ShapeType::Mesh: {
if (!collider.meshName.empty()) {
try {
result = m_physics->createMeshShape(collider.meshName);
} catch (...) {
Ogre::LogManager::getSingleton().logMessage(
"Failed to create mesh shape: " + collider.meshName);
}
}
break;
}
case PhysicsColliderComponent::ShapeType::ConvexHull: {
if (!collider.meshName.empty()) {
try {
result = m_physics->createConvexHullShape(
collider.meshName);
} catch (...) {
Ogre::LogManager::getSingleton().logMessage(
"Failed to create convex hull shape: " +
collider.meshName);
}
}
break;
}
}
if (!result)
result = m_physics->createBoxShape(Ogre::Vector3(0.1f, 0.1f,
0.1f));
if (collider.offset != Ogre::Vector3::ZERO ||
collider.rotationOffset != Ogre::Quaternion::IDENTITY)
result = m_physics->createRotatedTranslatedShape(
collider.offset, collider.rotationOffset, result);
collider.shape = result;
collider.shapeDirty = false;
return result;
}
JPH::ShapeRefC CharacterSystem::buildCharacterShape(flecs::entity e,
CharacterComponent &cc)
{
std::vector<JPH::ShapeRefC> shapes;
std::vector<Ogre::Vector3> positions;
std::vector<Ogre::Quaternion> rotations;
/* Main capsule */
float halfHeight = cc.getHalfHeight();
JPH::ShapeRefC capsule = m_physics->createCapsuleShape(halfHeight,
cc.radius);
/* Shift capsule so its bottom sits at y = 0 */
Ogre::Vector3 capsulePos(0.0f, halfHeight + cc.radius, 0.0f);
capsule = m_physics->createRotatedTranslatedShape(
capsulePos + cc.offset, Ogre::Quaternion::IDENTITY,
capsule);
shapes.push_back(capsule);
positions.push_back(Ogre::Vector3::ZERO);
rotations.push_back(Ogre::Quaternion::IDENTITY);
/* Child colliders */
e.children([&](flecs::entity child) {
if (!child.has<PhysicsColliderComponent>() ||
!child.has<TransformComponent>())
return;
auto &collider = child.get_mut<PhysicsColliderComponent>();
auto &transform = child.get<TransformComponent>();
JPH::ShapeRefC shape = createColliderShape(collider);
if (shape) {
shapes.push_back(shape);
positions.push_back(transform.position);
rotations.push_back(transform.rotation);
}
});
if (shapes.size() == 1)
return shapes[0];
return m_physics->createStaticCompoundShape(shapes, positions,
rotations);
}
void CharacterSystem::setupEntity(flecs::entity e,
CharacterComponent &cc)
{
if (!m_physics)
return;
if (!e.has<TransformComponent>())
return;
auto &transform = e.get<TransformComponent>();
if (!transform.node)
return;
teardownEntity(e);
JPH::ShapeRefC shape = buildCharacterShape(e, cc);
if (!shape)
return;
JPH::CharacterBase *base = m_physics->createCharacter(
transform.node, shape);
if (!base)
return;
auto *ch = static_cast<JPH::Character *>(base);
ch->AddToPhysicsSystem();
CharacterState state;
state.character = ch;
state.sceneNode = transform.node;
m_states[e.id()] = state;
cc.dirty = false;
}
void CharacterSystem::teardownEntity(flecs::entity e)
{
auto it = m_states.find(e.id());
if (it == m_states.end())
return;
CharacterState &state = it->second;
if (state.character) {
state.character->RemoveFromPhysicsSystem();
m_physics->destroyCharacter(
std::shared_ptr<JPH::Character>(state.character));
}
m_states.erase(it);
}
void CharacterSystem::update(float deltaTime)
{
if (!m_initialized || !m_physics)
return;
m_world.query<CharacterComponent, TransformComponent>().each(
[this, deltaTime](flecs::entity e,
CharacterComponent &cc,
TransformComponent &transform) {
if (!cc.enabled) {
teardownEntity(e);
return;
}
if (cc.dirty) {
setupEntity(e, cc);
cc.dirty = false;
}
auto it = m_states.find(e.id());
if (it == m_states.end())
return;
CharacterState &state = it->second;
if (!state.character || !state.sceneNode)
return;
/* Read current physics position */
Ogre::Vector3 charPos = JoltPhysics::convert(
state.character->GetPosition());
Ogre::Vector3 nodePos =
state.sceneNode->_getDerivedPosition();
/* If scene node was moved externally (editor gizmo),
* teleport character there */
Ogre::Vector3 diff = nodePos - charPos;
if (diff.squaredLength() > 0.001f) {
state.character->SetPosition(
JoltPhysics::convert(nodePos));
charPos = nodePos;
}
/* Integrate velocity */
if (cc.linearVelocity.squaredLength() > 0.0f) {
Ogre::Vector3 newPos = charPos +
cc.linearVelocity * deltaTime;
state.character->SetPosition(
JoltPhysics::convert(newPos));
}
/* Sync rotation from scene node */
state.character->SetRotation(
JoltPhysics::convert(
state.sceneNode->_getDerivedOrientation()));
});
}

View File

@@ -0,0 +1,52 @@
#ifndef EDITSCENE_CHARACTERSYSTEM_HPP
#define EDITSCENE_CHARACTERSYSTEM_HPP
#pragma once
#include <flecs.h>
#include <Ogre.h>
#include <memory>
#include <unordered_map>
#include "../../physics/physics.h"
#include "../components/Character.hpp"
#include "../components/Transform.hpp"
#include "../components/PhysicsCollider.hpp"
/**
* System that manages JPH::Character instances for entities with
* CharacterComponent.
*
* - Creates a capsule shape (plus child colliders) for each character
* - Integrates linearVelocity each frame
* - Syncs position/rotation between physics and SceneNode
*/
class CharacterSystem {
public:
CharacterSystem(flecs::world &world, Ogre::SceneManager *sceneMgr);
~CharacterSystem();
void initialize();
void update(float deltaTime);
private:
struct CharacterState {
JPH::Character *character = nullptr;
Ogre::SceneNode *sceneNode = nullptr;
};
void setupEntity(flecs::entity e, CharacterComponent &cc);
void teardownEntity(flecs::entity e);
JPH::ShapeRefC buildCharacterShape(flecs::entity e,
CharacterComponent &cc);
JPH::ShapeRefC createColliderShape(PhysicsColliderComponent &collider);
flecs::world &m_world;
Ogre::SceneManager *m_sceneMgr;
JoltPhysicsWrapper *m_physics = nullptr;
bool m_initialized = false;
std::unordered_map<flecs::entity_t, CharacterState> m_states;
};
#endif // EDITSCENE_CHARACTERSYSTEM_HPP

View File

@@ -17,6 +17,7 @@
#include "../components/TriangleBuffer.hpp"
#include "../components/LodSettings.hpp"
#include "../components/CharacterSlots.hpp"
#include "../components/Character.hpp"
#include "../components/AnimationTree.hpp"
#include "../components/CellGrid.hpp"
#include "../ui/TransformEditor.hpp"
@@ -629,6 +630,13 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
componentCount++;
}
// Render Character if present
if (entity.has<CharacterComponent>()) {
auto &cc = entity.get_mut<CharacterComponent>();
m_componentRegistry.render<CharacterComponent>(entity, cc);
componentCount++;
}
// Render CharacterSlots if present
if (entity.has<CharacterSlotsComponent>()) {
auto &cs = entity.get_mut<CharacterSlotsComponent>();

View File

@@ -0,0 +1,50 @@
#include "CharacterEditor.hpp"
#include <imgui.h>
bool CharacterEditor::renderComponent(flecs::entity entity,
CharacterComponent &cc)
{
(void)entity;
bool modified = false;
if (ImGui::Checkbox("Enabled", &cc.enabled))
modified = true;
ImGui::Separator();
ImGui::Text("Capsule");
if (ImGui::SliderFloat("Radius", &cc.radius, 0.05f, 2.0f,
"%.2f")) {
modified = true;
}
if (ImGui::SliderFloat("Height", &cc.height, 0.1f, 5.0f,
"%.2f")) {
modified = true;
}
ImGui::TextDisabled("Total: %.2f m",
cc.getTotalHeight());
ImGui::Separator();
ImGui::Text("Offset");
float off[3] = { cc.offset.x, cc.offset.y, cc.offset.z };
if (ImGui::InputFloat3("Position", off, "%.2f")) {
cc.offset = Ogre::Vector3(off[0], off[1], off[2]);
modified = true;
}
ImGui::Separator();
ImGui::Text("Velocity");
float vel[3] = { cc.linearVelocity.x, cc.linearVelocity.y,
cc.linearVelocity.z };
if (ImGui::InputFloat3("Linear (m/s)", vel, "%.2f")) {
cc.linearVelocity = Ogre::Vector3(vel[0], vel[1],
vel[2]);
modified = true;
}
if (modified)
cc.dirty = true;
return modified;
}

View File

@@ -0,0 +1,18 @@
#ifndef EDITSCENE_CHARACTEREDITOR_HPP
#define EDITSCENE_CHARACTEREDITOR_HPP
#pragma once
#include "ComponentEditor.hpp"
#include "../components/Character.hpp"
/**
* Editor for CharacterComponent
*/
class CharacterEditor : public ComponentEditor<CharacterComponent> {
public:
bool renderComponent(flecs::entity entity,
CharacterComponent &cc) override;
const char *getName() const override { return "Character Physics"; }
};
#endif // EDITSCENE_CHARACTEREDITOR_HPP