physics save/load added

This commit is contained in:
2026-04-02 02:40:40 +03:00
parent 2371ba3b19
commit d68da8fc04
21 changed files with 3348 additions and 3 deletions

View File

@@ -5,6 +5,7 @@ find_package(OGRE REQUIRED COMPONENTS Bites Overlay CONFIG)
find_package(flecs REQUIRED CONFIG)
find_package(nlohmann_json REQUIRED)
find_package(SDL2 REQUIRED)
find_package(Jolt REQUIRED)
# Collect all source files
set(EDITSCENE_SOURCES
@@ -12,10 +13,14 @@ set(EDITSCENE_SOURCES
EditorApp.cpp
systems/EditorUISystem.cpp
systems/SceneSerializer.cpp
systems/PhysicsSystem.cpp
ui/TransformEditor.cpp
ui/RenderableEditor.cpp
ui/PhysicsColliderEditor.cpp
ui/RigidBodyEditor.cpp
camera/EditorCamera.cpp
gizmo/Gizmo.cpp
physics/physics.cpp
)
set(EDITSCENE_HEADERS
@@ -24,24 +29,34 @@ set(EDITSCENE_HEADERS
components/Renderable.hpp
components/EntityName.hpp
components/Relationship.hpp
components/PhysicsCollider.hpp
components/RigidBody.hpp
systems/EditorUISystem.hpp
systems/SceneSerializer.hpp
systems/PhysicsSystem.hpp
ui/ComponentEditor.hpp
ui/ComponentRegistry.hpp
ui/TransformEditor.hpp
ui/RenderableEditor.hpp
ui/PhysicsColliderEditor.hpp
ui/RigidBodyEditor.hpp
camera/EditorCamera.hpp
gizmo/Gizmo.hpp
physics/physics.h
)
add_executable(editSceneEditor ${EDITSCENE_SOURCES} ${EDITSCENE_HEADERS})
# Define JPH_DEBUG_RENDERER for physics debug drawing
target_compile_definitions(editSceneEditor PRIVATE JPH_DEBUG_RENDERER)
target_link_libraries(editSceneEditor
OgreMain
OgreBites
OgreOverlay
flecs::flecs_static
nlohmann_json::nlohmann_json
Jolt::Jolt
)
target_include_directories(editSceneEditor PRIVATE

View File

@@ -1,11 +1,14 @@
#include <iostream>
#include "EditorApp.hpp"
#include "systems/EditorUISystem.hpp"
#include "systems/PhysicsSystem.hpp"
#include "camera/EditorCamera.hpp"
#include "components/EntityName.hpp"
#include "components/Transform.hpp"
#include "components/Renderable.hpp"
#include "components/EditorMarker.hpp"
#include "components/PhysicsCollider.hpp"
#include "components/RigidBody.hpp"
#include <OgreRTShaderSystem.h>
#include <imgui.h>
@@ -104,6 +107,11 @@ void EditorApp::setup()
// Setup UI system
m_uiSystem = std::make_unique<EditorUISystem>(m_world, m_sceneMgr);
// Setup physics system
m_physicsSystem = std::make_unique<EditorPhysicsSystem>(m_world, m_sceneMgr);
m_physicsSystem->initialize();
m_uiSystem->setPhysicsSystem(m_physicsSystem.get());
// Add default entities to UI cache
for (auto &e : m_defaultEntities) {
m_uiSystem->addEntity(e);
@@ -131,6 +139,10 @@ void EditorApp::setupECS()
m_world.component<TransformComponent>();
m_world.component<RenderableComponent>();
m_world.component<EditorMarkerComponent>();
// Register physics components
m_world.component<PhysicsColliderComponent>();
m_world.component<RigidBodyComponent>();
}
void EditorApp::createDefaultEntities()
@@ -254,6 +266,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
m_camera->update(evt.timeSinceLastFrame);
}
// Update physics
if (m_physicsSystem) {
m_physicsSystem->update(evt.timeSinceLastFrame);
}
// Don't call base class - it crashes when iterating input listeners
return true;
}

View File

@@ -13,6 +13,7 @@
// Forward declarations
class EditorUISystem;
class EditorCamera;
class EditorPhysicsSystem;
/**
* RenderTargetListener for ImGui frame management
@@ -78,6 +79,7 @@ private:
std::unique_ptr<EditorUISystem> m_uiSystem;
std::unique_ptr<EditorCamera> m_camera;
std::unique_ptr<ImGuiRenderListener> m_imguiListener;
std::unique_ptr<EditorPhysicsSystem> m_physicsSystem;
// State
uint16_t m_currentModifiers;

View File

@@ -0,0 +1,50 @@
#ifndef EDITSCENE_PHYSICSCOLLIDER_HPP
#define EDITSCENE_PHYSICSCOLLIDER_HPP
#pragma once
#include <Ogre.h>
#include <Jolt/Jolt.h>
#include <Jolt/Physics/Collision/Shape/Shape.h>
#include <memory>
/**
* Physics Collider component
* Defines a collision shape for use in rigid bodies
* Multiple colliders can be attached to a single rigid body (as children)
*/
struct PhysicsColliderComponent {
// Collider shape types
enum class ShapeType {
Box,
Sphere,
Capsule,
Cylinder,
Mesh,
ConvexHull
};
ShapeType shapeType = ShapeType::Box;
// Shape parameters
// Box: half extents
// Sphere: radius
// Capsule/Cylinder: halfHeight, radius
Ogre::Vector3 parameters = Ogre::Vector3(0.5f, 0.5f, 0.5f); // For box: half extents
float radius = 0.5f; // For sphere, capsule, cylinder
float halfHeight = 1.0f; // For capsule, cylinder
// For mesh/convex hull colliders
std::string meshName;
// Offset from parent transform
Ogre::Vector3 offset = Ogre::Vector3::ZERO;
Ogre::Quaternion rotationOffset = Ogre::Quaternion::IDENTITY;
// The actual Jolt shape (created when body is built)
JPH::ShapeRefC shape;
bool shapeDirty = true; // Needs rebuild
void markDirty() { shapeDirty = true; }
};
#endif // EDITSCENE_PHYSICSCOLLIDER_HPP

View File

@@ -0,0 +1,40 @@
#ifndef EDITSCENE_RIGIDBODY_HPP
#define EDITSCENE_RIGIDBODY_HPP
#pragma once
#include <Ogre.h>
#include <Jolt/Jolt.h>
#include <Jolt/Physics/Body/BodyID.h>
/**
* RigidBody component
* Represents a physics body that can be static or dynamic
* Uses children's Collider components for collision shapes
*/
struct RigidBodyComponent {
// Body type
enum class BodyType {
Static, // Non-moving (infinite mass)
Dynamic, // Affected by forces and collisions
Kinematic // Controlled by code, not physics
};
BodyType bodyType = BodyType::Static;
// Mass (only used for dynamic bodies)
float mass = 1.0f;
// Physics properties
float friction = 0.5f;
float restitution = 0.0f; // Bounciness
bool isSensor = false; // Detects collisions but doesn't respond
// The Jolt body ID
JPH::BodyID bodyID;
bool bodyCreated = false;
bool bodyDirty = true; // Needs rebuild
void markDirty() { bodyDirty = true; }
};
#endif // EDITSCENE_RIGIDBODY_HPP

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,225 @@
#ifndef __PHYSICS_H_
#define __PHYSICS_H_
#include <Ogre.h>
#include <OgreSingleton.h>
#include <Jolt/Jolt.h>
#include <Jolt/Physics/Collision/Shape/Shape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Collision/ObjectLayer.h>
#include <Jolt/Physics/Collision/ContactListener.h>
#include <Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/EActivation.h>
void physics();
namespace JPH
{
class CharacterBase;
class Character;
class ContactManifold;
class ContactSettings;
class SubShapeIDPair;
}
// Layer that objects can be in, determines which other objects it can collide with
// Typically you at least want to have 1 layer for moving bodies and 1 layer for static bodies, but you can have more
// layers if you want. E.g. you could have a layer for high detail collision (which is not used by the physics simulation
// but only if you do collision testing).
namespace Layers
{
static constexpr JPH::ObjectLayer NON_MOVING = 0;
static constexpr JPH::ObjectLayer MOVING = 1;
static constexpr JPH::ObjectLayer SENSORS = 2;
static constexpr JPH::ObjectLayer NUM_LAYERS = 3;
};
// Each broadphase layer results in a separate bounding volume tree in the broad phase. You at least want to have
// a layer for non-moving and moving objects to avoid having to update a tree full of static objects every frame.
// You can have a 1-on-1 mapping between object layers and broadphase layers (like in this case) but if you have
// many object layers you'll be creating many broad phase trees, which is not efficient. If you want to fine tune
// your broadphase layers define JPH_TRACK_BROADPHASE_STATS and look at the stats reported on the TTY.
namespace BroadPhaseLayers
{
static constexpr JPH::BroadPhaseLayer NON_MOVING(0);
static constexpr JPH::BroadPhaseLayer MOVING(1);
static constexpr uint NUM_LAYERS(2);
};
namespace JoltPhysics
{
template<class T>
Ogre::Vector3 convert(const T &vec)
{
return {vec[0], vec[1], vec[2]};
}
template<class T> T convert(const Ogre::Vector3 &vec)
{
return { vec.x, vec.y, vec.z };
}
Ogre::Quaternion convert(const JPH::QuatArg &rot);
JPH::Quat convert(const Ogre::Quaternion &rot);
struct ShapeData;
struct CompoundShapeBuilder {
JPH::StaticCompoundShapeSettings shapeSettings;
void addShape(JPH::ShapeRefC shape, const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation);
JPH::ShapeRefC build();
};
class ContactListener : public JPH::ContactListener {
public:
struct ContactReport {
bool entered;
JPH::BodyID id1, id2;
JPH::ContactManifold manifold;
JPH::ContactSettings settings;
};
private:
std::list<ContactReport> reports;
std::function<void(const ContactReport &report)> dispatch;
std::map<JPH::BodyID, std::function<void(const ContactReport &report)> >
listeners;
public:
ContactListener();
JPH::ValidateResult OnContactValidate(
const JPH::Body &inBody1, const JPH::Body &inBody2,
JPH::RVec3Arg inBaseOffset,
const JPH::CollideShapeResult &inCollisionResult) override;
void OnContactAdded(const JPH::Body &inBody1, const JPH::Body &inBody2,
const JPH::ContactManifold &inManifold,
JPH::ContactSettings &ioSettings) override;
void OnContactPersisted(const JPH::Body &inBody1,
const JPH::Body &inBody2,
const JPH::ContactManifold &inManifold,
JPH::ContactSettings &ioSettings) override;
void
OnContactRemoved(const JPH::SubShapeIDPair &inSubShapePair) override;
void setDispatch(const std::function<void(const ContactReport &report)>
dispatcher)
{
dispatch = dispatcher;
}
void addListener(
const JPH::BodyID &id,
const std::function<void(const ContactReport &report)> listener)
{
listeners[id] = listener;
}
void removeListener(const JPH::BodyID &id)
{
listeners.erase(id);
}
void update();
};
}
class JoltPhysicsWrapper : public Ogre::Singleton<JoltPhysicsWrapper> {
public:
JoltPhysics::ContactListener contacts;
JoltPhysicsWrapper(Ogre::SceneManager *scnMgr,
Ogre::SceneNode *cameraNode);
~JoltPhysicsWrapper();
void update(float dt);
void addBody(const JPH::BodyID &body, JPH::EActivation activation);
bool isAdded(const JPH::BodyID &body);
JPH::ShapeRefC createBoxShape(const Ogre::Vector3 &extents);
JPH::ShapeRefC createSphereShape(float radius);
JPH::ShapeRefC createCapsuleShape(float halfHeight, float radius);
JPH::ShapeRefC createCylinderShape(float halfHeight, float radius);
JPH::ShapeRefC createMeshShape(Ogre::MeshPtr mesh);
JPH::ShapeRefC createMeshShape(Ogre::String meshName);
JPH::ShapeRefC createConvexHullShape(Ogre::MeshPtr mesh);
JPH::ShapeRefC createConvexHullShape(Ogre::String meshName);
JPH::ShapeRefC createHeightfieldShape(const float *samples,
Ogre::Vector3 offset,
Ogre::Vector3 scale,
int sampleCount);
JPH::ShapeRefC createMutableCompoundShape(
const std::vector<JPH::ShapeRefC> &shapes,
const std::vector<Ogre::Vector3> &positions,
const std::vector<Ogre::Quaternion> &rotations);
JPH::ShapeRefC createStaticCompoundShape(
const std::vector<JPH::ShapeRefC> &shapes,
const std::vector<Ogre::Vector3> &positions,
const std::vector<Ogre::Quaternion> &rotations);
JPH::ShapeRefC
createOffsetCenterOfMassShape(const Ogre::Vector3 &offset,
JPH::ShapeRefC shape);
JPH::ShapeRefC
createRotatedTranslatedShape(const Ogre::Vector3 &offset, const Ogre::Quaternion rotation,
JPH::ShapeRefC shape);
JPH::BodyID createBody(const JPH::BodyCreationSettings &settings);
JPH::BodyID createBody(const JPH::Shape *shape, float mass,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation,
JPH::EMotionType motion, JPH::ObjectLayer layer);
JPH::BodyID createBody(const JPH::Shape *shape, float mass,
Ogre::SceneNode *node, JPH::EMotionType motion,
JPH::ObjectLayer layer);
JPH::BodyID createSensor(const JPH::Shape *shape,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation,
JPH::EMotionType motion,
JPH::ObjectLayer layer);
JPH::BodyID createSensor(const JPH::Shape *shape, Ogre::SceneNode *node,
JPH::EMotionType motion,
JPH::ObjectLayer layer);
JPH::CharacterBase *createCharacter(Ogre::SceneNode *node,
float characterHeight,
float characterRadius);
void addShapeToCompound(JPH::Ref<JPH::Shape> compoundShape,
JPH::ShapeRefC childShape,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation);
void removeBody(const JPH::BodyID &id);
void destroyBody(const JPH::BodyID &id);
void setDebugDraw(bool enable);
void broadphaseQuery(float dt, const Ogre::Vector3 &position,
std::set<JPH::BodyID> &inWater);
void applyBuoyancyImpulse(JPH::BodyID id,
const Ogre::Vector3 &surfacePosition,
const Ogre::Vector3 &surfaceNormal,
float buoyancy, float linearDrag,
float angularDrag,
const Ogre::Vector3 &fluidVelocity,
const Ogre::Vector3 &gravity, float dt);
void applyBuoyancyImpulse(JPH::BodyID id,
const Ogre::Vector3 &surfacePosition,
const Ogre::Vector3 &surfaceNormal,
float buoyancy, float linearDrag,
float angularDrag,
const Ogre::Vector3 &fluidVelocity, float dt);
bool isActive(JPH::BodyID id);
void activate(JPH::BodyID id);
Ogre::Vector3 getPosition(JPH::BodyID id);
void setPosition(JPH::BodyID id, const Ogre::Vector3 &position,
bool activate = true);
Ogre::Quaternion getRotation(JPH::BodyID id);
void setRotation(JPH::BodyID id, const Ogre::Quaternion &rotation,
bool activate = true);
void getPositionAndRotation(JPH::BodyID id, Ogre::Vector3 &position,
Ogre::Quaternion &rotation);
void setPositionAndRotation(JPH::BodyID id,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation,
bool activate = true);
Ogre::Vector3 getLinearVelocity(JPH::BodyID id);
Ogre::Vector3 getAngularVelocity(JPH::BodyID id);
float getFriction(JPH::BodyID id);
void setFriction(JPH::BodyID id, float friction);
void addAngularImpulse(const JPH::BodyID &id,
const Ogre::Vector3 &impulse);
void setDispatch(std::function<void(const JoltPhysics::ContactListener::
ContactReport &report)>
dispatcher);
void addContactListener(
const JPH::BodyID &id,
const std::function<void(const JoltPhysics::ContactListener::
ContactReport &report)>
listener);
void removeContactListener(const JPH::BodyID &id);
bool raycastQuery(Ogre::Vector3 startPoint, Ogre::Vector3 endPoint,
Ogre::Vector3 &position, JPH::BodyID &id);
bool bodyIsCharacter(JPH::BodyID id) const;
void destroyCharacter(std::shared_ptr<JPH::Character> ch);
};
#endif

View File

@@ -0,0 +1,65 @@
// Simple editor materials for GLES2 compatibility
material Editor/Red
{
technique
{
pass
{
lighting off
ambient 1 0 0
diffuse 1 0 0
}
}
}
material Editor/Green
{
technique
{
pass
{
lighting off
ambient 0 1 0
diffuse 0 1 0
}
}
}
material Editor/Blue
{
technique
{
pass
{
lighting off
ambient 0 0 1
diffuse 0 0 1
}
}
}
material Editor/White
{
technique
{
pass
{
lighting off
ambient 1 1 1
diffuse 1 1 1
}
}
}
material Editor/Grey
{
technique
{
pass
{
lighting off
ambient 0.5 0.5 0.5
diffuse 0.5 0.5 0.5
}
}
}

View File

@@ -0,0 +1,52 @@
// Simple test material for GL3Plus
material Test/Red
{
technique
{
pass
{
lighting off
ambient 1 0 0
diffuse 1 0 0
}
}
}
material Test/Green
{
technique
{
pass
{
lighting off
ambient 0 1 0
diffuse 0 1 0
}
}
}
material Test/Blue
{
technique
{
pass
{
lighting off
ambient 0 0 1
diffuse 0 0 1
}
}
}
material Test/White
{
technique
{
pass
{
lighting off
ambient 1 1 1
diffuse 1 1 1
}
}
}

View File

@@ -3,8 +3,13 @@
#include "../components/Transform.hpp"
#include "../components/Renderable.hpp"
#include "../components/EditorMarker.hpp"
#include "../components/PhysicsCollider.hpp"
#include "../components/RigidBody.hpp"
#include "../ui/TransformEditor.hpp"
#include "../ui/RenderableEditor.hpp"
#include "../ui/PhysicsColliderEditor.hpp"
#include "../ui/RigidBodyEditor.hpp"
#include "PhysicsSystem.hpp"
#include <imgui.h>
#include <algorithm>
#include <filesystem>
@@ -104,6 +109,40 @@ void EditorUISystem::registerComponentEditors()
e.remove<RenderableComponent>();
}
});
// Register RigidBody component
auto rigidBodyEditor = std::make_unique<RigidBodyEditor>();
m_componentRegistry.registerComponent<RigidBodyComponent>(
"Rigid Body", std::move(rigidBodyEditor),
// Adder
[this](flecs::entity e) {
if (!e.has<RigidBodyComponent>()) {
e.set<RigidBodyComponent>({});
}
},
// Remover
[this](flecs::entity e) {
if (e.has<RigidBodyComponent>()) {
e.remove<RigidBodyComponent>();
}
});
// Register PhysicsCollider component
auto colliderEditor = std::make_unique<PhysicsColliderEditor>(m_sceneMgr);
m_componentRegistry.registerComponent<PhysicsColliderComponent>(
"Physics Collider", std::move(colliderEditor),
// Adder
[this](flecs::entity e) {
if (!e.has<PhysicsColliderComponent>()) {
e.set<PhysicsColliderComponent>({});
}
},
// Remover
[this](flecs::entity e) {
if (e.has<PhysicsColliderComponent>()) {
e.remove<PhysicsColliderComponent>();
}
});
}
void EditorUISystem::update()
@@ -199,6 +238,18 @@ void EditorUISystem::renderHierarchyWindow()
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("When enabled, child entities' SceneNodes are parented to their parent's SceneNode, inheriting transforms. When disabled, SceneNodes are created at root level.");
}
// Physics debug draw toggle
if (m_physicsSystem) {
bool debugDraw = m_physicsSystem->isDebugDrawEnabled();
if (ImGui::Checkbox("Physics Debug Draw", &debugDraw)) {
m_physicsSystem->setDebugDraw(debugDraw);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Toggle physics collision shape visualization");
}
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
@@ -290,6 +341,10 @@ void EditorUISystem::renderEntityNode(flecs::entity entity, int depth)
indicators += " [T]";
if (entity.has<RenderableComponent>())
indicators += " [R]";
if (entity.has<RigidBodyComponent>())
indicators += " [RB]";
if (entity.has<PhysicsColliderComponent>())
indicators += " [C]";
snprintf(label, sizeof(label), "%s%s##%llu", name.c_str(),
indicators.c_str(), (unsigned long long)entity.id());
@@ -410,9 +465,23 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
renderable);
}
if (entity.has<RigidBodyComponent>()) {
auto &rigidBody = entity.get_mut<RigidBodyComponent>();
m_componentRegistry.render<RigidBodyComponent>(entity,
rigidBody);
}
if (entity.has<PhysicsColliderComponent>()) {
auto &collider = entity.get_mut<PhysicsColliderComponent>();
m_componentRegistry.render<PhysicsColliderComponent>(entity,
collider);
}
// Show message if no components
if (!entity.has<TransformComponent>() &&
!entity.has<RenderableComponent>()) {
!entity.has<RenderableComponent>() &&
!entity.has<RigidBodyComponent>() &&
!entity.has<PhysicsColliderComponent>()) {
ImGui::TextDisabled("No components");
ImGui::Text("Click 'Add Component' to add components");
}
@@ -428,6 +497,8 @@ void EditorUISystem::renderAddComponentMenu(flecs::entity entity)
bool hasTransform = entity.has<TransformComponent>();
bool hasRenderable = entity.has<RenderableComponent>();
bool hasRigidBody = entity.has<RigidBodyComponent>();
bool hasCollider = entity.has<PhysicsColliderComponent>();
if (!hasTransform) {
if (ImGui::MenuItem("Transform")) {
@@ -445,8 +516,21 @@ void EditorUISystem::renderAddComponentMenu(flecs::entity entity)
}
}
if (hasTransform && hasRenderable) {
ImGui::TextDisabled("All components added");
ImGui::Separator();
ImGui::Text("Physics:");
if (!hasRigidBody) {
if (ImGui::MenuItem("Rigid Body")) {
m_componentRegistry
.addComponent<RigidBodyComponent>(entity);
}
}
if (!hasCollider) {
if (ImGui::MenuItem("Physics Collider")) {
m_componentRegistry
.addComponent<PhysicsColliderComponent>(entity);
}
}
ImGui::EndPopup();
@@ -478,6 +562,22 @@ void EditorUISystem::renderRemoveComponentMenu(flecs::entity entity)
}
}
if (entity.has<RigidBodyComponent>()) {
hasAny = true;
if (ImGui::MenuItem("Rigid Body")) {
m_componentRegistry.removeComponent<
RigidBodyComponent>(entity);
}
}
if (entity.has<PhysicsColliderComponent>()) {
hasAny = true;
if (ImGui::MenuItem("Physics Collider")) {
m_componentRegistry.removeComponent<
PhysicsColliderComponent>(entity);
}
}
if (!hasAny) {
ImGui::TextDisabled("No components to remove");
}

