feat: add new fancy reflection

This commit is contained in:
2026-11-01 23:12:00 +01:00
parent 08acd84404
commit 6a85d17899
58 changed files with 380 additions and 1055 deletions
@@ -27,6 +27,7 @@ public:
std::string GetTypeName() const override { return "AudioSource"; }
void Reflect(FieldVisitor& v) override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
@@ -17,8 +17,7 @@ public:
std::string GetTypeName() const override { return "CameraComponent"; }
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void Reflect(FieldVisitor& v) override;
void ImGuiInspector() override;
// Places the camera at this object's world transform, looking down its local -Z.
@@ -19,9 +19,7 @@ public:
std::string GetTypeName() const override { return "LightComponent"; }
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
void SetType(LightType type) { m_Type = type; }
[[nodiscard]] LightType GetType() const { return m_Type; }
@@ -26,9 +26,10 @@ public:
std::string GetTypeName() const override { return "OrbitAndSpin"; }
nlohmann::json Serialize() const override;
void Reflect(FieldVisitor& v) override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void OnFieldsEdited() override;
// optional setters
void SetRadius(float r) { m_Radius = r; }
@@ -36,6 +37,7 @@ public:
private:
void BuildOrbitBasis(); // builds m_U/m_V from m_OrbitAxis
void Sanitize();
float m_Radius = 5.0f;
glm::vec3 m_Center{0.0f};
@@ -11,16 +11,13 @@ public:
void Update(float) override {}
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
std::string GetTypeName() const override
{
return "BoxCollider";
}
void DrawDebugLines(const glm::vec4& color) const override;
[[nodiscard]] PhysicsShapeDesc BuildPhysicsShape() const override {
@@ -17,24 +17,7 @@ public:
return "CapsuleCollider";
}
void ImGuiInspector() override;
nlohmann::json Serialize() const override {
auto data = SerializeCollider();
data["radius"] = m_Radius;
data["height"] = m_Height;
return data;
}
void Deserialize(const nlohmann::json& data) override {
DeserializeCollider(data);
if (data.contains("radius")) {
SetRadius(data.at("radius").get<float>());
}
if (data.contains("height")) {
SetHeight(data.at("height").get<float>());
}
}
void Reflect(FieldVisitor& v) override;
void DrawDebugLines(const glm::vec4& color) const override;
@@ -16,7 +16,7 @@ public:
[[nodiscard]] bool AffectsPhysics() const override { return true; }
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
[[nodiscard]] virtual PhysicsShapeDesc BuildPhysicsShape() const = 0;
virtual void DrawDebugLines(const glm::vec4& color) const = 0;
@@ -30,18 +30,6 @@ public:
void SetCenterOffset(const glm::vec3& offset) { m_CenterOffset = offset; NotifyPhysicsChanged(); }
protected:
[[nodiscard]] nlohmann::json SerializeCollider() const {
return {
{"centerOffset", m_CenterOffset},
{"isTrigger", m_IsTrigger}
};
}
void DeserializeCollider(const nlohmann::json& data) {
m_CenterOffset = data.value("centerOffset", m_CenterOffset);
m_IsTrigger = data.value("isTrigger", m_IsTrigger);
}
bool m_IsTrigger{false};
glm::vec3 m_CenterOffset{0.0f};
};
@@ -17,9 +17,7 @@ public:
return "ConvexHullCollider";
}
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
void DrawDebugLines(const glm::vec4& color) const override;
@@ -17,24 +17,7 @@ public:
return "CylinderCollider";
}
void ImGuiInspector() override;
nlohmann::json Serialize() const override {
auto data = SerializeCollider();
data["radius"] = m_Radius;
data["height"] = m_Height;
return data;
}
void Deserialize(const nlohmann::json& data) override {
DeserializeCollider(data);
if (data.contains("radius")) {
SetRadius(data.at("radius").get<float>());
}
if (data.contains("height")) {
SetHeight(data.at("height").get<float>());
}
}
void Reflect(FieldVisitor& v) override;
void DrawDebugLines(const glm::vec4& color) const override;
@@ -14,14 +14,6 @@ public:
return "MeshCollider";
}
nlohmann::json Serialize() const override {
return SerializeCollider();
}
void Deserialize(const nlohmann::json& data) override {
DeserializeCollider(data);
}
void ImGuiInspector() override;
void DrawDebugLines(const glm::vec4& color) const override;
@@ -21,8 +21,7 @@ public:
void Start() override;
void Destroy() override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void Reflect(FieldVisitor& v) override;
void ImGuiInspector() override;
std::string GetTypeName() const override
@@ -12,9 +12,7 @@ public:
void Update(float) override {}
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
std::string GetTypeName() const override
{
return "SphereCollider";
+1 -3
View File
@@ -20,9 +20,7 @@ public:
std::string GetTypeName() const override { return "Rotator"; }
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
void SetDistance(float distance);
void SetSpeed(float speed) { m_Speed = speed; }
+1 -3
View File
@@ -18,9 +18,7 @@ public:
std::string GetTypeName() const override { return "Spinner"; }
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
void SetAxis(const glm::vec3& axis) {
m_Axis = glm::dot(axis, axis) > 0.000001f
@@ -23,9 +23,7 @@ public:
void Render(const RenderContext& ctx) override;
void Draw(const glm::vec2& screenSize);
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
[[nodiscard]] float GetScale(const glm::vec2& screenSize) const;
@@ -22,8 +22,7 @@ public:
std::string GetTypeName() const override { return "RectTransform"; }
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void Reflect(FieldVisitor& v) override;
void ImGuiInspector() override;
[[nodiscard]] UIRect ComputeRect(const UIRect& parentRect, float scale) const;
@@ -20,9 +20,7 @@ public:
void DrawUI(const UIRect& rect, float scale) override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
void SetShape(UIImageShape shape) { m_Shape = shape; }
void SetOutline(float thickness) { m_Outline = thickness; }
@@ -21,9 +21,7 @@ public:
void DrawUI(const UIRect& rect, float scale) override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void Reflect(FieldVisitor& v) override;
void SetText(std::string text) { m_Text = std::move(text); }
void SetFont(std::string assetPath) { m_FontPath = std::move(assetPath); }
@@ -29,10 +29,14 @@ struct FieldOptions {
std::optional<double> max;
const char* format = nullptr;
bool color = false;
bool angle = false; // radians, shown in degrees
bool multiline = false;
bool acceptAssets = false; // strings take a virtual path dropped from the asset browser
bool readOnly = false;
bool hidden = false;
bool transient = false;
bool hidden = false; // saved, not shown
bool transient = false; // shown, not saved
const char* label = nullptr;
const char* tooltip = nullptr;
std::span<const char* const> enumNames;
std::function<bool(const GameObject&)> filter;
};
@@ -66,6 +70,7 @@ namespace reflect_detail {
}
bool DrawScalar(const char* label, Scalar type, void* value, int components, const void* min, const void* max, const FieldOptions& options);
bool DrawAngle(const char* label, float& radians, const FieldOptions& options);
bool DrawEnum(const char* label, std::int64_t& value, const FieldOptions& options);
void BeginList(const char* label);
@@ -77,6 +82,9 @@ namespace reflect_detail {
template <class T>
concept Numeric = std::is_arithmetic_v<T> && !std::same_as<T, bool>;
template <class T>
concept FloatVec = std::same_as<T, glm::vec2> || std::same_as<T, glm::vec3> || std::same_as<T, glm::vec4>;
}
bool DrawField(const char* label, bool& value, const FieldOptions& options, const Scene* scene);
@@ -90,6 +98,11 @@ bool DrawField(const char* label, std::vector<ObjectRef>& value, const FieldOpti
template <reflect_detail::Numeric T>
bool DrawField(const char* label, T& value, const FieldOptions& options, const Scene*)
{
if constexpr (std::same_as<T, float>) {
if (options.angle) {
return reflect_detail::DrawAngle(label, value, options);
}
}
const T min = options.min ? static_cast<T>(*options.min) : T{};
const T max = options.max ? static_cast<T>(*options.max) : T{};
return reflect_detail::DrawScalar(label, reflect_detail::ScalarOf<T>(), &value, 1,
@@ -181,6 +194,9 @@ struct FieldCodec {
if constexpr (reflect_detail::Numeric<T>) {
if (options.min && typed < static_cast<T>(*options.min)) typed = static_cast<T>(*options.min);
if (options.max && typed > static_cast<T>(*options.max)) typed = static_cast<T>(*options.max);
} else if constexpr (reflect_detail::FloatVec<T>) {
if (options.min) typed = glm::max(typed, T{static_cast<float>(*options.min)});
if (options.max) typed = glm::min(typed, T{static_cast<float>(*options.max)});
}
}
@@ -203,14 +219,26 @@ class FieldVisitor {
public:
virtual ~FieldVisitor() = default;
using Commit = std::function<void(void* value)>;
template <Reflectable T>
void Field(std::string_view key, T& value, const FieldOptions& options = {})
{
Visit(key, &value, options, FieldCodec<T>::Ops);
Visit(key, &value, options, FieldCodec<T>::Ops, {});
}
// Loads and edits go through the setter, so it can validate and react
template <Reflectable T, std::invocable<const T&> Setter>
void Property(std::string_view key, const T& value, Setter&& setter, const FieldOptions& options = {})
{
T copy = value;
Visit(key, &copy, options, FieldCodec<T>::Ops, [&setter](void* changed) {
setter(*static_cast<const T*>(changed));
});
}
protected:
virtual void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) = 0;
virtual void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit) = 0;
};
class JsonWriter final : public FieldVisitor {
@@ -218,7 +246,7 @@ public:
[[nodiscard]] nlohmann::json& Result() { return m_Result; }
protected:
void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override;
void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit) override;
private:
nlohmann::json m_Result = nlohmann::json::object();
@@ -229,7 +257,7 @@ public:
explicit JsonReader(const nlohmann::json& data) : m_Data(data) {}
protected:
void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override;
void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit) override;
private:
const nlohmann::json& m_Data;
@@ -240,7 +268,7 @@ public:
ReferenceResolver(const Component& owner, const ObjectRef::ObjectMap& objects) : m_Owner(owner), m_Objects(objects) {}
protected:
void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override;
void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit) override;
private:
const Component& m_Owner;
@@ -254,7 +282,7 @@ public:
[[nodiscard]] bool Changed() const { return m_Changed; }
protected:
void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops) override;
void Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit) override;
private:
const Scene* m_Scene;
+20 -47
View File
@@ -4,6 +4,7 @@
#include <destrum/Assets/AssetTypes.h>
#include <destrum/Audio/AudioSystem.h>
#include <destrum/ObjectModel/Transform.h>
#include <destrum/Serialization/Reflect.h>
#include <destrum/Util/ImGuiUtils.h>
#include <AL/al.h>
@@ -158,18 +159,25 @@ void AudioSource::SetDistances(float minDistance, float maxDistance)
ApplyProperties();
}
void AudioSource::Reflect(FieldVisitor& v)
{
v.Property("volume", m_Volume, [this](float volume) { SetVolume(volume); }, {.speed = 0.01f, .min = 0.0, .max = 2.0});
v.Property("pitch", m_Pitch, [this](float pitch) { SetPitch(pitch); }, {.speed = 0.01f, .min = 0.1, .max = 3.0});
v.Property("loop", m_Loop, [this](bool loop) { SetLoop(loop); });
v.Field("playOnStart", m_PlayOnStart);
v.Property("spatial", m_Spatial, [this](bool spatial) { SetSpatial(spatial); });
v.Property("minDistance", m_MinDistance, [this](float minDistance) { SetDistances(minDistance, m_MaxDistance); },
{.speed = 0.05f, .min = 0.01, .format = "%.2f m", .hidden = !m_Spatial, .tooltip = "Full volume up to this distance"});
v.Property("maxDistance", m_MaxDistance, [this](float maxDistance) { SetDistances(m_MinDistance, maxDistance); },
{.speed = 0.1f, .min = 0.01, .format = "%.2f m", .hidden = !m_Spatial,
.tooltip = "The sound stops getting quieter past this distance"});
}
nlohmann::json AudioSource::Serialize() const
{
return {
{"clip", m_ClipPath.empty() ? nlohmann::json(nullptr) : AssetDatabase::GetInstance().WriteReference(m_ClipPath)},
{"volume", m_Volume},
{"pitch", m_Pitch},
{"loop", m_Loop},
{"playOnStart", m_PlayOnStart},
{"spatial", m_Spatial},
{"minDistance", m_MinDistance},
{"maxDistance", m_MaxDistance},
};
nlohmann::json data = Component::Serialize();
data["clip"] = m_ClipPath.empty() ? nlohmann::json(nullptr) : AssetDatabase::GetInstance().WriteReference(m_ClipPath);
return data;
}
void AudioSource::Deserialize(const nlohmann::json& data)
@@ -183,13 +191,7 @@ void AudioSource::Deserialize(const nlohmann::json& data)
SetClip({});
}
}
m_Loop = data.value("loop", m_Loop);
m_PlayOnStart = data.value("playOnStart", m_PlayOnStart);
m_Spatial = data.value("spatial", m_Spatial);
SetVolume(data.value("volume", m_Volume));
SetPitch(data.value("pitch", m_Pitch));
SetDistances(data.value("minDistance", m_MinDistance), data.value("maxDistance", m_MaxDistance));
Component::Deserialize(data);
}
void AudioSource::ImGuiInspector()
@@ -218,36 +220,7 @@ void AudioSource::ImGuiInspector()
m_Clip->GetSampleRate(), m_Clip->GetDuration());
}
float volume = m_Volume;
if (ImGui::SliderFloat("Volume", &volume, 0.0f, 2.0f)) {
SetVolume(volume);
}
float pitch = m_Pitch;
if (ImGui::SliderFloat("Pitch", &pitch, 0.1f, 3.0f)) {
SetPitch(pitch);
}
bool loop = m_Loop;
if (ImGui::Checkbox("Loop", &loop)) {
SetLoop(loop);
}
ImGui::Checkbox("Play On Start", &m_PlayOnStart);
bool spatial = m_Spatial;
if (ImGui::Checkbox("Spatial", &spatial)) {
SetSpatial(spatial);
}
if (m_Spatial) {
float minDistance = m_MinDistance;
float maxDistance = m_MaxDistance;
bool changed = ImGui::DragFloat("Min Distance", &minDistance, 0.05f, 0.01f, 1000.0f, "%.2f m");
ImGui::SetItemTooltip("Full volume up to this distance");
changed |= ImGui::DragFloat("Max Distance", &maxDistance, 0.1f, 0.01f, 10000.0f, "%.2f m");
ImGui::SetItemTooltip("The sound stops getting quieter past this distance");
if (changed) {
SetDistances(minDistance, maxDistance);
}
}
Component::ImGuiInspector();
ImGui::BeginDisabled(m_ClipPath.empty());
if (ImGui::Button(IsPlaying() ? "Restart" : "Play")) {
+6 -26
View File
@@ -3,6 +3,7 @@
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/ObjectModel/Transform.h>
#include <destrum/Scene/Scene.h>
#include <destrum/Serialization/Reflect.h>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
@@ -54,19 +55,10 @@ bool CameraComponent::IsCurrent() const
return scene != nullptr && scene->GetCurrentCamera() == this;
}
nlohmann::json CameraComponent::Serialize() const {
return {
{"fieldOfView", m_FieldOfView},
{"zNear", m_ZNear},
{"zFar", m_ZFar}
};
}
void CameraComponent::Deserialize(const nlohmann::json& data) {
if (data.contains("fieldOfView")) {
SetFieldOfView(data.at("fieldOfView").get<float>());
}
SetClipPlanes(data.value("zNear", m_ZNear), data.value("zFar", m_ZFar));
void CameraComponent::Reflect(FieldVisitor& v) {
v.Property("fieldOfView", m_FieldOfView, [this](float fov) { SetFieldOfView(fov); }, {.speed = 0.5f, .min = 1.0, .max = 179.0});
v.Property("zNear", m_ZNear, [this](float zNear) { SetClipPlanes(zNear, m_ZFar); }, {.speed = 0.01f, .min = 0.0001, .label = "Near"});
v.Property("zFar", m_ZFar, [this](float zFar) { SetClipPlanes(m_ZNear, zFar); }, {.speed = 1.0f, .min = 0.1, .label = "Far"});
}
void CameraComponent::ImGuiInspector() {
@@ -74,17 +66,5 @@ void CameraComponent::ImGuiInspector() {
if (ImGui::Checkbox("Current Camera", &current) && current) {
MakeCurrent();
}
float fieldOfView = m_FieldOfView;
if (ImGui::DragFloat("Field Of View", &fieldOfView, 0.5f, 1.0f, 179.0f)) {
SetFieldOfView(fieldOfView);
}
float zNear = m_ZNear;
float zFar = m_ZFar;
const bool nearChanged = ImGui::DragFloat("Near", &zNear, 0.01f, 0.0001f, 100.0f);
const bool farChanged = ImGui::DragFloat("Far", &zFar, 1.0f, 0.1f, 100000.0f);
if (nearChanged || farChanged) {
SetClipPlanes(zNear, zFar);
}
Component::ImGuiInspector();
}
+16 -85
View File
@@ -2,7 +2,7 @@
#include <destrum/Graphics/RenderContext.h>
#include <destrum/ObjectModel/Transform.h>
#include <destrum/Serialization/GlmJson.h>
#include <destrum/Serialization/Reflect.h>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/quaternion.hpp>
@@ -11,8 +11,6 @@
#include <algorithm>
#include <array>
#include <imgui.h>
LightComponent::LightComponent(GameObject& parent, LightType type)
: Component(parent, "LightComponent")
, m_Type(type)
@@ -52,86 +50,19 @@ void LightComponent::Render(const RenderContext& ctx)
});
}
nlohmann::json LightComponent::Serialize() const {
return {
{"type", static_cast<std::uint32_t>(m_Type)},
{"color", m_Color},
{"intensity", m_Intensity},
{"range", m_Range},
{"innerConeAngle", m_InnerConeAngle},
{"outerConeAngle", m_OuterConeAngle},
{"castShadows", m_CastShadows}
};
}
void LightComponent::Deserialize(const nlohmann::json& data) {
if (data.contains("type")) {
const auto type = data.at("type").get<std::uint32_t>();
if (type <= static_cast<std::uint32_t>(LightType::Spot)) {
m_Type = static_cast<LightType>(type);
}
}
m_Color = data.value("color", m_Color);
if (data.contains("intensity")) {
SetIntensity(data.at("intensity").get<float>());
}
if (data.contains("range")) {
SetRange(data.at("range").get<float>());
}
float innerConeAngle = m_InnerConeAngle;
float outerConeAngle = m_OuterConeAngle;
if (data.contains("innerConeAngle")) {
innerConeAngle = data.at("innerConeAngle").get<float>();
}
if (data.contains("outerConeAngle")) {
outerConeAngle = data.at("outerConeAngle").get<float>();
}
SetConeAngles(innerConeAngle, outerConeAngle);
if (data.contains("castShadows")) {
m_CastShadows = data.at("castShadows").get<bool>();
}
}
void LightComponent::ImGuiInspector() {
constexpr std::array typeNames{"Directional", "Point", "Spot"};
int type = static_cast<int>(m_Type);
if (ImGui::Combo("Type", &type, typeNames.data(), static_cast<int>(typeNames.size()))) {
SetType(static_cast<LightType>(type));
}
ImGui::ColorEdit3("Color", &m_Color.x);
float intensity = m_Intensity;
if (ImGui::DragFloat("Intensity", &intensity, 0.05f, 0.0f)) {
SetIntensity(intensity);
}
if (m_Type == LightType::Directional) {
ImGui::Checkbox("Cast Shadows", &m_CastShadows);
ImGui::SameLine();
ImGui::TextDisabled("(first directional light only)");
return;
}
if (m_Type == LightType::Spot) {
ImGui::Checkbox("Cast Shadows", &m_CastShadows);
}
float range = m_Range;
if (ImGui::DragFloat("Range", &range, 0.05f, 0.01f)) {
SetRange(range);
}
if (m_Type == LightType::Spot) {
float innerConeAngle = m_InnerConeAngle;
float outerConeAngle = m_OuterConeAngle;
bool changed = ImGui::SliderAngle("Inner Cone", &innerConeAngle, 0.0f, 90.0f);
changed |= ImGui::SliderAngle("Outer Cone", &outerConeAngle, 0.0f, 90.0f);
if (changed) {
SetConeAngles(innerConeAngle, outerConeAngle);
}
}
void LightComponent::Reflect(FieldVisitor& v) {
static constexpr std::array<const char*, 3> TypeNames{"Directional", "Point", "Spot"};
const bool directional = m_Type == LightType::Directional;
const bool spot = m_Type == LightType::Spot;
v.Field("type", m_Type, {.enumNames = TypeNames});
v.Field("color", m_Color, {.color = true});
v.Property("intensity", m_Intensity, [this](float intensity) { SetIntensity(intensity); }, {.speed = 0.05f, .min = 0.0});
v.Field("castShadows", m_CastShadows, {.hidden = !directional && !spot,
.tooltip = directional ? "Only the first directional light casts shadows" : nullptr});
v.Property("range", m_Range, [this](float range) { SetRange(range); }, {.speed = 0.05f, .min = 0.01, .hidden = directional});
v.Property("innerConeAngle", m_InnerConeAngle, [this](float angle) { SetConeAngles(angle, m_OuterConeAngle); },
{.min = 0.0, .max = glm::half_pi<double>(), .angle = true, .hidden = !spot, .label = "Inner Cone"});
v.Property("outerConeAngle", m_OuterConeAngle, [this](float angle) { SetConeAngles(m_InnerConeAngle, angle); },
{.min = 0.0, .max = glm::half_pi<double>(), .angle = true, .hidden = !spot, .label = "Outer Cone"});
}
+24 -88
View File
@@ -7,10 +7,9 @@
#include "destrum/ObjectModel/GameObject.h"
#include "destrum/ObjectModel/Transform.h"
#include "destrum/Components/MeshRendererComponent.h"
#include "destrum/Serialization/GlmJson.h"
#include "destrum/Serialization/Reflect.h"
#include "destrum/Util/GameState.h"
#include "destrum/Util/DeltaTime.h"
#include "destrum/Util/ImGuiUtils.h"
#include <imgui.h>
@@ -126,40 +125,33 @@ void OrbitAndSpin::Start() {
}
nlohmann::json OrbitAndSpin::Serialize() const {
return {
{"radius", m_Radius},
{"center", m_Center},
{"orbitAxis", m_OrbitAxis},
{"orbitSpeed", m_OrbitSpeed},
{"orbitAngle", m_OrbitAngle},
{"orbitPhase", m_OrbitPhase},
{"growPhase", m_GrowPhase},
{"growSpeed", m_GrowSpeed},
{"growMin", m_GrowMin},
{"growMax", m_GrowMax},
{"spinAxis", m_SpinAxis},
{"spinSpeed", m_SpinSpeed},
{"baseScale", m_BaseScale}
};
void OrbitAndSpin::Reflect(FieldVisitor& v) {
v.Field("radius", m_Radius, {.speed = 0.05f, .min = 0.0});
v.Field("center", m_Center);
v.Field("orbitAxis", m_OrbitAxis);
v.Field("orbitSpeed", m_OrbitSpeed, {.speed = 0.05f});
v.Field("orbitPhase", m_OrbitPhase, {.speed = 0.05f});
v.Field("orbitAngle", m_OrbitAngle, {.format = "%.2f rad", .readOnly = true});
v.Field("growSpeed", m_GrowSpeed, {.speed = 0.05f, .min = 0.0});
v.Field("growMin", m_GrowMin, {.speed = 0.01f, .min = 0.0});
v.Field("growMax", m_GrowMax, {.speed = 0.01f, .min = 0.0});
v.Field("baseScale", m_BaseScale, {.speed = 0.05f});
v.Field("spinAxis", m_SpinAxis);
v.Field("spinSpeed", m_SpinSpeed, {.speed = 0.05f});
v.Field("growPhase", m_GrowPhase, {.format = "%.2f rad", .readOnly = true});
}
void OrbitAndSpin::Deserialize(const nlohmann::json& data) {
m_Radius = data.value("radius", m_Radius);
m_Center = data.value("center", m_Center);
m_OrbitAxis = data.value("orbitAxis", m_OrbitAxis);
m_OrbitSpeed = data.value("orbitSpeed", m_OrbitSpeed);
m_OrbitAngle = data.value("orbitAngle", m_OrbitAngle);
m_OrbitPhase = data.value("orbitPhase", m_OrbitPhase);
m_GrowPhase = data.value("growPhase", m_GrowPhase);
m_GrowSpeed = data.value("growSpeed", m_GrowSpeed);
m_GrowMin = data.value("growMin", m_GrowMin);
m_GrowMax = data.value("growMax", m_GrowMax);
m_SpinAxis = data.value("spinAxis", m_SpinAxis);
m_BaseScale = data.value("baseScale", m_BaseScale);
Component::Deserialize(data);
m_BaseScaleLoaded = data.contains("baseScale");
m_SpinSpeed = data.value("spinSpeed", m_SpinSpeed);
Sanitize();
}
void OrbitAndSpin::OnFieldsEdited() {
Sanitize();
}
void OrbitAndSpin::Sanitize() {
if (m_GrowMin > m_GrowMax) {
std::swap(m_GrowMin, m_GrowMax);
}
@@ -172,62 +164,6 @@ void OrbitAndSpin::Deserialize(const nlohmann::json& data) {
}
void OrbitAndSpin::ImGuiInspector() {
float radius = m_Radius;
if (ImGui::DragFloat("Radius", &radius, 0.05f, 0.0f)) {
SetRadius(radius);
}
glm::vec3 center = m_Center;
if (ImGuiUtils::DrawVec3Control("Center", center)) {
SetCenter(center);
}
glm::vec3 orbitAxis = m_OrbitAxis;
if (ImGuiUtils::DrawVec3Control("Orbit axis", orbitAxis)) {
if (glm::dot(orbitAxis, orbitAxis) >= 1e-8f) {
m_OrbitAxis = glm::normalize(orbitAxis);
BuildOrbitBasis();
}
}
float orbitSpeed = m_OrbitSpeed;
if (ImGui::DragFloat("Orbit speed", &orbitSpeed, 0.05f)) {
m_OrbitSpeed = orbitSpeed;
}
ImGui::DragFloat("Orbit phase", &m_OrbitPhase, 0.05f);
ImGui::Text("Orbit angle: %.2f rad", m_OrbitAngle);
float growSpeed = m_GrowSpeed;
if (ImGui::DragFloat("Grow speed", &growSpeed, 0.05f, 0.0f)) {
m_GrowSpeed = growSpeed;
}
float growMin = m_GrowMin;
if (ImGui::DragFloat("Grow min", &growMin, 0.01f, 0.0f)) {
m_GrowMin = std::min(growMin, m_GrowMax);
}
float growMax = m_GrowMax;
if (ImGui::DragFloat("Grow max", &growMax, 0.01f, 0.0f)) {
m_GrowMax = std::max(growMax, m_GrowMin);
}
glm::vec3 baseScale = m_BaseScale;
if (ImGuiUtils::DrawVec3Control("Base scale", baseScale, 0.05f)) {
m_BaseScale = baseScale;
}
glm::vec3 spinAxis = m_SpinAxis;
if (ImGuiUtils::DrawVec3Control("Spin axis", spinAxis)) {
if (glm::dot(spinAxis, spinAxis) >= 1e-8f) {
m_SpinAxis = glm::normalize(spinAxis);
}
}
float spinSpeed = m_SpinSpeed;
if (ImGui::DragFloat("Spin speed", &spinSpeed, 0.05f)) {
m_SpinSpeed = spinSpeed;
}
ImGui::Text("Grow phase: %.2f rad", m_GrowPhase);
Component::ImGuiInspector();
ImGui::Text("Material ID: %u", m_MaterialID);
}
+4 -23
View File
@@ -1,32 +1,13 @@
#include <destrum/Components/Physics/BoxCollider.h>
#include <destrum/Graphics/DebugDraw.h>
#include <destrum/Util/ImGuiUtils.h>
#include <imgui.h>
#include <destrum/Serialization/Reflect.h>
void BoxCollider::DrawDebugLines(const glm::vec4& color) const {
DebugDraw::Box(GetWorldCenter(), GetWorldRotation(), m_HalfExtents, color);
}
nlohmann::json BoxCollider::Serialize() const {
auto data = SerializeCollider();
data["halfExtents"] = m_HalfExtents;
return data;
}
void BoxCollider::Deserialize(const nlohmann::json& data) {
DeserializeCollider(data);
if (data.contains("halfExtents")) {
SetHalfExtents(data.at("halfExtents").get<glm::vec3>());
}
}
void BoxCollider::ImGuiInspector() {
Collider::ImGuiInspector();
glm::vec3 halfExtents = m_HalfExtents;
if (ImGuiUtils::DrawVec3Control("Half extents", halfExtents)) {
SetHalfExtents(halfExtents);
}
void BoxCollider::Reflect(FieldVisitor& v) {
Collider::Reflect(v);
v.Property("halfExtents", m_HalfExtents, [this](const glm::vec3& halfExtents) { SetHalfExtents(halfExtents); }, {.min = 0.0001});
}
@@ -2,22 +2,14 @@
#include <destrum/Graphics/DebugDraw.h>
#include <imgui.h>
#include <destrum/Serialization/Reflect.h>
void CapsuleCollider::DrawDebugLines(const glm::vec4& color) const {
DebugDraw::Capsule(GetWorldCenter(), GetWorldRotation(), m_Radius, m_Height, color);
}
void CapsuleCollider::ImGuiInspector() {
Collider::ImGuiInspector();
float radius = m_Radius;
if (ImGui::DragFloat("Radius", &radius, 0.05f, 0.0001f)) {
SetRadius(radius);
}
float height = m_Height;
if (ImGui::DragFloat("Height", &height, 0.05f, 0.0001f)) {
SetHeight(height);
}
void CapsuleCollider::Reflect(FieldVisitor& v) {
Collider::Reflect(v);
v.Property("radius", m_Radius, [this](float radius) { SetRadius(radius); }, {.speed = 0.05f, .min = 0.0001});
v.Property("height", m_Height, [this](float height) { SetHeight(height); }, {.speed = 0.05f, .min = 0.0001});
}
+4 -13
View File
@@ -1,9 +1,7 @@
#include <destrum/Components/Physics/Collider.h>
#include <imgui.h>
#include <destrum/ObjectModel/Transform.h>
#include <destrum/Util/ImGuiUtils.h>
#include <destrum/Serialization/Reflect.h>
glm::vec3 Collider::GetWorldCenter() const {
Transform& transform = GetTransform();
@@ -14,14 +12,7 @@ glm::quat Collider::GetWorldRotation() const {
return GetTransform().GetWorldRotation();
}
void Collider::ImGuiInspector() {
bool isTrigger = m_IsTrigger;
if (ImGui::Checkbox("Is trigger", &isTrigger)) {
SetTrigger(isTrigger);
}
glm::vec3 centerOffset = m_CenterOffset;
if (ImGuiUtils::DrawVec3Control("Center offset", centerOffset)) {
SetCenterOffset(centerOffset);
}
void Collider::Reflect(FieldVisitor& v) {
v.Property("isTrigger", m_IsTrigger, [this](bool trigger) { SetTrigger(trigger); });
v.Property("centerOffset", m_CenterOffset, [this](const glm::vec3& offset) { SetCenterOffset(offset); });
}
@@ -1,29 +1,13 @@
#include <destrum/Components/Physics/ConvexHullCollider.h>
#include <imgui.h>
#include <destrum/Serialization/Reflect.h>
nlohmann::json ConvexHullCollider::Serialize() const {
auto data = SerializeCollider();
data["maxVertices"] = m_MaxVertices;
return data;
}
void ConvexHullCollider::Deserialize(const nlohmann::json& data) {
DeserializeCollider(data);
if (data.contains("maxVertices")) {
SetMaxVertices(data.at("maxVertices").get<int>());
}
void ConvexHullCollider::Reflect(FieldVisitor& v) {
Collider::Reflect(v);
v.Property("maxVertices", m_MaxVertices, [this](int maxVertices) { SetMaxVertices(maxVertices); },
{.speed = 1.0f, .min = MinVertices, .max = MaxVertices});
}
void ConvexHullCollider::DrawDebugLines(const glm::vec4& color) const {
DrawConvexHull(m_MaxVertices, color);
}
void ConvexHullCollider::ImGuiInspector() {
MeshShapeCollider::ImGuiInspector();
int maxVertices = m_MaxVertices;
if (ImGui::SliderInt("Max vertices", &maxVertices, MinVertices, MaxVertices)) {
SetMaxVertices(maxVertices);
}
}
@@ -2,22 +2,14 @@
#include <destrum/Graphics/DebugDraw.h>
#include <imgui.h>
#include <destrum/Serialization/Reflect.h>
void CylinderCollider::DrawDebugLines(const glm::vec4& color) const {
DebugDraw::Cylinder(GetWorldCenter(), GetWorldRotation(), m_Radius, m_Height, color);
}
void CylinderCollider::ImGuiInspector() {
Collider::ImGuiInspector();
float radius = m_Radius;
if (ImGui::DragFloat("Radius", &radius, 0.05f, 0.0001f)) {
SetRadius(radius);
}
float height = m_Height;
if (ImGui::DragFloat("Height", &height, 0.05f, 0.0001f)) {
SetHeight(height);
}
void CylinderCollider::Reflect(FieldVisitor& v) {
Collider::Reflect(v);
v.Property("radius", m_Radius, [this](float radius) { SetRadius(radius); }, {.speed = 0.05f, .min = 0.0001});
v.Property("height", m_Height, [this](float height) { SetHeight(height); }, {.speed = 0.05f, .min = 0.0001});
}
+14 -99
View File
@@ -3,40 +3,12 @@
#include <destrum/Physics/PhysicsWorld.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Scene/Scene.h>
#include <destrum/Serialization/Reflect.h>
#include <array>
#include <imgui.h>
namespace {
[[nodiscard]] const char* RigidbodyTypeName(RigidbodyType type) {
switch (type) {
case RigidbodyType::Static:
return "Static";
case RigidbodyType::Kinematic:
return "Kinematic";
case RigidbodyType::Dynamic:
default:
return "Dynamic";
}
}
[[nodiscard]] RigidbodyType ParseRigidbodyType(const nlohmann::json& value) {
if (value.is_string()) {
const std::string type = value.get<std::string>();
if (type == "Static") return RigidbodyType::Static;
if (type == "Kinematic") return RigidbodyType::Kinematic;
return RigidbodyType::Dynamic;
}
const int type = value.get<int>();
switch (type) {
case 0: return RigidbodyType::Static;
case 2: return RigidbodyType::Kinematic;
case 1:
default: return RigidbodyType::Dynamic;
}
}
}
Rigidbody::~Rigidbody() {
if (m_World != nullptr) {
m_World->UnregisterRigidbody(*this);
@@ -110,78 +82,21 @@ glm::vec3 Rigidbody::GetLinearVelocity() const {
return m_World->GetLinearVelocity(m_Body);
}
nlohmann::json Rigidbody::Serialize() const {
return {
{"type", RigidbodyTypeName(m_Type)},
{"mass", m_Mass},
{"friction", m_Friction},
{"restitution", m_Restitution},
{"useGravity", m_UseGravity},
{"allowSleep", m_AllowSleep},
{"freezeRotation", m_FreezeRotation}
};
}
void Rigidbody::Reflect(FieldVisitor& v) {
static constexpr std::array<const char*, 3> TypeNames{"Static", "Dynamic", "Kinematic"};
void Rigidbody::Deserialize(const nlohmann::json& data) {
if (data.contains("type")) {
m_Type = ParseRigidbodyType(data.at("type"));
}
if (data.contains("mass")) {
SetMass(data.at("mass").get<float>());
}
if (data.contains("friction")) {
SetFriction(data.at("friction").get<float>());
}
if (data.contains("restitution")) {
SetRestitution(data.at("restitution").get<float>());
}
if (data.contains("useGravity")) {
m_UseGravity = data.at("useGravity").get<bool>();
}
if (data.contains("allowSleep")) {
m_AllowSleep = data.at("allowSleep").get<bool>();
}
if (data.contains("freezeRotation")) {
m_FreezeRotation = data.at("freezeRotation").get<bool>();
}
v.Property("type", m_Type, [this](RigidbodyType type) { SetType(type); }, {.label = "Body Type", .enumNames = TypeNames});
v.Property("mass", m_Mass, [this](float mass) { SetMass(mass); }, {.min = 0.0001});
v.Property("friction", m_Friction, [this](float friction) { SetFriction(friction); }, {.speed = 0.01f, .min = 0.0});
v.Property("restitution", m_Restitution, [this](float restitution) { SetRestitution(restitution); },
{.speed = 0.01f, .min = 0.0, .max = 1.0});
v.Property("useGravity", m_UseGravity, [this](bool useGravity) { SetUseGravity(useGravity); });
v.Property("allowSleep", m_AllowSleep, [this](bool allowSleep) { SetAllowSleep(allowSleep); });
v.Property("freezeRotation", m_FreezeRotation, [this](bool freezeRotation) { SetFreezeRotation(freezeRotation); });
}
void Rigidbody::ImGuiInspector() {
static const char* typeNames[] = {"Static", "Dynamic", "Kinematic"};
int type = static_cast<int>(m_Type);
if (ImGui::Combo("Body type", &type, typeNames, IM_ARRAYSIZE(typeNames))) {
SetType(static_cast<RigidbodyType>(type));
}
float mass = m_Mass;
if (ImGui::DragFloat("Mass", &mass, 0.1f, 0.0001f)) {
SetMass(mass);
}
float friction = m_Friction;
if (ImGui::DragFloat("Friction", &friction, 0.01f, 0.0f)) {
SetFriction(friction);
}
float restitution = m_Restitution;
if (ImGui::SliderFloat("Restitution", &restitution, 0.0f, 1.0f)) {
SetRestitution(restitution);
}
bool useGravity = m_UseGravity;
if (ImGui::Checkbox("Use gravity", &useGravity)) {
SetUseGravity(useGravity);
}
bool allowSleep = m_AllowSleep;
if (ImGui::Checkbox("Allow sleep", &allowSleep)) {
SetAllowSleep(allowSleep);
}
bool freezeRotation = m_FreezeRotation;
if (ImGui::Checkbox("Freeze rotation", &freezeRotation)) {
SetFreezeRotation(freezeRotation);
}
Component::ImGuiInspector();
if (HasPhysicsBody()) {
const glm::vec3 velocity = GetLinearVelocity();
@@ -2,30 +2,13 @@
#include <destrum/Graphics/DebugDraw.h>
#include <imgui.h>
#include <destrum/Serialization/Reflect.h>
void SphereCollider::DrawDebugLines(const glm::vec4& color) const {
DebugDraw::Sphere(GetWorldCenter(), m_Radius, color);
}
nlohmann::json SphereCollider::Serialize() const {
auto data = SerializeCollider();
data["radius"] = m_Radius;
return data;
}
void SphereCollider::Deserialize(const nlohmann::json& data) {
DeserializeCollider(data);
if (data.contains("radius")) {
SetRadius(data.at("radius").get<float>());
}
}
void SphereCollider::ImGuiInspector() {
Collider::ImGuiInspector();
float radius = m_Radius;
if (ImGui::DragFloat("Radius", &radius, 0.05f, 0.0001f)) {
SetRadius(radius);
}
void SphereCollider::Reflect(FieldVisitor& v) {
Collider::Reflect(v);
v.Property("radius", m_Radius, [this](float radius) { SetRadius(radius); }, {.speed = 0.05f, .min = 0.0001});
}
+10 -48
View File
@@ -5,11 +5,8 @@
#include <cmath>
#include <glm/gtc/quaternion.hpp> // glm::quat, glm::angleAxis
#include <glm/gtx/quaternion.hpp> // operator*(quat, vec3)
#include <destrum/Serialization/GlmJson.h>
#include <destrum/Serialization/Reflect.h>
#include <destrum/Util/DeltaTime.h>
#include <destrum/Util/ImGuiUtils.h>
#include <imgui.h>
glm::vec3 Rotator::MakePerpendicularUnitVector(const glm::vec3& axis)
{
@@ -98,48 +95,13 @@ void Rotator::Update(float dt)
GetTransform().SetLocalPosition(m_Pivot + rotatedOffset);
}
nlohmann::json Rotator::Serialize() const {
return {
{"distance", m_Distance},
{"speed", m_Speed},
{"currentAngle", m_CurrentAngle},
{"pivot", m_Pivot},
{"axis", m_Axis},
{"initialOffset", m_InitialOffset}
};
}
void Rotator::Deserialize(const nlohmann::json& data) {
m_Distance = data.value("distance", m_Distance);
m_Speed = data.value("speed", m_Speed);
m_CurrentAngle = data.value("currentAngle", m_CurrentAngle);
m_Pivot = data.value("pivot", m_Pivot);
if (data.contains("axis")) {
SetAxis(data.at("axis").get<glm::vec3>());
}
m_InitialOffset = data.value("initialOffset", m_InitialOffset);
}
void Rotator::ImGuiInspector() {
float distance = m_Distance;
if (ImGui::DragFloat("Distance", &distance, 0.05f, 0.0f)) {
SetDistance(distance);
}
float speed = m_Speed;
if (ImGui::DragFloat("Speed", &speed, 0.05f)) {
SetSpeed(speed);
}
glm::vec3 pivot = m_Pivot;
if (ImGuiUtils::DrawVec3Control("Pivot", pivot)) {
SetPivotPosition(pivot);
}
glm::vec3 axis = m_Axis;
if (ImGuiUtils::DrawVec3Control("Axis", axis)) {
SetAxis(axis);
}
ImGui::Text("Current angle: %.2f rad", m_CurrentAngle);
// SetPivotPosition derives the offset and distance from the current position, so the pivot is
// loaded first and the saved distance and offset overwrite that
void Rotator::Reflect(FieldVisitor& v) {
v.Property("pivot", m_Pivot, [this](const glm::vec3& pivot) { SetPivotPosition(pivot); });
v.Property("distance", m_Distance, [this](float distance) { SetDistance(distance); }, {.speed = 0.05f, .min = 0.0});
v.Field("speed", m_Speed, {.speed = 0.05f});
v.Property("axis", m_Axis, [this](const glm::vec3& axis) { SetAxis(axis); });
v.Field("currentAngle", m_CurrentAngle, {.format = "%.2f rad", .readOnly = true});
v.Field("initialOffset", m_InitialOffset, {.hidden = true});
}
+5 -32
View File
@@ -3,11 +3,8 @@
#include <glm/gtx/quaternion.hpp>
#include "destrum/ObjectModel/Transform.h"
#include "destrum/Serialization/GlmJson.h"
#include "destrum/Serialization/Reflect.h"
#include "destrum/Util/DeltaTime.h"
#include "destrum/Util/ImGuiUtils.h"
#include <imgui.h>
void Spinner::Update(float dt)
{
@@ -17,32 +14,8 @@ void Spinner::Update(float dt)
GetTransform().SetLocalRotation(q);
}
nlohmann::json Spinner::Serialize() const {
return {
{"axis", m_Axis},
{"speed", m_Speed},
{"angle", m_Angle}
};
}
void Spinner::Deserialize(const nlohmann::json& data) {
if (data.contains("axis")) {
SetAxis(data.at("axis").get<glm::vec3>());
}
m_Speed = data.value("speed", m_Speed);
m_Angle = data.value("angle", m_Angle);
}
void Spinner::ImGuiInspector() {
glm::vec3 axis = m_Axis;
if (ImGuiUtils::DrawVec3Control("Axis", axis)) {
SetAxis(axis);
}
float speed = m_Speed;
if (ImGui::DragFloat("Speed", &speed, 0.05f)) {
SetSpeed(speed);
}
ImGui::Text("Current angle: %.2f rad", m_Angle);
void Spinner::Reflect(FieldVisitor& v) {
v.Property("axis", m_Axis, [this](const glm::vec3& axis) { SetAxis(axis); });
v.Field("speed", m_Speed, {.speed = 0.05f});
v.Field("angle", m_Angle, {.format = "%.2f rad", .readOnly = true});
}
+8 -35
View File
@@ -7,8 +7,7 @@
#include <destrum/Graphics/Managers/Renderer2D.h>
#include <destrum/Graphics/Resources/FontCache.h>
#include <destrum/ObjectModel/GameObject.h>
#include <imgui.h>
#include <destrum/Serialization/Reflect.h>
#include "tracy/Tracy.hpp"
@@ -109,39 +108,13 @@ void Canvas::DrawObject(GameObject& object, const UIRect& rect, float scale, boo
}
}
nlohmann::json Canvas::Serialize() const
void Canvas::Reflect(FieldVisitor& v)
{
return {
{"scaleMode", m_ScaleMode == CanvasScaleMode::ConstantPixelSize ? "constantPixelSize" : "scaleWithScreenHeight"},
{"referenceHeight", m_ReferenceHeight},
{"scaleFactor", m_ScaleFactor},
{"drawDebugRects", m_DrawDebugRects},
};
}
static constexpr std::array<const char*, 2> ScaleModeNames{"constantPixelSize", "scaleWithScreenHeight"};
void Canvas::Deserialize(const nlohmann::json& data)
{
m_ScaleMode = data.value("scaleMode", std::string{"scaleWithScreenHeight"}) == "constantPixelSize"
? CanvasScaleMode::ConstantPixelSize
: CanvasScaleMode::ScaleWithScreenHeight;
m_ReferenceHeight = data.value("referenceHeight", m_ReferenceHeight);
m_ScaleFactor = data.value("scaleFactor", m_ScaleFactor);
m_DrawDebugRects = data.value("drawDebugRects", m_DrawDebugRects);
}
void Canvas::ImGuiInspector()
{
constexpr std::array modeNames{"Constant Pixel Size", "Scale With Screen Height"};
int mode = static_cast<int>(m_ScaleMode);
if (ImGui::Combo("Scale Mode", &mode, modeNames.data(), static_cast<int>(modeNames.size()))) {
m_ScaleMode = static_cast<CanvasScaleMode>(mode);
}
if (m_ScaleMode == CanvasScaleMode::ScaleWithScreenHeight) {
ImGui::DragFloat("Reference Height", &m_ReferenceHeight, 1.0f, 1.0f, 8192.0f);
} else {
ImGui::DragFloat("Scale Factor", &m_ScaleFactor, 0.01f, 0.01f, 10.0f);
}
ImGui::Checkbox("Draw Debug Rects", &m_DrawDebugRects);
const bool scaleWithHeight = m_ScaleMode == CanvasScaleMode::ScaleWithScreenHeight;
v.Field("scaleMode", m_ScaleMode, {.enumNames = ScaleModeNames});
v.Field("referenceHeight", m_ReferenceHeight, {.speed = 1.0f, .min = 1.0, .max = 8192.0, .hidden = !scaleWithHeight});
v.Field("scaleFactor", m_ScaleFactor, {.speed = 0.01f, .min = 0.01, .max = 10.0, .hidden = scaleWithHeight});
v.Field("drawDebugRects", m_DrawDebugRects);
}
+8 -23
View File
@@ -1,6 +1,6 @@
#include <destrum/Components/UI/RectTransform.h>
#include <destrum/Serialization/GlmJson.h>
#include <destrum/Serialization/Reflect.h>
#include <array>
@@ -61,24 +61,13 @@ void RectTransform::Stretch()
m_Size = glm::vec2{0.0f};
}
nlohmann::json RectTransform::Serialize() const
void RectTransform::Reflect(FieldVisitor& v)
{
return {
{"anchorMin", m_AnchorMin},
{"anchorMax", m_AnchorMax},
{"pivot", m_Pivot},
{"position", m_Position},
{"size", m_Size},
};
}
void RectTransform::Deserialize(const nlohmann::json& data)
{
m_AnchorMin = data.value("anchorMin", m_AnchorMin);
m_AnchorMax = data.value("anchorMax", m_AnchorMax);
m_Pivot = data.value("pivot", m_Pivot);
m_Position = data.value("position", m_Position);
m_Size = data.value("size", m_Size);
v.Field("anchorMin", m_AnchorMin, {.speed = 0.01f, .min = 0.0, .max = 1.0});
v.Field("anchorMax", m_AnchorMax, {.speed = 0.01f, .min = 0.0, .max = 1.0});
v.Field("pivot", m_Pivot, {.speed = 0.01f, .min = 0.0, .max = 1.0});
v.Field("position", m_Position, {.speed = 1.0f});
v.Field("size", m_Size, {.speed = 1.0f});
}
void RectTransform::ImGuiInspector()
@@ -118,11 +107,7 @@ void RectTransform::ImGuiInspector()
}
}
ImGui::DragFloat2("Anchor Min", &m_AnchorMin.x, 0.01f, 0.0f, 1.0f);
ImGui::DragFloat2("Anchor Max", &m_AnchorMax.x, 0.01f, 0.0f, 1.0f);
ImGui::DragFloat2("Pivot", &m_Pivot.x, 0.01f, 0.0f, 1.0f);
ImGui::DragFloat2("Position", &m_Position.x, 1.0f);
ImGui::DragFloat2("Size", &m_Size.x, 1.0f);
Component::ImGuiInspector();
ImGui::TextDisabled("Screen: %.0f, %.0f %.0f x %.0f",
m_ScreenRect.min.x, m_ScreenRect.min.y, m_ScreenRect.size.x, m_ScreenRect.size.y);
+11 -43
View File
@@ -5,11 +5,9 @@
#include <destrum/FS/AssetFS.h>
#include <destrum/Graphics/RenderResources.h>
#include <destrum/Graphics/Renderer.h>
#include <destrum/Serialization/GlmJson.h>
#include <destrum/Serialization/Reflect.h>
#include <destrum/Util/GameState.h>
#include <destrum/Util/ImGuiUtils.h>
#include <imgui.h>
#include <spdlog/spdlog.h>
UIImage::UIImage(GameObject& parent, const glm::vec4& color)
@@ -64,45 +62,15 @@ ImageID UIImage::ResolveTexture()
return m_Texture;
}
nlohmann::json UIImage::Serialize() const
void UIImage::Reflect(FieldVisitor& v)
{
return {
{"color", m_Color},
{"shape", m_Shape == UIImageShape::Ellipse ? "ellipse" : "rect"},
{"outline", m_Outline},
{"cornerRadius", m_CornerRadius},
{"texture", m_TexturePath},
{"filter", m_Filter == TextureFilter::Nearest ? "nearest" : "linear"},
};
}
void UIImage::Deserialize(const nlohmann::json& data)
{
m_Color = data.value("color", m_Color);
m_Shape = data.value("shape", std::string{"rect"}) == "ellipse" ? UIImageShape::Ellipse : UIImageShape::Rect;
m_Outline = data.value("outline", m_Outline);
m_CornerRadius = data.value("cornerRadius", m_CornerRadius);
m_TexturePath = data.value("texture", m_TexturePath);
m_Filter = data.value("filter", std::string{"linear"}) == "nearest" ? TextureFilter::Nearest : TextureFilter::Linear;
}
void UIImage::ImGuiInspector()
{
constexpr std::array shapeNames{"Rect", "Ellipse"};
int shape = static_cast<int>(m_Shape);
if (ImGui::Combo("Shape", &shape, shapeNames.data(), static_cast<int>(shapeNames.size()))) {
m_Shape = static_cast<UIImageShape>(shape);
}
ImGui::ColorEdit4("Color", &m_Color.x);
ImGui::DragFloat("Outline", &m_Outline, 0.1f, 0.0f, 100.0f);
if (m_Shape == UIImageShape::Rect) {
ImGui::DragFloat("Corner Radius", &m_CornerRadius, 0.1f, 0.0f, 500.0f);
}
ImGuiUtils::InputString("Texture", m_TexturePath);
bool nearest = m_Filter == TextureFilter::Nearest;
if (ImGui::Checkbox("Nearest Filter", &nearest)) {
m_Filter = nearest ? TextureFilter::Nearest : TextureFilter::Linear;
}
static constexpr std::array<const char*, 2> ShapeNames{"rect", "ellipse"};
static constexpr std::array<const char*, 2> FilterNames{"nearest", "linear"};
v.Field("shape", m_Shape, {.enumNames = ShapeNames});
v.Field("color", m_Color, {.color = true});
v.Field("outline", m_Outline, {.min = 0.0, .max = 100.0});
v.Field("cornerRadius", m_CornerRadius, {.min = 0.0, .max = 500.0, .hidden = m_Shape != UIImageShape::Rect});
v.Field("texture", m_TexturePath, {.acceptAssets = true});
v.Field("filter", m_Filter, {.enumNames = FilterNames});
}
+11 -64
View File
@@ -4,10 +4,7 @@
#include <destrum/Graphics/Managers/Renderer2D.h>
#include <destrum/Graphics/Resources/FontCache.h>
#include <destrum/Serialization/GlmJson.h>
#include <destrum/Util/ImGuiUtils.h>
#include <imgui.h>
#include <destrum/Serialization/Reflect.h>
namespace {
[[nodiscard]] float AlignFactor(UITextAlign align)
@@ -19,24 +16,6 @@ namespace {
}
return 0.0f;
}
[[nodiscard]] const char* AlignName(UITextAlign align)
{
switch (align) {
case UITextAlign::Start: return "start";
case UITextAlign::Center: return "center";
case UITextAlign::End: return "end";
}
return "start";
}
[[nodiscard]] UITextAlign ParseAlign(const std::string& name, UITextAlign fallback)
{
if (name == "start") return UITextAlign::Start;
if (name == "center") return UITextAlign::Center;
if (name == "end") return UITextAlign::End;
return fallback;
}
}
UIText::UIText(GameObject& parent, std::string text, float fontSize)
@@ -86,48 +65,16 @@ void UIText::DrawUI(const UIRect& rect, float scale)
TextStyle{.color = m_Color, .outline = m_Outline * scale, .outlineColor = m_OutlineColor});
}
nlohmann::json UIText::Serialize() const
void UIText::Reflect(FieldVisitor& v)
{
return {
{"text", m_Text},
{"font", m_FontPath},
{"fontSize", m_FontSize},
{"color", m_Color},
{"outline", m_Outline},
{"outlineColor", m_OutlineColor},
{"horizontalAlign", AlignName(m_HorizontalAlign)},
{"verticalAlign", AlignName(m_VerticalAlign)},
};
}
static constexpr std::array<const char*, 3> AlignNames{"start", "center", "end"};
void UIText::Deserialize(const nlohmann::json& data)
{
m_Text = data.value("text", m_Text);
m_FontPath = data.value("font", m_FontPath);
m_FontSize = data.value("fontSize", m_FontSize);
m_Color = data.value("color", m_Color);
m_Outline = data.value("outline", m_Outline);
m_OutlineColor = data.value("outlineColor", m_OutlineColor);
m_HorizontalAlign = ParseAlign(data.value("horizontalAlign", std::string{}), m_HorizontalAlign);
m_VerticalAlign = ParseAlign(data.value("verticalAlign", std::string{}), m_VerticalAlign);
}
void UIText::ImGuiInspector()
{
ImGuiUtils::InputStringMultiline("Text", m_Text);
ImGuiUtils::InputString("Font", m_FontPath);
ImGui::DragFloat("Font Size", &m_FontSize, 0.5f, 1.0f, 1000.0f);
ImGui::ColorEdit4("Color", &m_Color.x);
ImGui::DragFloat("Outline", &m_Outline, 0.1f, 0.0f, 16.0f);
ImGui::ColorEdit4("Outline Color", &m_OutlineColor.x);
constexpr std::array alignNames{"Start", "Center", "End"};
int horizontal = static_cast<int>(m_HorizontalAlign);
if (ImGui::Combo("Horizontal", &horizontal, alignNames.data(), static_cast<int>(alignNames.size()))) {
m_HorizontalAlign = static_cast<UITextAlign>(horizontal);
}
int vertical = static_cast<int>(m_VerticalAlign);
if (ImGui::Combo("Vertical", &vertical, alignNames.data(), static_cast<int>(alignNames.size()))) {
m_VerticalAlign = static_cast<UITextAlign>(vertical);
}
v.Field("text", m_Text, {.multiline = true});
v.Field("font", m_FontPath, {.acceptAssets = true});
v.Field("fontSize", m_FontSize, {.speed = 0.5f, .min = 1.0, .max = 1000.0});
v.Field("color", m_Color, {.color = true});
v.Field("outline", m_Outline, {.min = 0.0, .max = 16.0});
v.Field("outlineColor", m_OutlineColor, {.color = true});
v.Field("horizontalAlign", m_HorizontalAlign, {.label = "Horizontal", .enumNames = AlignNames});
v.Field("verticalAlign", m_VerticalAlign, {.label = "Vertical", .enumNames = AlignNames});
}
+51 -10
View File
@@ -65,6 +65,26 @@ namespace reflect_detail {
return ImGui::DragScalarN(label, ToImGui(type), value, components, options.speed, min, max, options.format, ClampFlags(options));
}
bool DrawAngle(const char* label, float& radians, const FieldOptions& options)
{
float degrees = glm::degrees(radians);
const char* format = options.format != nullptr ? options.format : "%.1f deg";
bool changed;
if (options.min && options.max) {
changed = ImGui::SliderFloat(label, &degrees, glm::degrees(static_cast<float>(*options.min)),
glm::degrees(static_cast<float>(*options.max)), format, ImGuiSliderFlags_AlwaysClamp);
} else {
const float min = options.min ? glm::degrees(static_cast<float>(*options.min)) : 0.0f;
const float max = options.max ? glm::degrees(static_cast<float>(*options.max)) : 0.0f;
changed = ImGui::DragScalar(label, ImGuiDataType_Float, &degrees, options.speed,
options.min ? &min : nullptr, options.max ? &max : nullptr, format, ClampFlags(options));
}
if (changed) {
radians = glm::radians(degrees);
}
return changed;
}
bool DrawEnum(const char* label, std::int64_t& value, const FieldOptions& options)
{
if (options.enumNames.empty()) {
@@ -72,11 +92,11 @@ namespace reflect_detail {
}
const auto count = static_cast<std::int64_t>(options.enumNames.size());
const char* preview = value >= 0 && value < count ? options.enumNames[static_cast<std::size_t>(value)] : "?";
const std::string preview = value >= 0 && value < count ? PrettifyFieldName(options.enumNames[static_cast<std::size_t>(value)]) : "?";
bool changed = false;
if (ImGui::BeginCombo(label, preview)) {
if (ImGui::BeginCombo(label, preview.c_str())) {
for (std::int64_t i = 0; i < count; ++i) {
if (ImGui::Selectable(options.enumNames[static_cast<std::size_t>(i)], i == value)) {
if (ImGui::Selectable(PrettifyFieldName(options.enumNames[static_cast<std::size_t>(i)]).c_str(), i == value)) {
value = i;
changed = true;
}
@@ -134,9 +154,17 @@ bool DrawField(const char* label, bool& value, const FieldOptions&, const Scene*
return ImGui::Checkbox(label, &value);
}
bool DrawField(const char* label, std::string& value, const FieldOptions&, const Scene*)
bool DrawField(const char* label, std::string& value, const FieldOptions& options, const Scene*)
{
return ImGuiUtils::InputString(label, value);
bool changed = options.multiline ? ImGuiUtils::InputStringMultiline(label, value) : ImGuiUtils::InputString(label, value);
if (options.acceptAssets && ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(ImGuiUtils::AssetPayload)) {
value = static_cast<const char*>(payload->Data);
changed = true;
}
ImGui::EndDragDropTarget();
}
return changed;
}
bool DrawField(const char* label, glm::vec2& value, const FieldOptions& options, const Scene*)
@@ -241,14 +269,14 @@ void ResolveField(std::vector<ObjectRef>& value, const ObjectRef::ObjectMap& obj
});
}
void JsonWriter::Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops)
void JsonWriter::Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit&)
{
if (!options.transient) {
ops.save(value, m_Result[std::string{key}], options);
}
}
void JsonReader::Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops)
void JsonReader::Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit)
{
if (options.transient || !m_Data.is_object()) {
return;
@@ -262,15 +290,21 @@ void JsonReader::Visit(std::string_view key, void* value, const FieldOptions& op
} catch (const std::exception& exception) {
throw std::runtime_error("\"" + std::string{key} + "\": " + exception.what());
}
if (commit) {
commit(value);
}
}
void ReferenceResolver::Visit(std::string_view key, void* value, const FieldOptions&, const FieldOps& ops)
void ReferenceResolver::Visit(std::string_view key, void* value, const FieldOptions&, const FieldOps& ops, const Commit& commit)
{
if (ops.resolve == nullptr) {
return;
}
std::vector<ObjectId> dropped;
ops.resolve(value, m_Objects, dropped);
if (commit) {
commit(value);
}
const GameObject* owner = m_Owner.GetGameObject();
for (const ObjectId id : dropped) {
spdlog::warn("{} on {}: {} references object {} which is not in the scene file, dropping it",
@@ -278,7 +312,7 @@ void ReferenceResolver::Visit(std::string_view key, void* value, const FieldOpti
}
}
void InspectorDrawer::Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops)
void InspectorDrawer::Visit(std::string_view key, void* value, const FieldOptions& options, const FieldOps& ops, const Commit& commit)
{
if (options.hidden) {
return;
@@ -286,7 +320,14 @@ void InspectorDrawer::Visit(std::string_view key, void* value, const FieldOption
const std::string label = options.label != nullptr ? options.label : reflect_detail::PrettifyFieldName(key);
ImGui::PushID(key.data(), key.data() + key.size());
ImGui::BeginDisabled(options.readOnly);
m_Changed |= ops.draw(label.c_str(), value, options, m_Scene);
const bool changed = ops.draw(label.c_str(), value, options, m_Scene);
if (options.tooltip != nullptr) {
ImGui::SetItemTooltip("%s", options.tooltip);
}
if (changed && commit) {
commit(value);
}
m_Changed |= changed;
ImGui::EndDisabled();
ImGui::PopID();
}