From fb6881998c3f2b1d2e29432776a6aa9c5d34c204 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Sun, 19 Apr 2026 23:45:00 +0300 Subject: [PATCH] Added character physics but it does not work yet --- src/features/editScene/CMakeLists.txt | 6 + src/features/editScene/EditorApp.cpp | 16 ++ src/features/editScene/EditorApp.hpp | 2 + .../editScene/components/Character.hpp | 37 +++ .../editScene/components/CharacterModule.cpp | 20 ++ src/features/editScene/physics/physics.cpp | 29 +++ src/features/editScene/physics/physics.h | 4 + .../editScene/systems/CharacterSystem.cpp | 242 ++++++++++++++++++ .../editScene/systems/CharacterSystem.hpp | 52 ++++ .../editScene/systems/EditorUISystem.cpp | 8 + src/features/editScene/ui/CharacterEditor.cpp | 50 ++++ src/features/editScene/ui/CharacterEditor.hpp | 18 ++ 12 files changed, 484 insertions(+) create mode 100644 src/features/editScene/components/Character.hpp create mode 100644 src/features/editScene/components/CharacterModule.cpp create mode 100644 src/features/editScene/systems/CharacterSystem.cpp create mode 100644 src/features/editScene/systems/CharacterSystem.hpp create mode 100644 src/features/editScene/ui/CharacterEditor.cpp create mode 100644 src/features/editScene/ui/CharacterEditor.hpp diff --git a/src/features/editScene/CMakeLists.txt b/src/features/editScene/CMakeLists.txt index 7706e27..cda13cb 100644 --- a/src/features/editScene/CMakeLists.txt +++ b/src/features/editScene/CMakeLists.txt @@ -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 diff --git a/src/features/editScene/EditorApp.cpp b/src/features/editScene/EditorApp.cpp index 518f7fb..59e3249 100644 --- a/src/features/editScene/EditorApp.cpp +++ b/src/features/editScene/EditorApp.cpp @@ -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 @@ -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( + m_world, m_sceneMgr); + m_characterSystem->initialize(); + // Setup CellGrid system m_cellGridSystem = std::make_unique(m_world, m_sceneMgr); @@ -311,6 +319,9 @@ void EditorApp::setupECS() // Register AnimationTree component m_world.component(); + // Register Character component + m_world.component(); + // 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); diff --git a/src/features/editScene/EditorApp.hpp b/src/features/editScene/EditorApp.hpp index 6eb83a2..a0a5743 100644 --- a/src/features/editScene/EditorApp.hpp +++ b/src/features/editScene/EditorApp.hpp @@ -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 m_proceduralMeshSystem; std::unique_ptr m_characterSlotSystem; std::unique_ptr m_animationTreeSystem; + std::unique_ptr m_characterSystem; std::unique_ptr m_cellGridSystem; std::unique_ptr m_roomLayoutSystem; diff --git a/src/features/editScene/components/Character.hpp b/src/features/editScene/components/Character.hpp new file mode 100644 index 0000000..4d8593a --- /dev/null +++ b/src/features/editScene/components/Character.hpp @@ -0,0 +1,37 @@ +#ifndef EDITSCENE_CHARACTER_HPP +#define EDITSCENE_CHARACTER_HPP +#pragma once + +#include + +/** + * 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 diff --git a/src/features/editScene/components/CharacterModule.cpp b/src/features/editScene/components/CharacterModule.cpp new file mode 100644 index 0000000..154a609 --- /dev/null +++ b/src/features/editScene/components/CharacterModule.cpp @@ -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( + "Character Physics", "Physics", std::make_unique(), + // Adder + [](flecs::entity e) { + if (!e.has()) + e.set({}); + }, + // Remover + [](flecs::entity e) { + if (e.has()) + e.remove(); + }); +} diff --git a/src/features/editScene/physics/physics.cpp b/src/features/editScene/physics/physics.cpp index 088f935..0ac2cc2 100644 --- a/src/features/editScene/physics/physics.cpp +++ b/src/features/editScene/physics/physics.cpp @@ -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 compoundShape, JPH::ShapeRefC childShape, const Ogre::Vector3 &position, diff --git a/src/features/editScene/physics/physics.h b/src/features/editScene/physics/physics.h index 85880f0..ea61750 100644 --- a/src/features/editScene/physics/physics.h +++ b/src/features/editScene/physics/physics.h @@ -54,6 +54,8 @@ template 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 compoundShape, JPH::ShapeRefC childShape, const Ogre::Vector3 &position, diff --git a/src/features/editScene/systems/CharacterSystem.cpp b/src/features/editScene/systems/CharacterSystem.cpp new file mode 100644 index 0000000..3e27561 --- /dev/null +++ b/src/features/editScene/systems/CharacterSystem.cpp @@ -0,0 +1,242 @@ +#include "CharacterSystem.hpp" +#include +#include + +CharacterSystem::CharacterSystem(flecs::world &world, + Ogre::SceneManager *sceneMgr) + : m_world(world) + , m_sceneMgr(sceneMgr) +{ + m_world.observer("CharacterCleanup") + .event(flecs::OnRemove) + .each([this](flecs::entity e, CharacterComponent &) { + teardownEntity(e); + }); +} + +CharacterSystem::~CharacterSystem() +{ + std::vector 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 shapes; + std::vector positions; + std::vector 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() || + !child.has()) + return; + auto &collider = child.get_mut(); + auto &transform = child.get(); + + 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()) + return; + auto &transform = e.get(); + 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(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(state.character)); + } + + m_states.erase(it); +} + +void CharacterSystem::update(float deltaTime) +{ + if (!m_initialized || !m_physics) + return; + + m_world.query().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())); + }); +} diff --git a/src/features/editScene/systems/CharacterSystem.hpp b/src/features/editScene/systems/CharacterSystem.hpp new file mode 100644 index 0000000..412a3a4 --- /dev/null +++ b/src/features/editScene/systems/CharacterSystem.hpp @@ -0,0 +1,52 @@ +#ifndef EDITSCENE_CHARACTERSYSTEM_HPP +#define EDITSCENE_CHARACTERSYSTEM_HPP +#pragma once + +#include +#include +#include +#include + +#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 m_states; +}; + +#endif // EDITSCENE_CHARACTERSYSTEM_HPP diff --git a/src/features/editScene/systems/EditorUISystem.cpp b/src/features/editScene/systems/EditorUISystem.cpp index 1a8ae65..bd18ec7 100644 --- a/src/features/editScene/systems/EditorUISystem.cpp +++ b/src/features/editScene/systems/EditorUISystem.cpp @@ -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()) { + auto &cc = entity.get_mut(); + m_componentRegistry.render(entity, cc); + componentCount++; + } + // Render CharacterSlots if present if (entity.has()) { auto &cs = entity.get_mut(); diff --git a/src/features/editScene/ui/CharacterEditor.cpp b/src/features/editScene/ui/CharacterEditor.cpp new file mode 100644 index 0000000..f43201f --- /dev/null +++ b/src/features/editScene/ui/CharacterEditor.cpp @@ -0,0 +1,50 @@ +#include "CharacterEditor.hpp" +#include + +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; +} diff --git a/src/features/editScene/ui/CharacterEditor.hpp b/src/features/editScene/ui/CharacterEditor.hpp new file mode 100644 index 0000000..1791c3f --- /dev/null +++ b/src/features/editScene/ui/CharacterEditor.hpp @@ -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 { +public: + bool renderComponent(flecs::entity entity, + CharacterComponent &cc) override; + const char *getName() const override { return "Character Physics"; } +}; + +#endif // EDITSCENE_CHARACTEREDITOR_HPP