View File

@@ -11,6 +11,9 @@
#include "../gizmo/Gizmo.hpp"
#include "SceneSerializer.hpp"
// Forward declaration
class EditorPhysicsSystem;
/**
* Main UI system for the scene editor
* Handles rendering of:
@@ -66,6 +69,11 @@ public:
bool getParentSceneNodes() const { return m_parentSceneNodes; }
void setParentSceneNodes(bool value) { m_parentSceneNodes = value; }
/**
* Set physics system for debug toggle
*/
void setPhysicsSystem(EditorPhysicsSystem* physics) { m_physicsSystem = physics; }
/**
* Save scene to file
*/
@@ -124,6 +132,9 @@ private:
// Settings
bool m_parentSceneNodes = true; // Whether child entities inherit parent's SceneNode
// Physics system reference (for debug toggle)
EditorPhysicsSystem* m_physicsSystem = nullptr;
// File dialog state
bool m_showFileDialog = false;
bool m_fileDialogIsSave = false; // true = save, false = load

View File

@@ -0,0 +1,243 @@
#include "PhysicsSystem.hpp"
#include "../components/Transform.hpp"
#include <OgreLogManager.h>
EditorPhysicsSystem::EditorPhysicsSystem(flecs::world& world,
Ogre::SceneManager* sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
, m_rigidBodyQuery(world.query<RigidBodyComponent, TransformComponent>())
{
}
EditorPhysicsSystem::~EditorPhysicsSystem() = default;
void EditorPhysicsSystem::initialize()
{
if (m_initialized) return;
// Create physics wrapper
Ogre::SceneNode* cameraNode = m_sceneMgr->getRootSceneNode()->createChildSceneNode("PhysicsCameraNode");
m_physics = std::make_unique<JoltPhysicsWrapper>(m_sceneMgr, cameraNode);
m_initialized = true;
Ogre::LogManager::getSingleton().logMessage("Physics system initialized");
}
void EditorPhysicsSystem::update(float deltaTime)
{
if (!m_initialized || !m_physics) return;
// Sync bodies before simulation
syncBodies();
// Step physics
m_physics->update(deltaTime);
}
void EditorPhysicsSystem::syncBodies()
{
if (!m_initialized || !m_physics) return;
// Process all rigid bodies
m_rigidBodyQuery.each([&](flecs::entity entity,
RigidBodyComponent& rigidBody,
TransformComponent& transform) {
if (rigidBody.bodyDirty || !rigidBody.bodyCreated) {
updateRigidBody(entity, rigidBody, transform);
}
});
}
JPH::ShapeRefC EditorPhysicsSystem::createShape(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 {
// Ensure mesh is loaded first
Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().load(
collider.meshName, "General");
if (mesh && mesh->isLoaded()) {
result = m_physics->createMeshShape(collider.meshName);
}
} catch (const Ogre::Exception& e) {
Ogre::LogManager::getSingleton().logMessage(
"Failed to create mesh shape: " + collider.meshName +
" - " + e.getDescription());
}
}
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 no shape created, default to a small box
if (!result) {
result = m_physics->createBoxShape(Ogre::Vector3(0.1f, 0.1f, 0.1f));
}
// Apply offset if any
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 EditorPhysicsSystem::buildCompoundShape(flecs::entity rigidBodyEntity)
{
std::vector<JPH::ShapeRefC> shapes;
std::vector<Ogre::Vector3> positions;
std::vector<Ogre::Quaternion> rotations;
// Collect all collider children
rigidBodyEntity.children([&](flecs::entity child) {
if (child.has<PhysicsColliderComponent>() &&
child.has<TransformComponent>()) {
auto& collider = child.get_mut<PhysicsColliderComponent>();
auto& transform = child.get<TransformComponent>();
JPH::ShapeRefC shape = createShape(collider);
if (shape) {
shapes.push_back(shape);
positions.push_back(transform.position);
rotations.push_back(transform.rotation);
}
}
});
if (shapes.empty()) {
// No colliders, use default box
return m_physics->createBoxShape(Ogre::Vector3(0.5f, 0.5f, 0.5f));
}
if (shapes.size() == 1) {
// Single shape - use rotated translated shape
return m_physics->createRotatedTranslatedShape(positions[0],
rotations[0],
shapes[0]);
}
// Multiple shapes - create static compound
return m_physics->createStaticCompoundShape(shapes, positions, rotations);
}
void EditorPhysicsSystem::updateRigidBody(flecs::entity entity,
RigidBodyComponent& rigidBody,
TransformComponent& transform)
{
// Remove existing body if any
if (rigidBody.bodyCreated) {
removeRigidBody(rigidBody);
}
// Build collision shape
JPH::ShapeRefC shape = buildCompoundShape(entity);
if (!shape) return;
// Determine motion type and layer
JPH::EMotionType motionType;
JPH::ObjectLayer layer;
switch (rigidBody.bodyType) {
case RigidBodyComponent::BodyType::Static:
motionType = JPH::EMotionType::Static;
layer = Layers::NON_MOVING;
break;
case RigidBodyComponent::BodyType::Kinematic:
motionType = JPH::EMotionType::Kinematic;
layer = Layers::MOVING;
break;
case RigidBodyComponent::BodyType::Dynamic:
default:
motionType = JPH::EMotionType::Dynamic;
layer = Layers::MOVING;
break;
}
// Create body
if (rigidBody.isSensor) {
rigidBody.bodyID = m_physics->createSensor(shape, transform.node,
motionType, layer);
} else {
rigidBody.bodyID = m_physics->createBody(shape, rigidBody.mass,
transform.node,
motionType, layer);
}
if (rigidBody.bodyID.IsInvalid()) {
Ogre::LogManager::getSingleton().logMessage(
"Failed to create rigid body");
return;
}
// Set properties
m_physics->setFriction(rigidBody.bodyID, rigidBody.friction);
// Add to physics world
m_physics->addBody(rigidBody.bodyID, JPH::EActivation::Activate);
rigidBody.bodyCreated = true;
rigidBody.bodyDirty = false;
Ogre::LogManager::getSingleton().logMessage("Rigid body created");
}
void EditorPhysicsSystem::removeRigidBody(RigidBodyComponent& rigidBody)
{
if (!rigidBody.bodyCreated || rigidBody.bodyID.IsInvalid()) return;
m_physics->removeBody(rigidBody.bodyID);
m_physics->destroyBody(rigidBody.bodyID);
rigidBody.bodyCreated = false;
rigidBody.bodyID = JPH::BodyID();
}
void EditorPhysicsSystem::setDebugDraw(bool enable)
{
m_debugDraw = enable;
if (m_physics) {
m_physics->setDebugDraw(enable);
}
}

View File

@@ -0,0 +1,61 @@
#ifndef EDITSCENE_PHYSICSSYSTEM_HPP
#define EDITSCENE_PHYSICSSYSTEM_HPP
#pragma once
#include <flecs.h>
#include <Ogre.h>
#include <memory>
#include "../physics/physics.h"
#include "../components/RigidBody.hpp"
#include "../components/PhysicsCollider.hpp"
/**
* Physics system for the editor
* Manages Jolt physics world and creates/updates bodies from components
*/
class EditorPhysicsSystem {
public:
EditorPhysicsSystem(flecs::world& world, Ogre::SceneManager* sceneMgr);
~EditorPhysicsSystem();
// Initialize physics world
void initialize();
// Update physics simulation
void update(float deltaTime);
// Process component changes and create/update bodies
void syncBodies();
// Enable/disable debug drawing
void setDebugDraw(bool enable);
bool isDebugDrawEnabled() const { return m_debugDraw; }
bool isInitialized() const { return m_initialized; }
private:
// Create a shape from collider component
JPH::ShapeRefC createShape(PhysicsColliderComponent& collider);
// Build compound shape from multiple colliders
JPH::ShapeRefC buildCompoundShape(flecs::entity rigidBodyEntity);
// Create or update a rigid body
void updateRigidBody(flecs::entity entity, RigidBodyComponent& rigidBody,
class TransformComponent& transform);
// Remove a rigid body
void removeRigidBody(RigidBodyComponent& rigidBody);
flecs::world& m_world;
Ogre::SceneManager* m_sceneMgr;
std::unique_ptr<JoltPhysicsWrapper> m_physics;
bool m_initialized = false;
bool m_debugDraw = false;
// Query for entities with RigidBody
flecs::query<RigidBodyComponent, TransformComponent> m_rigidBodyQuery;
};
#endif // EDITSCENE_PHYSICSSYSTEM_HPP

View File

@@ -3,6 +3,8 @@
#include "../components/Renderable.hpp"
#include "../components/EntityName.hpp"
#include "../components/EditorMarker.hpp"
#include "../components/RigidBody.hpp"
#include "../components/PhysicsCollider.hpp"
#include "EditorUISystem.hpp"
#include <fstream>
#include <iostream>
@@ -113,6 +115,14 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
json["renderable"] = serializeRenderable(entity);
}
if (entity.has<RigidBodyComponent>()) {
json["rigidBody"] = serializeRigidBody(entity);
}
if (entity.has<PhysicsColliderComponent>()) {
json["collider"] = serializeCollider(entity);
}
// Serialize children
json["children"] = nlohmann::json::array();
entity.children([&](flecs::entity child) {
@@ -156,6 +166,14 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit
deserializeRenderable(entity, json["renderable"]);
}
if (json.contains("rigidBody")) {
deserializeRigidBody(entity, json["rigidBody"]);
}
if (json.contains("collider")) {
deserializeCollider(entity, json["collider"]);
}
// Add to UI system if provided
if (uiSystem) {
uiSystem->addEntity(entity);
@@ -213,6 +231,88 @@ nlohmann::json SceneSerializer::serializeEntityName(flecs::entity entity)
return json;
}
nlohmann::json SceneSerializer::serializeRigidBody(flecs::entity entity)
{
auto& rb = entity.get<RigidBodyComponent>();
nlohmann::json json;
// Serialize body type as string
switch (rb.bodyType) {
case RigidBodyComponent::BodyType::Static:
json["bodyType"] = "static";
break;
case RigidBodyComponent::BodyType::Dynamic:
json["bodyType"] = "dynamic";
break;
case RigidBodyComponent::BodyType::Kinematic:
json["bodyType"] = "kinematic";
break;
}
json["mass"] = rb.mass;
json["friction"] = rb.friction;
json["restitution"] = rb.restitution;
json["isSensor"] = rb.isSensor;
return json;
}
nlohmann::json SceneSerializer::serializeCollider(flecs::entity entity)
{
auto& collider = entity.get<PhysicsColliderComponent>();
nlohmann::json json;
// Serialize shape type as string
switch (collider.shapeType) {
case PhysicsColliderComponent::ShapeType::Box:
json["shapeType"] = "box";
break;
case PhysicsColliderComponent::ShapeType::Sphere:
json["shapeType"] = "sphere";
break;
case PhysicsColliderComponent::ShapeType::Capsule:
json["shapeType"] = "capsule";
break;
case PhysicsColliderComponent::ShapeType::Cylinder:
json["shapeType"] = "cylinder";
break;
case PhysicsColliderComponent::ShapeType::Mesh:
json["shapeType"] = "mesh";
break;
case PhysicsColliderComponent::ShapeType::ConvexHull:
json["shapeType"] = "convexHull";
break;
}
// Serialize shape parameters
json["parameters"] = {
{"x", collider.parameters.x},
{"y", collider.parameters.y},
{"z", collider.parameters.z}
};
json["radius"] = collider.radius;
json["halfHeight"] = collider.halfHeight;
json["meshName"] = collider.meshName;
// Serialize offset
json["offset"] = {
{"x", collider.offset.x},
{"y", collider.offset.y},
{"z", collider.offset.z}
};
// Serialize rotation offset
json["rotationOffset"] = {
{"w", collider.rotationOffset.w},
{"x", collider.rotationOffset.x},
{"y", collider.rotationOffset.y},
{"z", collider.rotationOffset.z}
};
return json;
}
void SceneSerializer::deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity)
{
TransformComponent transform;
@@ -299,3 +399,90 @@ void SceneSerializer::deserializeEntityName(flecs::entity entity, const nlohmann
std::string name = json.value("name", "Entity");
entity.set<EntityNameComponent>(EntityNameComponent(name));
}
void SceneSerializer::deserializeRigidBody(flecs::entity entity, const nlohmann::json& json)
{
RigidBodyComponent rb;
// Deserialize body type
std::string bodyType = json.value("bodyType", "static");
if (bodyType == "static") {
rb.bodyType = RigidBodyComponent::BodyType::Static;
} else if (bodyType == "dynamic") {
rb.bodyType = RigidBodyComponent::BodyType::Dynamic;
} else if (bodyType == "kinematic") {
rb.bodyType = RigidBodyComponent::BodyType::Kinematic;
}
rb.mass = json.value("mass", 1.0f);
rb.friction = json.value("friction", 0.5f);
rb.restitution = json.value("restitution", 0.0f);
rb.isSensor = json.value("isSensor", false);
// Mark as dirty so physics system will create the body
rb.bodyDirty = true;
rb.bodyCreated = false;
entity.set<RigidBodyComponent>(rb);
}
void SceneSerializer::deserializeCollider(flecs::entity entity, const nlohmann::json& json)
{
PhysicsColliderComponent collider;
// Deserialize shape type
std::string shapeType = json.value("shapeType", "box");
if (shapeType == "box") {
collider.shapeType = PhysicsColliderComponent::ShapeType::Box;
} else if (shapeType == "sphere") {
collider.shapeType = PhysicsColliderComponent::ShapeType::Sphere;
} else if (shapeType == "capsule") {
collider.shapeType = PhysicsColliderComponent::ShapeType::Capsule;
} else if (shapeType == "cylinder") {
collider.shapeType = PhysicsColliderComponent::ShapeType::Cylinder;
} else if (shapeType == "mesh") {
collider.shapeType = PhysicsColliderComponent::ShapeType::Mesh;
} else if (shapeType == "convexHull") {
collider.shapeType = PhysicsColliderComponent::ShapeType::ConvexHull;
}
// Deserialize shape parameters
if (json.contains("parameters")) {
auto& params = json["parameters"];
collider.parameters = Ogre::Vector3(
params.value("x", 0.5f),
params.value("y", 0.5f),
params.value("z", 0.5f)
);
}
collider.radius = json.value("radius", 0.5f);
collider.halfHeight = json.value("halfHeight", 1.0f);
collider.meshName = json.value("meshName", "");
// Deserialize offset
if (json.contains("offset")) {
auto& off = json["offset"];
collider.offset = Ogre::Vector3(
off.value("x", 0.0f),
off.value("y", 0.0f),
off.value("z", 0.0f)
);
}
// Deserialize rotation offset
if (json.contains("rotationOffset")) {
auto& rot = json["rotationOffset"];
collider.rotationOffset = Ogre::Quaternion(
rot.value("w", 1.0f),
rot.value("x", 0.0f),
rot.value("y", 0.0f),
rot.value("z", 0.0f)
);
}
// Mark as dirty so physics system will create the shape
collider.shapeDirty = true;
entity.set<PhysicsColliderComponent>(collider);
}

View File

@@ -42,11 +42,15 @@ private:
nlohmann::json serializeTransform(flecs::entity entity);
nlohmann::json serializeRenderable(flecs::entity entity);
nlohmann::json serializeEntityName(flecs::entity entity);
nlohmann::json serializeRigidBody(flecs::entity entity);
nlohmann::json serializeCollider(flecs::entity entity);
// Component deserialization
void deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity);
void deserializeRenderable(flecs::entity entity, const nlohmann::json& json);
void deserializeEntityName(flecs::entity entity, const nlohmann::json& json);
void deserializeRigidBody(flecs::entity entity, const nlohmann::json& json);
void deserializeCollider(flecs::entity entity, const nlohmann::json& json);
flecs::world& m_world;
Ogre::SceneManager* m_sceneMgr;

