physics save/load added
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
50
src/features/editScene/components/PhysicsCollider.hpp
Normal file
50
src/features/editScene/components/PhysicsCollider.hpp
Normal 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
|
||||
40
src/features/editScene/components/RigidBody.hpp
Normal file
40
src/features/editScene/components/RigidBody.hpp
Normal 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
|
||||
1837
src/features/editScene/physics/physics.cpp
Normal file
1837
src/features/editScene/physics/physics.cpp
Normal file
File diff suppressed because it is too large
Load Diff
225
src/features/editScene/physics/physics.h
Normal file
225
src/features/editScene/physics/physics.h
Normal 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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
243
src/features/editScene/systems/PhysicsSystem.cpp
Normal file
243
src/features/editScene/systems/PhysicsSystem.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
61
src/features/editScene/systems/PhysicsSystem.hpp
Normal file
61
src/features/editScene/systems/PhysicsSystem.hpp
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
299
src/features/editScene/ui/PhysicsColliderEditor.cpp
Normal file
299
src/features/editScene/ui/PhysicsColliderEditor.cpp
Normal 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", ¤tType, 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;
|
||||
}
|
||||
34
src/features/editScene/ui/PhysicsColliderEditor.hpp
Normal file
34
src/features/editScene/ui/PhysicsColliderEditor.hpp
Normal 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
|
||||
78
src/features/editScene/ui/RigidBodyEditor.cpp
Normal file
78
src/features/editScene/ui/RigidBodyEditor.cpp
Normal 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", ¤tType, 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;
|
||||
}
|
||||
18
src/features/editScene/ui/RigidBodyEditor.hpp
Normal file
18
src/features/editScene/ui/RigidBodyEditor.hpp
Normal 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
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user