View File

@@ -0,0 +1,299 @@
#include "PhysicsColliderEditor.hpp"
#include "../components/RigidBody.hpp"
#include <imgui.h>
#include <OgreResourceGroupManager.h>
#include <algorithm>
PhysicsColliderEditor::PhysicsColliderEditor(Ogre::SceneManager *sceneMgr)
: m_sceneMgr(sceneMgr)
{
}
void PhysicsColliderEditor::scanMeshFiles()
{
if (m_meshesScanned)
return;
m_meshFiles.clear();
// Get all resource groups
Ogre::ResourceGroupManager &rgm = Ogre::ResourceGroupManager::getSingleton();
Ogre::StringVector groups = rgm.getResourceGroups();
for (const auto &group : groups) {
// Get file info for this group
Ogre::FileInfoListPtr fileList = rgm.findResourceFileInfo(group, "*");
if (fileList) {
for (const auto &fileInfo : *fileList) {
const std::string &filename = fileInfo.filename;
// Check for supported extensions
if (filename.size() > 5) {
std::string ext = filename.substr(filename.find_last_of('.') + 1);
// Convert to lowercase
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
if (ext == "mesh" || ext == "glb" || ext == "gltf") {
m_meshFiles.push_back(filename);
}
}
}
}
}
// Sort and remove duplicates
std::sort(m_meshFiles.begin(), m_meshFiles.end());
m_meshFiles.erase(std::unique(m_meshFiles.begin(), m_meshFiles.end()),
m_meshFiles.end());
m_meshesScanned = true;
}
bool PhysicsColliderEditor::matchesSearch(const std::string &meshName,
const std::string &search)
{
if (search.empty())
return true;
std::string lowerMesh = meshName;
std::string lowerSearch = search;
std::transform(lowerMesh.begin(), lowerMesh.end(), lowerMesh.begin(),
::tolower);
std::transform(lowerSearch.begin(), lowerSearch.end(), lowerSearch.begin(),
::tolower);
return lowerMesh.find(lowerSearch) != std::string::npos;
}
void PhysicsColliderEditor::markParentRigidBodiesDirty(flecs::entity entity)
{
// Mark this entity's collider as dirty
if (entity.has<PhysicsColliderComponent>()) {
auto &collider = entity.get_mut<PhysicsColliderComponent>();
collider.markDirty();
}
// Check if parent has RigidBody component and mark it dirty
flecs::entity parent = entity.parent();
if (parent.is_valid() && parent.is_alive() && parent.has<RigidBodyComponent>()) {
auto &rigidBody = parent.get_mut<RigidBodyComponent>();
rigidBody.markDirty();
}
}
void PhysicsColliderEditor::renderMeshBrowser(PhysicsColliderComponent &collider)
{
if (ImGui::BeginPopupModal("Mesh Browser (Collider)", &m_showMeshBrowser,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Search:");
ImGui::SameLine();
ImGui::InputText("##search", m_searchBuffer, sizeof(m_searchBuffer));
ImGui::Separator();
// Show filtered mesh list
ImGui::BeginChild("MeshList", ImVec2(400, 300), true);
std::string searchStr(m_searchBuffer);
for (const auto &meshName : m_meshFiles) {
if (!matchesSearch(meshName, searchStr))
continue;
// Highlight current selection
bool isCurrent = (collider.meshName == meshName);
if (isCurrent) {
ImGui::PushStyleColor(ImGuiCol_Text,
ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
}
if (ImGui::Selectable(meshName.c_str(), isCurrent)) {
collider.meshName = meshName;
}
if (isCurrent) {
ImGui::PopStyleColor();
}
}
if (m_meshFiles.empty()) {
ImGui::TextDisabled("No mesh files found");
}
ImGui::EndChild();
ImGui::Separator();
// Show count
int visibleCount = 0;
for (const auto &meshName : m_meshFiles) {
if (matchesSearch(meshName, searchStr))
visibleCount++;
}
ImGui::Text("Showing %d of %zu meshes", visibleCount, m_meshFiles.size());
// Buttons
if (ImGui::Button("Select", ImVec2(120, 0))) {
m_showMeshBrowser = false;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
m_showMeshBrowser = false;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Refresh", ImVec2(120, 0))) {
m_meshesScanned = false;
scanMeshFiles();
}
ImGui::EndPopup();
}
}
bool PhysicsColliderEditor::renderComponent(flecs::entity entity,
PhysicsColliderComponent &collider)
{
bool modified = false;
if (ImGui::CollapsingHeader("Physics Collider",
ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
// Shape type selector
const char *shapeTypes[] = {
"Box", "Sphere", "Capsule", "Cylinder", "Mesh", "Convex Hull"
};
int currentType = static_cast<int>(collider.shapeType);
if (ImGui::Combo("Shape Type", &currentType, shapeTypes,
IM_ARRAYSIZE(shapeTypes))) {
collider.shapeType =
static_cast<PhysicsColliderComponent::ShapeType>(
currentType);
collider.markDirty();
markParentRigidBodiesDirty(entity);
modified = true;
}
// Shape-specific parameters
switch (collider.shapeType) {
case PhysicsColliderComponent::ShapeType::Box: {
float extents[3] = { collider.parameters.x,
collider.parameters.y,
collider.parameters.z };
if (ImGui::DragFloat3("Half Extents", extents, 0.01f,
0.001f)) {
collider.parameters =
Ogre::Vector3(extents[0], extents[1],
extents[2]);
collider.markDirty();
markParentRigidBodiesDirty(entity);
modified = true;
}
break;
}
case PhysicsColliderComponent::ShapeType::Sphere: {
if (ImGui::DragFloat("Radius", &collider.radius, 0.01f,
0.001f)) {
collider.markDirty();
markParentRigidBodiesDirty(entity);
modified = true;
}
break;
}
case PhysicsColliderComponent::ShapeType::Capsule:
case PhysicsColliderComponent::ShapeType::Cylinder: {
if (ImGui::DragFloat("Half Height", &collider.halfHeight, 0.01f,
0.001f)) {
collider.markDirty();
markParentRigidBodiesDirty(entity);
modified = true;
}
if (ImGui::DragFloat("Radius", &collider.radius, 0.01f,
0.001f)) {
collider.markDirty();
markParentRigidBodiesDirty(entity);
modified = true;
}
break;
}
case PhysicsColliderComponent::ShapeType::Mesh:
case PhysicsColliderComponent::ShapeType::ConvexHull: {
char meshName[256];
std::strncpy(meshName, collider.meshName.c_str(),
sizeof(meshName) - 1);
meshName[sizeof(meshName) - 1] = '\0';
if (ImGui::InputText("Mesh Name", meshName,
sizeof(meshName))) {
collider.meshName = meshName;
collider.markDirty();
markParentRigidBodiesDirty(entity);
modified = true;
}
ImGui::SameLine();
if (ImGui::Button("Browse...")) {
scanMeshFiles();
m_showMeshBrowser = true;
m_searchBuffer[0] = '\0';
}
// Mesh Browser Popup
if (m_showMeshBrowser) {
ImGui::OpenPopup("Mesh Browser (Collider)");
}
renderMeshBrowser(collider);
break;
}
}
// Offset from parent
float offset[3] = { collider.offset.x, collider.offset.y,
collider.offset.z };
if (ImGui::DragFloat3("Position Offset", offset, 0.01f)) {
collider.offset =
Ogre::Vector3(offset[0], offset[1], offset[2]);
collider.markDirty();
markParentRigidBodiesDirty(entity);
modified = true;
}
// Rotation offset
float yaw = 0.0f, pitch = 0.0f, roll = 0.0f;
if (ImGui::TreeNode("Rotation Offset")) {
Ogre::Radian y = collider.rotationOffset.getYaw();
Ogre::Radian p = collider.rotationOffset.getPitch();
Ogre::Radian r = collider.rotationOffset.getRoll();
yaw = y.valueDegrees();
pitch = p.valueDegrees();
roll = r.valueDegrees();
bool rotModified = false;
if (ImGui::DragFloat("Yaw", &yaw, 0.5f)) rotModified = true;
if (ImGui::DragFloat("Pitch", &pitch, 0.5f)) rotModified = true;
if (ImGui::DragFloat("Roll", &roll, 0.5f)) rotModified = true;
if (rotModified) {
Ogre::Quaternion q1(Ogre::Degree(yaw),
Ogre::Vector3::UNIT_Y);
Ogre::Quaternion q2(Ogre::Degree(pitch),
Ogre::Vector3::UNIT_X);
Ogre::Quaternion q3(Ogre::Degree(roll),
Ogre::Vector3::UNIT_Z);
collider.rotationOffset = q1 * q2 * q3;
collider.markDirty();
markParentRigidBodiesDirty(entity);
modified = true;
}
ImGui::TreePop();
}
ImGui::Unindent();
}
return modified;
}

View File

@@ -0,0 +1,34 @@
#ifndef EDITSCENE_PHYSICSCOLLIDEREDITOR_HPP
#define EDITSCENE_PHYSICSCOLLIDEREDITOR_HPP
#pragma once
#include "ComponentEditor.hpp"
#include "../components/PhysicsCollider.hpp"
#include <OgreSceneManager.h>
#include <vector>
#include <string>
/**
* Editor for PhysicsColliderComponent
*/
class PhysicsColliderEditor : public ComponentEditor<PhysicsColliderComponent> {
public:
PhysicsColliderEditor(Ogre::SceneManager *sceneMgr = nullptr);
bool renderComponent(flecs::entity entity,
PhysicsColliderComponent &collider) override;
const char *getName() const override { return "Physics Collider"; }
private:
void scanMeshFiles();
bool matchesSearch(const std::string &meshName, const std::string &search);
void renderMeshBrowser(PhysicsColliderComponent &collider);
void markParentRigidBodiesDirty(flecs::entity entity);
Ogre::SceneManager *m_sceneMgr;
std::vector<std::string> m_meshFiles;
bool m_meshesScanned = false;
bool m_showMeshBrowser = false;
char m_searchBuffer[256] = { 0 };
};
#endif // EDITSCENE_PHYSICSCOLLIDEREDITOR_HPP

View File

@@ -0,0 +1,78 @@
#include "RigidBodyEditor.hpp"
#include <imgui.h>
bool RigidBodyEditor::renderComponent(flecs::entity entity,
RigidBodyComponent &rigidBody)
{
bool modified = false;
if (ImGui::CollapsingHeader("Rigid Body",
ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
// Body type selector
const char *bodyTypes[] = { "Static", "Dynamic", "Kinematic" };
int currentType = static_cast<int>(rigidBody.bodyType);
if (ImGui::Combo("Body Type", &currentType, bodyTypes,
IM_ARRAYSIZE(bodyTypes))) {
rigidBody.bodyType =
static_cast<RigidBodyComponent::BodyType>(currentType);
rigidBody.markDirty();
modified = true;
}
// Mass (only for dynamic bodies)
bool isDynamic = rigidBody.bodyType ==
RigidBodyComponent::BodyType::Dynamic;
if (isDynamic) {
if (ImGui::DragFloat("Mass (kg)", &rigidBody.mass, 0.1f,
0.001f)) {
if (rigidBody.mass < 0.001f) rigidBody.mass = 0.001f;
rigidBody.markDirty();
modified = true;
}
} else {
ImGui::Text("Mass: N/A (infinite for static/kinematic)");
}
// Friction
if (ImGui::DragFloat("Friction", &rigidBody.friction, 0.01f, 0.0f,
1.0f)) {
rigidBody.markDirty();
modified = true;
}
// Restitution (bounciness)
if (ImGui::DragFloat("Restitution (Bounciness)",
&rigidBody.restitution, 0.01f, 0.0f, 1.0f)) {
rigidBody.markDirty();
modified = true;
}
// Is sensor
if (ImGui::Checkbox("Is Sensor", &rigidBody.isSensor)) {
rigidBody.markDirty();
modified = true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Sensors detect collisions but don't physically react");
}
// Status info
ImGui::Separator();
if (rigidBody.bodyCreated) {
ImGui::Text("Status: Body created");
} else {
ImGui::Text("Status: Body not created");
}
if (rigidBody.bodyDirty) {
ImGui::TextColored(ImVec4(1, 0.5f, 0, 1),
"(needs rebuild)");
}
ImGui::Unindent();
}
return modified;
}

View File

@@ -0,0 +1,18 @@
#ifndef EDITSCENE_RIGIDBODYEDITOR_HPP
#define EDITSCENE_RIGIDBODYEDITOR_HPP
#pragma once
#include "ComponentEditor.hpp"
#include "../components/RigidBody.hpp"
/**
* Editor for RigidBodyComponent
*/
class RigidBodyEditor : public ComponentEditor<RigidBodyComponent> {
public:
bool renderComponent(flecs::entity entity,
RigidBodyComponent &rigidBody) override;
const char *getName() const override { return "Rigid Body"; }
};
#endif // EDITSCENE_RIGIDBODYEDITOR_HPP

View File

@@ -1582,6 +1582,12 @@ JPH::ShapeRefC JoltPhysicsWrapper::createSphereShape(float radius)
return phys->createSphereShape(radius);
}
JPH::ShapeRefC JoltPhysicsWrapper::createCapsuleShape(float halfHeight,
float radius)
{
return phys->createCapsuleShape(halfHeight, radius);
}
JPH::ShapeRefC JoltPhysicsWrapper::createCylinderShape(float halfHeight,
float radius)
{

View File

@@ -123,6 +123,7 @@ public:
bool isAdded(const JPH::BodyID &body);
JPH::ShapeRefC createBoxShape(const Ogre::Vector3 &extents);
JPH::ShapeRefC createSphereShape(float radius);
JPH::ShapeRefC createCapsuleShape(float halfHeight, float radius);
JPH::ShapeRefC createCylinderShape(float halfHeight, float radius);
JPH::ShapeRefC createMeshShape(Ogre::MeshPtr mesh);
JPH::ShapeRefC createMeshShape(Ogre::String meshName);