feat: add imguizmo + trigger buxfix
This commit is contained in:
@@ -51,3 +51,6 @@
|
|||||||
[submodule "destrum/third_party/tracy"]
|
[submodule "destrum/third_party/tracy"]
|
||||||
path = destrum/third_party/tracy
|
path = destrum/third_party/tracy
|
||||||
url = https://github.com/wolfpld/tracy.git
|
url = https://github.com/wolfpld/tracy.git
|
||||||
|
[submodule "destrum/third_party/imguizmo"]
|
||||||
|
path = destrum/third_party/imguizmo
|
||||||
|
url = https://github.com/CedricGuillemet/ImGuizmo.git
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ set(SRC_FILES
|
|||||||
|
|
||||||
"src/Util/DeltaTime.cpp"
|
"src/Util/DeltaTime.cpp"
|
||||||
"src/Util/ImGuiUtils.cpp"
|
"src/Util/ImGuiUtils.cpp"
|
||||||
|
"src/Util/TransformGizmo.cpp"
|
||||||
|
|
||||||
"src/Physics/PhysicsWorld.cpp"
|
"src/Physics/PhysicsWorld.cpp"
|
||||||
"src/Physics/SimplePhysicsWorld.cpp"
|
"src/Physics/SimplePhysicsWorld.cpp"
|
||||||
@@ -150,6 +151,7 @@ target_link_libraries(destrum
|
|||||||
tinygltf
|
tinygltf
|
||||||
assimp
|
assimp
|
||||||
imgui
|
imgui
|
||||||
|
imguizmo
|
||||||
Jolt
|
Jolt
|
||||||
Tracy::TracyClient
|
Tracy::TracyClient
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ public:
|
|||||||
|
|
||||||
~Collider() override = default;
|
~Collider() override = default;
|
||||||
|
|
||||||
|
[[nodiscard]] bool AffectsPhysics() const override { return true; }
|
||||||
|
|
||||||
void ImGuiInspector() override;
|
void ImGuiInspector() override;
|
||||||
|
|
||||||
[[nodiscard]] virtual PhysicsShapeDesc BuildPhysicsShape() const = 0;
|
[[nodiscard]] virtual PhysicsShapeDesc BuildPhysicsShape() const = 0;
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ public:
|
|||||||
|
|
||||||
~Rigidbody() override;
|
~Rigidbody() override;
|
||||||
|
|
||||||
|
[[nodiscard]] bool AffectsPhysics() const override { return true; }
|
||||||
|
|
||||||
void Update(float) override {}
|
void Update(float) override {}
|
||||||
void Start() override;
|
void Start() override;
|
||||||
void Destroy() override;
|
void Destroy() override;
|
||||||
|
|||||||
@@ -126,6 +126,9 @@ public:
|
|||||||
void SetMovementSpeed(float speed) { m_movementSpeed = speed; }
|
void SetMovementSpeed(float speed) { m_movementSpeed = speed; }
|
||||||
[[nodiscard]] float GetMovementSpeed() const { return m_movementSpeed; }
|
[[nodiscard]] float GetMovementSpeed() const { return m_movementSpeed; }
|
||||||
|
|
||||||
|
void SetMouseSensitivity(float sensitivity) { m_mouseSensitivity = sensitivity; }
|
||||||
|
[[nodiscard]] float GetMouseSensitivity() const { return m_mouseSensitivity; }
|
||||||
|
|
||||||
void SetRotation(float yawRadians, float pitchRadians);
|
void SetRotation(float yawRadians, float pitchRadians);
|
||||||
void SetRotation(const glm::vec2& yawPitchRadians);
|
void SetRotation(const glm::vec2& yawPitchRadians);
|
||||||
void SetOrientation(const glm::vec3& forward, const glm::vec3& up);
|
void SetOrientation(const glm::vec3& forward, const glm::vec3& up);
|
||||||
@@ -168,6 +171,7 @@ private:
|
|||||||
Frustum m_frustum{};
|
Frustum m_frustum{};
|
||||||
|
|
||||||
float m_movementSpeed{2.0f};
|
float m_movementSpeed{2.0f};
|
||||||
|
float m_mouseSensitivity{0.003f};
|
||||||
|
|
||||||
float m_yaw = 0.0f; // radians
|
float m_yaw = 0.0f; // radians
|
||||||
float m_pitch = 0.0f; // radians
|
float m_pitch = 0.0f; // radians
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public:
|
|||||||
void init(SDL_Window* window, const std::string& appName, bool vSync);
|
void init(SDL_Window* window, const std::string& appName, bool vSync);
|
||||||
void cleanup();
|
void cleanup();
|
||||||
|
|
||||||
void recreateSwapchain(int width, int height);
|
[[nodiscard]] bool recreateSwapchain(int width, int height);
|
||||||
|
|
||||||
VkCommandBuffer beginFrame();
|
VkCommandBuffer beginFrame();
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public:
|
|||||||
std::uint32_t height,
|
std::uint32_t height,
|
||||||
bool vSync);
|
bool vSync);
|
||||||
|
|
||||||
void recreateSwapchain(
|
[[nodiscard]] bool recreateSwapchain(
|
||||||
VkDevice device,
|
VkDevice device,
|
||||||
vkb::Device vkbDevice,
|
vkb::Device vkbDevice,
|
||||||
VkSurfaceKHR surface,
|
VkSurfaceKHR surface,
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ public:
|
|||||||
virtual void Start();
|
virtual void Start();
|
||||||
virtual void SetEnabled(bool enabled);
|
virtual void SetEnabled(bool enabled);
|
||||||
|
|
||||||
|
[[nodiscard]] virtual bool AffectsPhysics() const { return false; }
|
||||||
|
|
||||||
// Reason for keeping No DT function -> i am tired and lazy
|
// Reason for keeping No DT function -> i am tired and lazy
|
||||||
// If im feeling frisky im gonna claim backwards compatibility
|
// If im feeling frisky im gonna claim backwards compatibility
|
||||||
virtual void Update(float dt);
|
virtual void Update(float dt);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public:
|
|||||||
|
|
||||||
void CleanupComponents();
|
void CleanupComponents();
|
||||||
void RefreshPhysics();
|
void RefreshPhysics();
|
||||||
|
void RefreshPhysicsInHierarchy();
|
||||||
|
|
||||||
void SetActive(bool active) {
|
void SetActive(bool active) {
|
||||||
if (active == m_Active) {
|
if (active == m_Active) {
|
||||||
@@ -36,7 +37,7 @@ public:
|
|||||||
|
|
||||||
m_Active = active;
|
m_Active = active;
|
||||||
SetActiveDirty();
|
SetActiveDirty();
|
||||||
RefreshPhysics();
|
RefreshPhysicsInHierarchy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool IsActive() const { return m_Active; }
|
[[nodiscard]] bool IsActive() const { return m_Active; }
|
||||||
@@ -76,7 +77,9 @@ public:
|
|||||||
auto& addedComponent = m_Components.emplace_back(
|
auto& addedComponent = m_Components.emplace_back(
|
||||||
std::make_unique<TComponent>(*this, std::forward<Args>(args)...));
|
std::make_unique<TComponent>(*this, std::forward<Args>(args)...));
|
||||||
|
|
||||||
RefreshPhysics();
|
if (addedComponent->AffectsPhysics()) {
|
||||||
|
RefreshPhysics();
|
||||||
|
}
|
||||||
return static_cast<TComponent*>(addedComponent.get());
|
return static_cast<TComponent*>(addedComponent.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ public:
|
|||||||
return m_objects;
|
return m_objects;
|
||||||
}
|
}
|
||||||
[[nodiscard]] GameObject* FindGameObject(ObjectId id) const;
|
[[nodiscard]] GameObject* FindGameObject(ObjectId id) const;
|
||||||
|
[[nodiscard]] GameObject* GetSelectedObject() const { return FindGameObject(m_SelectedObjectId); }
|
||||||
|
|
||||||
void SetRegisterBindings(std::function<void()> registerBindings) {
|
void SetRegisterBindings(std::function<void()> registerBindings) {
|
||||||
m_registerBindings = std::move(registerBindings);
|
m_registerBindings = std::move(registerBindings);
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef DESTRUM_TRANSFORMGIZMO_H
|
||||||
|
#define DESTRUM_TRANSFORMGIZMO_H
|
||||||
|
|
||||||
|
class Camera;
|
||||||
|
class GameObject;
|
||||||
|
|
||||||
|
enum class GizmoOperation {
|
||||||
|
Translate,
|
||||||
|
Rotate,
|
||||||
|
Scale,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class GizmoSpace {
|
||||||
|
World,
|
||||||
|
Local,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TransformGizmo final {
|
||||||
|
public:
|
||||||
|
void HandleShortcuts();
|
||||||
|
void DrawToolbar();
|
||||||
|
bool Manipulate(GameObject& object, const Camera& camera);
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsUsing() const;
|
||||||
|
[[nodiscard]] bool IsOver() const;
|
||||||
|
|
||||||
|
GizmoOperation operation{GizmoOperation::Translate};
|
||||||
|
GizmoSpace space{GizmoSpace::World};
|
||||||
|
bool snap{false};
|
||||||
|
float translationSnap{0.5f};
|
||||||
|
float rotationSnapDegrees{15.0f};
|
||||||
|
float scaleSnap{0.1f};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DESTRUM_TRANSFORMGIZMO_H
|
||||||
+13
-3
@@ -170,6 +170,14 @@ void App::run()
|
|||||||
|
|
||||||
if (!isRunning) break;
|
if (!isRunning) break;
|
||||||
|
|
||||||
|
if (window.IsMinimized())
|
||||||
|
{
|
||||||
|
// The surface is 0x0 while minimized, there is nothing to render or present to
|
||||||
|
accumulator = 0.0f;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Consume SDL events before updating the base camera so held and
|
// Consume SDL events before updating the base camera so held and
|
||||||
// newly pressed inputs are applied in the same frame.
|
// newly pressed inputs are applied in the same frame.
|
||||||
camera.Update(dt);
|
camera.Update(dt);
|
||||||
@@ -290,11 +298,13 @@ if (!isRunning) break;
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
spdlog::info("Recreating swapchain to size: {}x{}", w, h);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
ZoneScopedN("Recreate Swapchain");
|
ZoneScopedN("Recreate Swapchain");
|
||||||
gfxDevice.recreateSwapchain(w, h);
|
if (!gfxDevice.recreateSwapchain(w, h))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
spdlog::info("Recreated swapchain at {}x{}", w, h);
|
||||||
imguiPass.onSwapchainRecreated();
|
imguiPass.onSwapchainRecreated();
|
||||||
onWindowResize(w, h);
|
onWindowResize(w, h);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ void Camera::Update(float deltaTime) {
|
|||||||
if (input.IsKeyDown(SDL_SCANCODE_LEFT)) m_yaw -= keyLookSpeed * deltaTime;
|
if (input.IsKeyDown(SDL_SCANCODE_LEFT)) m_yaw -= keyLookSpeed * deltaTime;
|
||||||
if (input.IsKeyDown(SDL_SCANCODE_RIGHT)) m_yaw += keyLookSpeed * deltaTime;
|
if (input.IsKeyDown(SDL_SCANCODE_RIGHT)) m_yaw += keyLookSpeed * deltaTime;
|
||||||
|
|
||||||
|
if (input.IsMouseDown(SDL_BUTTON_RIGHT)) {
|
||||||
|
m_yaw += static_cast<float>(input.MouseDeltaX()) * m_mouseSensitivity;
|
||||||
|
m_pitch -= static_cast<float>(input.MouseDeltaY()) * m_mouseSensitivity;
|
||||||
|
}
|
||||||
|
|
||||||
if (pad0 >= 0) {
|
if (pad0 >= 0) {
|
||||||
const float rx = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_RIGHTX);
|
const float rx = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_RIGHTX);
|
||||||
const float ry = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_RIGHTY);
|
const float ry = input.GetPadAxis(pad0, SDL_CONTROLLER_AXIS_RIGHTY);
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ void GfxDevice::cleanup() {
|
|||||||
initialized = false;
|
initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxDevice::recreateSwapchain(int width, int height) {
|
bool GfxDevice::recreateSwapchain(int width, int height) {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
throw std::logic_error("GfxDevice::recreateSwapchain called before init");
|
throw std::logic_error("GfxDevice::recreateSwapchain called before init");
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ void GfxDevice::recreateSwapchain(int width, int height) {
|
|||||||
}
|
}
|
||||||
waitIdle();
|
waitIdle();
|
||||||
|
|
||||||
swapchain.recreateSwapchain(
|
const bool recreated = swapchain.recreateSwapchain(
|
||||||
instanceManager.getDevice(),
|
instanceManager.getDevice(),
|
||||||
instanceManager.getVkbDevice(),
|
instanceManager.getVkbDevice(),
|
||||||
instanceManager.getSurface(),
|
instanceManager.getSurface(),
|
||||||
@@ -115,6 +115,7 @@ void GfxDevice::recreateSwapchain(int width, int height) {
|
|||||||
m_vSync);
|
m_vSync);
|
||||||
|
|
||||||
swapchainFormat = swapchain.getFormat();
|
swapchainFormat = swapchain.getFormat();
|
||||||
|
return recreated;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkCommandBuffer GfxDevice::beginFrame()
|
VkCommandBuffer GfxDevice::beginFrame()
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ void Swapchain::createSwapchain(
|
|||||||
dirty = false;
|
dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Swapchain::recreateSwapchain(
|
bool Swapchain::recreateSwapchain(
|
||||||
VkDevice,
|
VkDevice,
|
||||||
vkb::Device vkbDevice,
|
vkb::Device vkbDevice,
|
||||||
VkSurfaceKHR surf,
|
VkSurfaceKHR surf,
|
||||||
@@ -120,7 +120,15 @@ void Swapchain::recreateSwapchain(
|
|||||||
|
|
||||||
if (width == 0 || height == 0) {
|
if (width == 0 || height == 0) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A minimized window can still report its old size while the surface is 0x0
|
||||||
|
VkSurfaceCapabilitiesKHR capabilities{};
|
||||||
|
if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vkbDevice.physical_device, surf, &capabilities) == VK_SUCCESS &&
|
||||||
|
(capabilities.currentExtent.width == 0 || capabilities.currentExtent.height == 0)) {
|
||||||
|
dirty = true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
surface = surf;
|
surface = surf;
|
||||||
@@ -209,6 +217,8 @@ void Swapchain::recreateSwapchain(
|
|||||||
|
|
||||||
extent = m_swapchain.extent;
|
extent = m_swapchain.extent;
|
||||||
dirty = false;
|
dirty = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Swapchain::cleanup(VkDevice device) {
|
void Swapchain::cleanup(VkDevice device) {
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ Transform& Component::GetTransform() const {
|
|||||||
|
|
||||||
void Component::Destroy() {
|
void Component::Destroy() {
|
||||||
Object::Destroy();
|
Object::Destroy();
|
||||||
NotifyPhysicsChanged();
|
if (AffectsPhysics()) {
|
||||||
|
NotifyPhysicsChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Component::Start() {
|
void Component::Start() {
|
||||||
@@ -24,17 +26,14 @@ void Component::Start() {
|
|||||||
|
|
||||||
void Component::SetEnabled(bool enabled) {
|
void Component::SetEnabled(bool enabled) {
|
||||||
m_IsEnabled = enabled;
|
m_IsEnabled = enabled;
|
||||||
NotifyPhysicsChanged();
|
if (AffectsPhysics()) {
|
||||||
|
NotifyPhysicsChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Component::NotifyPhysicsChanged() {
|
void Component::NotifyPhysicsChanged() {
|
||||||
if (m_ParentGameObjectPtr != nullptr) {
|
if (m_ParentGameObjectPtr != nullptr) {
|
||||||
if (auto* rigidbody = m_ParentGameObjectPtr->GetComponent<Rigidbody>();
|
m_ParentGameObjectPtr->RefreshPhysics();
|
||||||
rigidbody != nullptr && rigidbody->GetPhysicsWorld() != nullptr) {
|
|
||||||
rigidbody->GetPhysicsWorld()->RefreshRigidbody(*rigidbody);
|
|
||||||
} else {
|
|
||||||
m_ParentGameObjectPtr->RefreshPhysics();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -196,8 +196,41 @@ void GameObject::CleanupComponents() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameObject::RefreshPhysics() {
|
namespace {
|
||||||
if (m_Scene != nullptr) {
|
void RefreshBody(GameObject& object, Scene* scene) {
|
||||||
m_Scene->RefreshPhysics();
|
auto* rigidbody = object.GetComponent<Rigidbody>();
|
||||||
|
if (rigidbody == nullptr || rigidbody->IsBeingDestroyed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PhysicsWorld* world = rigidbody->GetPhysicsWorld()) {
|
||||||
|
world->RefreshRigidbody(*rigidbody);
|
||||||
|
} else if (scene != nullptr) {
|
||||||
|
scene->GetPhysics().RefreshGameObject(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RefreshChildBodies(const Transform& transform, Scene* scene) {
|
||||||
|
for (Transform* child : transform.GetChildren()) {
|
||||||
|
if (GameObject* owner = child->GetOwner()) {
|
||||||
|
RefreshBody(*owner, scene);
|
||||||
|
}
|
||||||
|
RefreshChildBodies(*child, scene);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameObject::RefreshPhysics() {
|
||||||
|
for (Transform* transform = &m_TransformPtr; transform != nullptr; transform = transform->GetParent()) {
|
||||||
|
GameObject* owner = transform->GetOwner();
|
||||||
|
if (owner != nullptr && owner->GetComponent<Rigidbody>() != nullptr) {
|
||||||
|
RefreshBody(*owner, m_Scene);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameObject::RefreshPhysicsInHierarchy() {
|
||||||
|
RefreshPhysics();
|
||||||
|
RefreshChildBodies(m_TransformPtr, m_Scene);
|
||||||
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ void Transform::SetParent(Transform* parent, bool useWorldPosition) {
|
|||||||
|
|
||||||
if (m_Owner != nullptr) {
|
if (m_Owner != nullptr) {
|
||||||
m_Owner->SetActiveDirty();
|
m_Owner->SetActiveDirty();
|
||||||
m_Owner->RefreshPhysics();
|
m_Owner->RefreshPhysicsInHierarchy();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -362,10 +362,11 @@ namespace
|
|||||||
m_Overlaps.clear();
|
m_Overlaps.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_map<BodyPair, PairData, PairHash> SwapOverlaps()
|
// Persisted contacts are not reported again, so the set has to outlive each step
|
||||||
|
std::unordered_map<BodyPair, PairData, PairHash> SnapshotOverlaps()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(m_Mutex);
|
std::lock_guard<std::mutex> lock(m_Mutex);
|
||||||
return std::move(m_Overlaps);
|
return m_Overlaps;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -752,7 +753,7 @@ private:
|
|||||||
|
|
||||||
void ProcessSensorOverlaps()
|
void ProcessSensorOverlaps()
|
||||||
{
|
{
|
||||||
const auto currentOverlaps = m_SensorListener.SwapOverlaps();
|
auto currentOverlaps = m_SensorListener.SnapshotOverlaps();
|
||||||
|
|
||||||
for (const auto& [pair, data] : currentOverlaps)
|
for (const auto& [pair, data] : currentOverlaps)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ namespace ImGuiUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (transformChanged) {
|
if (transformChanged) {
|
||||||
selectedObject->RefreshPhysics();
|
selectedObject->RefreshPhysicsInHierarchy();
|
||||||
}
|
}
|
||||||
|
|
||||||
const glm::vec3& worldPosition = transform.GetWorldPosition();
|
const glm::vec3& worldPosition = transform.GetWorldPosition();
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
#include <destrum/Util/TransformGizmo.h>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/Camera.h>
|
||||||
|
#include <destrum/ObjectModel/GameObject.h>
|
||||||
|
|
||||||
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
|
#include <glm/gtx/matrix_decompose.hpp>
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <ImGuizmo.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
ImGuizmo::OPERATION toImGuizmo(GizmoOperation operation)
|
||||||
|
{
|
||||||
|
switch (operation) {
|
||||||
|
case GizmoOperation::Rotate:
|
||||||
|
return ImGuizmo::ROTATE;
|
||||||
|
case GizmoOperation::Scale:
|
||||||
|
return ImGuizmo::SCALE;
|
||||||
|
case GizmoOperation::Translate:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ImGuizmo::TRANSLATE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransformGizmo::HandleShortcuts()
|
||||||
|
{
|
||||||
|
if (ImGui::GetIO().WantTextInput || ImGuizmo::IsUsing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_T, false)) {
|
||||||
|
operation = GizmoOperation::Translate;
|
||||||
|
}
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_R, false)) {
|
||||||
|
operation = GizmoOperation::Rotate;
|
||||||
|
}
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_Y, false)) {
|
||||||
|
operation = GizmoOperation::Scale;
|
||||||
|
}
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_L, false)) {
|
||||||
|
space = space == GizmoSpace::World ? GizmoSpace::Local : GizmoSpace::World;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransformGizmo::DrawToolbar()
|
||||||
|
{
|
||||||
|
if (!ImGui::Begin("Gizmo", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto operationButton = [this](const char* label, GizmoOperation buttonOperation) {
|
||||||
|
if (ImGui::RadioButton(label, operation == buttonOperation)) {
|
||||||
|
operation = buttonOperation;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
operationButton("Move (T)", GizmoOperation::Translate);
|
||||||
|
ImGui::SameLine();
|
||||||
|
operationButton("Rotate (R)", GizmoOperation::Rotate);
|
||||||
|
ImGui::SameLine();
|
||||||
|
operationButton("Scale (Y)", GizmoOperation::Scale);
|
||||||
|
|
||||||
|
if (operation != GizmoOperation::Scale) {
|
||||||
|
bool local = space == GizmoSpace::Local;
|
||||||
|
if (ImGui::Checkbox("Local (L)", &local)) {
|
||||||
|
space = local ? GizmoSpace::Local : GizmoSpace::World;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Checkbox("Snap", &snap);
|
||||||
|
if (snap) {
|
||||||
|
ImGui::SetNextItemWidth(80.0f);
|
||||||
|
switch (operation) {
|
||||||
|
case GizmoOperation::Translate:
|
||||||
|
ImGui::DragFloat("Step", &translationSnap, 0.05f, 0.01f, 10.0f, "%.2f m");
|
||||||
|
break;
|
||||||
|
case GizmoOperation::Rotate:
|
||||||
|
ImGui::DragFloat("Step", &rotationSnapDegrees, 1.0f, 1.0f, 90.0f, "%.0f deg");
|
||||||
|
break;
|
||||||
|
case GizmoOperation::Scale:
|
||||||
|
ImGui::DragFloat("Step", &scaleSnap, 0.01f, 0.01f, 1.0f, "%.2f");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransformGizmo::Manipulate(GameObject& object, const Camera& camera)
|
||||||
|
{
|
||||||
|
ImGuizmo::BeginFrame();
|
||||||
|
|
||||||
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||||
|
ImGuizmo::SetOrthographic(false);
|
||||||
|
ImGuizmo::SetRect(viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y);
|
||||||
|
|
||||||
|
const glm::mat4 view = camera.GetViewMatrix();
|
||||||
|
glm::mat4 projection = camera.GetProjectionMatrix();
|
||||||
|
// ImGuizmo expects OpenGL's y up clip space, the engine flips y for Vulkan
|
||||||
|
projection[1][1] *= -1.0f;
|
||||||
|
|
||||||
|
Transform& transform = object.GetTransform();
|
||||||
|
glm::mat4 worldMatrix = transform.GetWorldMatrix();
|
||||||
|
|
||||||
|
const float snapStep = operation == GizmoOperation::Translate ? translationSnap
|
||||||
|
: operation == GizmoOperation::Rotate ? rotationSnapDegrees
|
||||||
|
: scaleSnap;
|
||||||
|
const float snapValues[3] = {snapStep, snapStep, snapStep};
|
||||||
|
const ImGuizmo::MODE mode =
|
||||||
|
operation == GizmoOperation::Scale || space == GizmoSpace::Local ? ImGuizmo::LOCAL : ImGuizmo::WORLD;
|
||||||
|
|
||||||
|
const bool changed = ImGuizmo::Manipulate(
|
||||||
|
glm::value_ptr(view),
|
||||||
|
glm::value_ptr(projection),
|
||||||
|
toImGuizmo(operation),
|
||||||
|
mode,
|
||||||
|
glm::value_ptr(worldMatrix),
|
||||||
|
nullptr,
|
||||||
|
snap ? snapValues : nullptr);
|
||||||
|
|
||||||
|
// Stops the click on a handle from reaching the game, e.g. grabbing the mouse for first person
|
||||||
|
if (ImGuizmo::IsOver() || ImGuizmo::IsUsing()) {
|
||||||
|
ImGui::SetNextFrameWantCaptureMouse(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 position;
|
||||||
|
glm::quat rotation;
|
||||||
|
glm::vec3 scale;
|
||||||
|
glm::vec3 skew;
|
||||||
|
glm::vec4 perspective;
|
||||||
|
if (!glm::decompose(worldMatrix, scale, rotation, position, skew, perspective)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (operation) {
|
||||||
|
case GizmoOperation::Translate:
|
||||||
|
transform.SetWorldPosition(position);
|
||||||
|
break;
|
||||||
|
case GizmoOperation::Rotate:
|
||||||
|
transform.SetWorldRotation(rotation);
|
||||||
|
break;
|
||||||
|
case GizmoOperation::Scale:
|
||||||
|
transform.SetWorldScale(scale);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
object.RefreshPhysicsInHierarchy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransformGizmo::IsUsing() const
|
||||||
|
{
|
||||||
|
return ImGuizmo::IsUsing();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransformGizmo::IsOver() const
|
||||||
|
{
|
||||||
|
return ImGuizmo::IsOver();
|
||||||
|
}
|
||||||
Vendored
+4
@@ -119,6 +119,10 @@ target_link_libraries(imgui PUBLIC
|
|||||||
volk
|
volk
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ImGuizmo
|
||||||
|
add_subdirectory(imguizmo)
|
||||||
|
target_link_libraries(imguizmo PUBLIC imgui)
|
||||||
|
|
||||||
# Jolt Physics
|
# Jolt Physics
|
||||||
set(DOUBLE_PRECISION OFF CACHE BOOL "" FORCE)
|
set(DOUBLE_PRECISION OFF CACHE BOOL "" FORCE)
|
||||||
set(GENERATE_DEBUG_SYMBOLS ON CACHE BOOL "" FORCE)
|
set(GENERATE_DEBUG_SYMBOLS ON CACHE BOOL "" FORCE)
|
||||||
|
|||||||
+1
Submodule destrum/third_party/imguizmo added at 105e4fde45
@@ -17,6 +17,7 @@ set(GAME_SRC
|
|||||||
src/components/FirstPersonController.cpp
|
src/components/FirstPersonController.cpp
|
||||||
src/components/Interactable.cpp
|
src/components/Interactable.cpp
|
||||||
src/components/InteractableButton.cpp
|
src/components/InteractableButton.cpp
|
||||||
|
src/components/LightToggle.cpp
|
||||||
src/components/PrintComponent.cpp
|
src/components/PrintComponent.cpp
|
||||||
src/components/TriggerLoggerComponent.cpp
|
src/components/TriggerLoggerComponent.cpp
|
||||||
)
|
)
|
||||||
|
|||||||
Binary file not shown.
@@ -6,6 +6,7 @@
|
|||||||
#include <destrum/App.h>
|
#include <destrum/App.h>
|
||||||
#include <destrum/Scene/SceneManager.h>
|
#include <destrum/Scene/SceneManager.h>
|
||||||
#include <destrum/Graphics/RenderResources.h>
|
#include <destrum/Graphics/RenderResources.h>
|
||||||
|
#include <destrum/Util/TransformGizmo.h>
|
||||||
|
|
||||||
#include "destrum/Graphics/Resources/Cubemap.h"
|
#include "destrum/Graphics/Resources/Cubemap.h"
|
||||||
#include "destrum/Graphics/Resources/Font.h"
|
#include "destrum/Graphics/Resources/Font.h"
|
||||||
@@ -31,13 +32,17 @@ public:
|
|||||||
private:
|
private:
|
||||||
void setupSkybox();
|
void setupSkybox();
|
||||||
void drawCrosshair();
|
void drawCrosshair();
|
||||||
|
void drawViewModeHint();
|
||||||
|
void updateViewMode();
|
||||||
[[nodiscard]] CameraComponent* getSceneCamera() const;
|
[[nodiscard]] CameraComponent* getSceneCamera() const;
|
||||||
|
[[nodiscard]] Camera& getViewCamera();
|
||||||
[[nodiscard]] bool isPlayerViewActive() const;
|
[[nodiscard]] bool isPlayerViewActive() const;
|
||||||
|
|
||||||
// LightkeeperDebugUi.cpp
|
// LightkeeperDebugUi.cpp
|
||||||
void updateDebugUi();
|
void updateDebugUi();
|
||||||
void drawDebugMenuBar();
|
void drawDebugMenuBar();
|
||||||
void drawWindowMenu();
|
void drawWindowMenu();
|
||||||
|
void drawSelectionGizmo();
|
||||||
void drawTestWindow();
|
void drawTestWindow();
|
||||||
void spawnBall();
|
void spawnBall();
|
||||||
void saveCurrentScene();
|
void saveCurrentScene();
|
||||||
@@ -54,6 +59,7 @@ private:
|
|||||||
|
|
||||||
ImageID debug2DTexture{NULL_IMAGE_ID};
|
ImageID debug2DTexture{NULL_IMAGE_ID};
|
||||||
Font hudFont;
|
Font hudFont;
|
||||||
|
TransformGizmo transformGizmo;
|
||||||
|
|
||||||
char scenePath[512]{"scenes/debug_scene.json"};
|
char scenePath[512]{"scenes/debug_scene.json"};
|
||||||
char newSceneName[128]{"EmptyScene"};
|
char newSceneName[128]{"EmptyScene"};
|
||||||
@@ -61,6 +67,7 @@ private:
|
|||||||
bool renderColliders{false};
|
bool renderColliders{false};
|
||||||
bool render2DShapes{true};
|
bool render2DShapes{true};
|
||||||
bool renderDebugLines{true};
|
bool renderDebugLines{true};
|
||||||
|
bool showTransformGizmo{true};
|
||||||
bool showTestWindow{true};
|
bool showTestWindow{true};
|
||||||
bool useFreeCamera{false};
|
bool useFreeCamera{false};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ public:
|
|||||||
[[nodiscard]] Interactable* GetFocusedInteractable() const { return m_FocusedInteractable; }
|
[[nodiscard]] Interactable* GetFocusedInteractable() const { return m_FocusedInteractable; }
|
||||||
[[nodiscard]] glm::vec3 GetLookDirection() const;
|
[[nodiscard]] glm::vec3 GetLookDirection() const;
|
||||||
|
|
||||||
[[nodiscard]] bool IsMouseCaptured() const { return InputManager::GetInstance().IsMouseCaptured(); }
|
[[nodiscard]] bool IsPossessed() const { return m_Possessed; }
|
||||||
void SetMouseCaptured(bool captured);
|
void SetPossessed(bool possessed);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] bool CheckGrounded() const;
|
[[nodiscard]] bool CheckGrounded() const;
|
||||||
@@ -70,6 +70,7 @@ private:
|
|||||||
|
|
||||||
Interactable* m_FocusedInteractable{nullptr};
|
Interactable* m_FocusedInteractable{nullptr};
|
||||||
|
|
||||||
|
bool m_Possessed{false};
|
||||||
bool m_Grounded{false};
|
bool m_Grounded{false};
|
||||||
bool m_JumpHeld{false};
|
bool m_JumpHeld{false};
|
||||||
float m_CoyoteTimer{0.0f};
|
float m_CoyoteTimer{0.0f};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <components/Door.h>
|
#include <components/Door.h>
|
||||||
#include <components/FirstPersonController.h>
|
#include <components/FirstPersonController.h>
|
||||||
#include <components/InteractableButton.h>
|
#include <components/InteractableButton.h>
|
||||||
|
#include <components/LightToggle.h>
|
||||||
#include <components/PrintComponent.h>
|
#include <components/PrintComponent.h>
|
||||||
#include <components/TriggerLoggerComponent.h>
|
#include <components/TriggerLoggerComponent.h>
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@
|
|||||||
X(Door) \
|
X(Door) \
|
||||||
X(FirstPersonController) \
|
X(FirstPersonController) \
|
||||||
X(InteractableButton) \
|
X(InteractableButton) \
|
||||||
|
X(LightToggle) \
|
||||||
X(PrintComponent) \
|
X(PrintComponent) \
|
||||||
X(TriggerLoggerComponent)
|
X(TriggerLoggerComponent)
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ public:
|
|||||||
[[nodiscard]] std::size_t GetColorIndex() const { return m_ColorIndex; }
|
[[nodiscard]] std::size_t GetColorIndex() const { return m_ColorIndex; }
|
||||||
[[nodiscard]] int GetPressCount() const { return m_PressCount; }
|
[[nodiscard]] int GetPressCount() const { return m_PressCount; }
|
||||||
|
|
||||||
|
void SetCycleColors(bool cycleColors) { m_CycleColors = cycleColors; }
|
||||||
|
|
||||||
// Every Activatable on a target object is activated when the button is pressed
|
// Every Activatable on a target object is activated when the button is pressed
|
||||||
void AddTarget(const GameObject& target);
|
void AddTarget(const GameObject& target);
|
||||||
void RemoveTarget(ObjectId target);
|
void RemoveTarget(ObjectId target);
|
||||||
@@ -36,6 +38,7 @@ private:
|
|||||||
|
|
||||||
std::size_t m_ColorIndex{0};
|
std::size_t m_ColorIndex{0};
|
||||||
int m_PressCount{0};
|
int m_PressCount{0};
|
||||||
|
bool m_CycleColors{true};
|
||||||
std::vector<ObjectId> m_Targets;
|
std::vector<ObjectId> m_Targets;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef LIGHTKEEPER_LIGHTTOGGLE_H
|
||||||
|
#define LIGHTKEEPER_LIGHTTOGGLE_H
|
||||||
|
|
||||||
|
#include <components/Activatable.h>
|
||||||
|
|
||||||
|
class LightToggle final : public Activatable {
|
||||||
|
public:
|
||||||
|
explicit LightToggle(GameObject& parent);
|
||||||
|
|
||||||
|
std::string GetTypeName() const override { return "LightToggle"; }
|
||||||
|
|
||||||
|
void Start() override;
|
||||||
|
|
||||||
|
nlohmann::json Serialize() const override;
|
||||||
|
void Deserialize(const nlohmann::json& data) override;
|
||||||
|
void ImGuiInspector() override;
|
||||||
|
|
||||||
|
void SetOn(bool on);
|
||||||
|
[[nodiscard]] bool IsOn() const { return m_On; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void OnActivate(GameObject& instigator) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Apply();
|
||||||
|
|
||||||
|
bool m_On{true};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LIGHTKEEPER_LIGHTTOGGLE_H
|
||||||
@@ -33,6 +33,7 @@ void LightKeeper::customInit()
|
|||||||
const float aspectRatio = static_cast<float>(m_params.renderSize.x) / static_cast<float>(m_params.renderSize.y);
|
const float aspectRatio = static_cast<float>(m_params.renderSize.x) / static_cast<float>(m_params.renderSize.y);
|
||||||
camera.setAspectRatio(aspectRatio);
|
camera.setAspectRatio(aspectRatio);
|
||||||
camera.SetRotation(glm::radians(glm::vec2(90.f, 0.f)));
|
camera.SetRotation(glm::radians(glm::vec2(90.f, 0.f)));
|
||||||
|
window.Maximize();
|
||||||
|
|
||||||
setupSkybox();
|
setupSkybox();
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ void LightKeeper::customInit()
|
|||||||
|
|
||||||
hudFont = Font::LoadFromFile(AssetFS::GetInstance().GetFullPath("engine://fonts/Roboto-Medium.ttf"));
|
hudFont = Font::LoadFromFile(AssetFS::GetInstance().GetFullPath("engine://fonts/Roboto-Medium.ttf"));
|
||||||
hudFont.Upload(gfxDevice, resources);
|
hudFont.Upload(gfxDevice, resources);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightKeeper::setupSkybox()
|
void LightKeeper::setupSkybox()
|
||||||
@@ -62,6 +64,8 @@ void LightKeeper::setupSkybox()
|
|||||||
|
|
||||||
void LightKeeper::customUpdate(float dt)
|
void LightKeeper::customUpdate(float dt)
|
||||||
{
|
{
|
||||||
|
updateViewMode();
|
||||||
|
|
||||||
// LightKeeper owns a camera that hides App::camera; update this instance
|
// LightKeeper owns a camera that hides App::camera; update this instance
|
||||||
// after SDL events have been consumed.
|
// after SDL events have been consumed.
|
||||||
if (!isPlayerViewActive() && getSceneCamera() == nullptr)
|
if (!isPlayerViewActive() && getSceneCamera() == nullptr)
|
||||||
@@ -79,6 +83,45 @@ void LightKeeper::customUpdate(float dt)
|
|||||||
updateDebugUi();
|
updateDebugUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LightKeeper::updateViewMode()
|
||||||
|
{
|
||||||
|
auto& input = InputManager::GetInstance();
|
||||||
|
FirstPersonController* player = mainScene.playerController;
|
||||||
|
|
||||||
|
if (player != nullptr && input.WasKeyPressed(SDL_SCANCODE_F5))
|
||||||
|
{
|
||||||
|
player->SetPossessed(!player->IsPossessed());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPlayerViewActive())
|
||||||
|
{
|
||||||
|
const bool looking = input.IsMouseDown(SDL_BUTTON_RIGHT);
|
||||||
|
if (looking != input.IsMouseCaptured())
|
||||||
|
{
|
||||||
|
input.SetMouseCaptured(looking);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightKeeper::drawViewModeHint()
|
||||||
|
{
|
||||||
|
if (isPlayerViewActive() || mainScene.playerController == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::string_view hint = "Free camera RMB look WASD / Q E move F5 play";
|
||||||
|
constexpr float hintSize = 18.0f;
|
||||||
|
const VkExtent3D extent = renderer.getDrawImage().extent;
|
||||||
|
const glm::vec2 hintExtent = hudFont.MeasureText(hint, hintSize);
|
||||||
|
|
||||||
|
Renderer2D::GetInstance().DrawText(
|
||||||
|
hudFont,
|
||||||
|
hint,
|
||||||
|
{16.0f, static_cast<float>(extent.height) - hintExtent.y - 12.0f},
|
||||||
|
hintSize,
|
||||||
|
TextStyle{.color = {1.0f, 1.0f, 1.0f, 0.85f}, .outline = 2.0f, .outlineColor = {0.0f, 0.0f, 0.0f, 0.6f}});
|
||||||
|
}
|
||||||
|
|
||||||
void LightKeeper::drawCrosshair()
|
void LightKeeper::drawCrosshair()
|
||||||
{
|
{
|
||||||
if (!isPlayerViewActive()) {
|
if (!isPlayerViewActive()) {
|
||||||
@@ -114,7 +157,7 @@ void LightKeeper::drawCrosshair()
|
|||||||
|
|
||||||
bool LightKeeper::isPlayerViewActive() const
|
bool LightKeeper::isPlayerViewActive() const
|
||||||
{
|
{
|
||||||
return mainScene.playerController != nullptr && mainScene.playerController->IsMouseCaptured();
|
return mainScene.playerController != nullptr && mainScene.playerController->IsPossessed();
|
||||||
}
|
}
|
||||||
|
|
||||||
CameraComponent* LightKeeper::getSceneCamera() const
|
CameraComponent* LightKeeper::getSceneCamera() const
|
||||||
@@ -126,6 +169,12 @@ CameraComponent* LightKeeper::getSceneCamera() const
|
|||||||
return activeScenes.front()->GetCurrentCamera();
|
return activeScenes.front()->GetCurrentCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Camera& LightKeeper::getViewCamera()
|
||||||
|
{
|
||||||
|
CameraComponent* sceneCamera = isPlayerViewActive() ? nullptr : getSceneCamera();
|
||||||
|
return sceneCamera != nullptr ? sceneCamera->GetCamera(camera.GetAspectRatio()) : camera;
|
||||||
|
}
|
||||||
|
|
||||||
void LightKeeper::customDraw()
|
void LightKeeper::customDraw()
|
||||||
{
|
{
|
||||||
const auto cmd = gfxDevice.beginFrame();
|
const auto cmd = gfxDevice.beginFrame();
|
||||||
@@ -136,6 +185,7 @@ void LightKeeper::customDraw()
|
|||||||
renderer.beginDrawing(gfxDevice);
|
renderer.beginDrawing(gfxDevice);
|
||||||
submitDebugDraws();
|
submitDebugDraws();
|
||||||
drawCrosshair();
|
drawCrosshair();
|
||||||
|
drawViewModeHint();
|
||||||
|
|
||||||
if (isPlayerViewActive())
|
if (isPlayerViewActive())
|
||||||
{
|
{
|
||||||
@@ -145,8 +195,7 @@ void LightKeeper::customDraw()
|
|||||||
camera.CalculateProjectionMatrix();
|
camera.CalculateProjectionMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
CameraComponent* sceneCamera = isPlayerViewActive() ? nullptr : getSceneCamera();
|
Camera& viewCamera = getViewCamera();
|
||||||
Camera& viewCamera = sceneCamera != nullptr ? sceneCamera->GetCamera(camera.GetAspectRatio()) : camera;
|
|
||||||
|
|
||||||
const SceneEnvironment& environment = SceneManager::GetInstance().GetCurrentScene().GetEnvironment();
|
const SceneEnvironment& environment = SceneManager::GetInstance().GetCurrentScene().GetEnvironment();
|
||||||
const GameRenderer::SceneData sceneData{
|
const GameRenderer::SceneData sceneData{
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ void LightKeeper::updateDebugUi()
|
|||||||
}
|
}
|
||||||
|
|
||||||
drawDebugMenuBar();
|
drawDebugMenuBar();
|
||||||
|
if (showTransformGizmo && !isPlayerViewActive()) {
|
||||||
|
drawSelectionGizmo();
|
||||||
|
}
|
||||||
if (showTestWindow) {
|
if (showTestWindow) {
|
||||||
drawTestWindow();
|
drawTestWindow();
|
||||||
}
|
}
|
||||||
@@ -129,6 +132,7 @@ void LightKeeper::drawDebugMenuBar()
|
|||||||
ImGui::MenuItem("Colliders", nullptr, &renderColliders);
|
ImGui::MenuItem("Colliders", nullptr, &renderColliders);
|
||||||
ImGui::MenuItem("2D Shapes", nullptr, &render2DShapes);
|
ImGui::MenuItem("2D Shapes", nullptr, &render2DShapes);
|
||||||
ImGui::MenuItem("Debug Lines", nullptr, &renderDebugLines);
|
ImGui::MenuItem("Debug Lines", nullptr, &renderDebugLines);
|
||||||
|
ImGui::MenuItem("Transform Gizmo", nullptr, &showTransformGizmo);
|
||||||
|
|
||||||
bool lightHeatmap = renderer.getRenderLightHeatmap();
|
bool lightHeatmap = renderer.getRenderLightHeatmap();
|
||||||
if (ImGui::MenuItem("Forward+ Light Heatmap", nullptr, &lightHeatmap)) {
|
if (ImGui::MenuItem("Forward+ Light Heatmap", nullptr, &lightHeatmap)) {
|
||||||
@@ -163,6 +167,18 @@ void LightKeeper::drawDebugMenuBar()
|
|||||||
ImGui::EndMainMenuBar();
|
ImGui::EndMainMenuBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LightKeeper::drawSelectionGizmo()
|
||||||
|
{
|
||||||
|
GameObject* selected = SceneManager::GetInstance().GetCurrentScene().GetSelectedObject();
|
||||||
|
if (selected == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
transformGizmo.HandleShortcuts();
|
||||||
|
transformGizmo.DrawToolbar();
|
||||||
|
transformGizmo.Manipulate(*selected, getViewCamera());
|
||||||
|
}
|
||||||
|
|
||||||
void LightKeeper::drawWindowMenu()
|
void LightKeeper::drawWindowMenu()
|
||||||
{
|
{
|
||||||
if (!ImGui::BeginMenu("Window")) {
|
if (!ImGui::BeginMenu("Window")) {
|
||||||
|
|||||||
@@ -34,9 +34,12 @@ FirstPersonController::FirstPersonController(GameObject& parent)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void FirstPersonController::SetMouseCaptured(bool captured)
|
void FirstPersonController::SetPossessed(bool possessed)
|
||||||
{
|
{
|
||||||
InputManager::GetInstance().SetMouseCaptured(captured);
|
m_Possessed = possessed;
|
||||||
|
m_JumpHeld = false;
|
||||||
|
m_JumpBufferTimer = 0.0f;
|
||||||
|
InputManager::GetInstance().SetMouseCaptured(possessed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FirstPersonController::Start()
|
void FirstPersonController::Start()
|
||||||
@@ -65,26 +68,16 @@ void FirstPersonController::Update(float dt)
|
|||||||
{
|
{
|
||||||
const auto& input = InputManager::GetInstance();
|
const auto& input = InputManager::GetInstance();
|
||||||
|
|
||||||
if (input.WasKeyPressed(SDL_SCANCODE_F5))
|
|
||||||
{
|
|
||||||
SetMouseCaptured(!IsMouseCaptured());
|
|
||||||
}
|
|
||||||
|
|
||||||
m_FocusedInteractable = nullptr;
|
m_FocusedInteractable = nullptr;
|
||||||
|
|
||||||
if (!IsMouseCaptured())
|
if (!m_Possessed)
|
||||||
{
|
{
|
||||||
m_JumpHeld = false;
|
|
||||||
if (input.WasMousePressed(SDL_BUTTON_LEFT) && !ImGui::GetIO().WantCaptureMouse)
|
|
||||||
{
|
|
||||||
SetMouseCaptured(true);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.WasKeyPressed(SDL_SCANCODE_ESCAPE))
|
if (input.WasKeyPressed(SDL_SCANCODE_ESCAPE))
|
||||||
{
|
{
|
||||||
SetMouseCaptured(false);
|
SetPossessed(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +136,7 @@ void FirstPersonController::FixedUpdate(float fixedDt)
|
|||||||
m_CoyoteTimer = m_Grounded ? m_CoyoteTime : std::max(m_CoyoteTimer - fixedDt, 0.0f);
|
m_CoyoteTimer = m_Grounded ? m_CoyoteTime : std::max(m_CoyoteTimer - fixedDt, 0.0f);
|
||||||
|
|
||||||
const auto& input = InputManager::GetInstance();
|
const auto& input = InputManager::GetInstance();
|
||||||
const bool sprinting = IsMouseCaptured() && input.IsKeyDown(SDL_SCANCODE_LSHIFT);
|
const bool sprinting = m_Possessed && input.IsKeyDown(SDL_SCANCODE_LSHIFT);
|
||||||
const glm::vec3 wishVelocity = GetWishDirection() * (sprinting ? m_SprintSpeed : m_WalkSpeed);
|
const glm::vec3 wishVelocity = GetWishDirection() * (sprinting ? m_SprintSpeed : m_WalkSpeed);
|
||||||
|
|
||||||
const float acceleration = m_Grounded ? m_GroundAcceleration : m_AirAcceleration;
|
const float acceleration = m_Grounded ? m_GroundAcceleration : m_AirAcceleration;
|
||||||
@@ -211,7 +204,7 @@ bool FirstPersonController::CheckGrounded() const
|
|||||||
|
|
||||||
glm::vec3 FirstPersonController::GetWishDirection() const
|
glm::vec3 FirstPersonController::GetWishDirection() const
|
||||||
{
|
{
|
||||||
if (!IsMouseCaptured())
|
if (!m_Possessed)
|
||||||
return glm::vec3{0.0f};
|
return glm::vec3{0.0f};
|
||||||
|
|
||||||
const auto& input = InputManager::GetInstance();
|
const auto& input = InputManager::GetInstance();
|
||||||
@@ -255,10 +248,8 @@ void FirstPersonController::ImGuiInspector()
|
|||||||
ImGui::Text("Grounded: %s", m_Grounded ? "yes" : "no");
|
ImGui::Text("Grounded: %s", m_Grounded ? "yes" : "no");
|
||||||
ImGui::Text("Yaw: %.1f deg", glm::degrees(m_Yaw));
|
ImGui::Text("Yaw: %.1f deg", glm::degrees(m_Yaw));
|
||||||
ImGui::Text("Pitch: %.1f deg", glm::degrees(m_Pitch));
|
ImGui::Text("Pitch: %.1f deg", glm::degrees(m_Pitch));
|
||||||
bool captured = IsMouseCaptured();
|
if (bool possessed = m_Possessed; ImGui::Checkbox("Possessed", &possessed)) {
|
||||||
ImGui::Checkbox("Mouse Captured", &captured);
|
SetPossessed(possessed);
|
||||||
if (captured != IsMouseCaptured()) {
|
|
||||||
SetMouseCaptured(captured);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,14 +33,18 @@ InteractableButton::InteractableButton(GameObject& parent)
|
|||||||
|
|
||||||
void InteractableButton::Start()
|
void InteractableButton::Start()
|
||||||
{
|
{
|
||||||
ApplyColor();
|
if (m_CycleColors) {
|
||||||
|
ApplyColor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InteractableButton::Interact(GameObject& interactor)
|
void InteractableButton::Interact(GameObject& interactor)
|
||||||
{
|
{
|
||||||
m_ColorIndex = (m_ColorIndex + 1) % ButtonColors.size();
|
|
||||||
++m_PressCount;
|
++m_PressCount;
|
||||||
ApplyColor();
|
if (m_CycleColors) {
|
||||||
|
m_ColorIndex = (m_ColorIndex + 1) % ButtonColors.size();
|
||||||
|
ApplyColor();
|
||||||
|
}
|
||||||
|
|
||||||
spdlog::info("{} pressed {} by {}", GetGameObject()->GetName(), m_PressCount, interactor.GetName());
|
spdlog::info("{} pressed {} by {}", GetGameObject()->GetName(), m_PressCount, interactor.GetName());
|
||||||
ActivateTargets(interactor);
|
ActivateTargets(interactor);
|
||||||
@@ -107,6 +111,7 @@ nlohmann::json InteractableButton::Serialize() const
|
|||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
{"colorIndex", m_ColorIndex},
|
{"colorIndex", m_ColorIndex},
|
||||||
|
{"cycleColors", m_CycleColors},
|
||||||
{"pressCount", m_PressCount},
|
{"pressCount", m_PressCount},
|
||||||
{"targets", m_Targets},
|
{"targets", m_Targets},
|
||||||
};
|
};
|
||||||
@@ -115,6 +120,7 @@ nlohmann::json InteractableButton::Serialize() const
|
|||||||
void InteractableButton::Deserialize(const nlohmann::json& data)
|
void InteractableButton::Deserialize(const nlohmann::json& data)
|
||||||
{
|
{
|
||||||
m_ColorIndex = data.value("colorIndex", m_ColorIndex) % ButtonColors.size();
|
m_ColorIndex = data.value("colorIndex", m_ColorIndex) % ButtonColors.size();
|
||||||
|
m_CycleColors = data.value("cycleColors", m_CycleColors);
|
||||||
m_PressCount = data.value("pressCount", m_PressCount);
|
m_PressCount = data.value("pressCount", m_PressCount);
|
||||||
m_Targets = data.value("targets", std::vector<ObjectId>{});
|
m_Targets = data.value("targets", std::vector<ObjectId>{});
|
||||||
}
|
}
|
||||||
@@ -125,6 +131,9 @@ void InteractableButton::ImGuiInspector()
|
|||||||
if (ImGui::Button("Press")) {
|
if (ImGui::Button("Press")) {
|
||||||
Interact(*GetGameObject());
|
Interact(*GetGameObject());
|
||||||
}
|
}
|
||||||
|
if (ImGui::Checkbox("Cycle Colors", &m_CycleColors) && m_CycleColors) {
|
||||||
|
ApplyColor();
|
||||||
|
}
|
||||||
|
|
||||||
DrawTargetsInspector();
|
DrawTargetsInspector();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#include <components/LightToggle.h>
|
||||||
|
|
||||||
|
#include <destrum/Components/LightComponent.h>
|
||||||
|
#include <destrum/Components/MeshRendererComponent.h>
|
||||||
|
#include <destrum/Graphics/Renderer.h>
|
||||||
|
#include <destrum/ObjectModel/GameObject.h>
|
||||||
|
#include <destrum/Util/GameState.h>
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
LightToggle::LightToggle(GameObject& parent)
|
||||||
|
: Activatable(parent, "LightToggle")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightToggle::Start()
|
||||||
|
{
|
||||||
|
Apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightToggle::SetOn(bool on)
|
||||||
|
{
|
||||||
|
m_On = on;
|
||||||
|
Apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightToggle::OnActivate(GameObject&)
|
||||||
|
{
|
||||||
|
SetOn(!m_On);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightToggle::Apply()
|
||||||
|
{
|
||||||
|
GameObject* object = GetGameObject();
|
||||||
|
auto* light = object->GetComponent<LightComponent>();
|
||||||
|
if (light != nullptr) {
|
||||||
|
light->SetEnabled(m_On);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The bulb glows in the light's colour while it is on
|
||||||
|
const auto* meshRenderer = object->GetComponent<MeshRendererComponent>();
|
||||||
|
if (meshRenderer == nullptr || meshRenderer->GetMaterialID() == NULL_MATERIAL_ID ||
|
||||||
|
!GameState::GetInstance().HasRenderer()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameRenderer& renderer = GameState::GetInstance().Renderer();
|
||||||
|
Material& material = renderer.getMaterialMutable(meshRenderer->GetMaterialID());
|
||||||
|
material.emissiveColor = m_On && light != nullptr ? light->GetColor() : glm::vec3{0.0f};
|
||||||
|
renderer.updateMaterialGPU(meshRenderer->GetMaterialID());
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json LightToggle::Serialize() const
|
||||||
|
{
|
||||||
|
return {{"on", m_On}};
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightToggle::Deserialize(const nlohmann::json& data)
|
||||||
|
{
|
||||||
|
m_On = data.value("on", m_On);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightToggle::ImGuiInspector()
|
||||||
|
{
|
||||||
|
if (bool on = m_On; ImGui::Checkbox("On", &on)) {
|
||||||
|
SetOn(on);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <components/FirstPersonController.h>
|
#include <components/FirstPersonController.h>
|
||||||
#include <components/Door.h>
|
#include <components/Door.h>
|
||||||
#include <components/InteractableButton.h>
|
#include <components/InteractableButton.h>
|
||||||
|
#include <components/LightToggle.h>
|
||||||
#include <components/TriggerLoggerComponent.h>
|
#include <components/TriggerLoggerComponent.h>
|
||||||
|
|
||||||
#include <glm/gtc/constants.hpp>
|
#include <glm/gtc/constants.hpp>
|
||||||
@@ -26,11 +27,13 @@
|
|||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// The visible ground plane is at y = -1
|
// The visible ground plane is at y = -1
|
||||||
constexpr float GroundHeight = -1.0f;
|
constexpr float GroundHeight = -1.0f;
|
||||||
|
constexpr float GroundHalfExtent = 25.0f;
|
||||||
|
|
||||||
ModelDoc::LoadOptions StaticModelOptions()
|
ModelDoc::LoadOptions StaticModelOptions()
|
||||||
{
|
{
|
||||||
@@ -80,12 +83,12 @@ namespace {
|
|||||||
planeMeshComp->SetMeshID(resources.meshes().addMesh(gfxDevice, planePrimitive.mesh));
|
planeMeshComp->SetMeshID(resources.meshes().addMesh(gfxDevice, planePrimitive.mesh));
|
||||||
planeMeshComp->SetMaterialID(planeMaterialID);
|
planeMeshComp->SetMaterialID(planeMaterialID);
|
||||||
planeObj->GetTransform().SetWorldPosition(glm::vec3(0.f, GroundHeight, 0.f));
|
planeObj->GetTransform().SetWorldPosition(glm::vec3(0.f, GroundHeight, 0.f));
|
||||||
planeObj->GetTransform().SetWorldScale(glm::vec3(10.f, 1.f, 10.f));
|
planeObj->GetTransform().SetWorldScale(glm::vec3(GroundHalfExtent, 1.f, GroundHalfExtent));
|
||||||
|
|
||||||
// The plane mesh is flat, so the floor collider sits right below it
|
// The plane mesh is flat, so the floor collider sits right below it
|
||||||
GameObject* groundObj = scene.CreateGameObject("GroundCollider");
|
GameObject* groundObj = scene.CreateGameObject("GroundCollider");
|
||||||
groundObj->GetTransform().SetWorldPosition(glm::vec3(0.f, GroundHeight - 0.5f, 0.f));
|
groundObj->GetTransform().SetWorldPosition(glm::vec3(0.f, GroundHeight - 0.5f, 0.f));
|
||||||
groundObj->AddComponent<BoxCollider>(glm::vec3{10.0f, 0.5f, 10.0f});
|
groundObj->AddComponent<BoxCollider>(glm::vec3{GroundHalfExtent, 0.5f, GroundHalfExtent});
|
||||||
groundObj->AddComponent<Rigidbody>()->SetType(RigidbodyType::Static);
|
groundObj->AddComponent<Rigidbody>()->SetType(RigidbodyType::Static);
|
||||||
scene.GetPhysics().RegisterGameObject(*groundObj);
|
scene.GetPhysics().RegisterGameObject(*groundObj);
|
||||||
}
|
}
|
||||||
@@ -201,6 +204,124 @@ namespace {
|
|||||||
button->AddComponent<InteractableButton>()->AddTarget(target);
|
button->AddComponent<InteractableButton>()->AddTarget(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct WallOpening {
|
||||||
|
float offset;
|
||||||
|
float width;
|
||||||
|
float bottom;
|
||||||
|
float top;
|
||||||
|
};
|
||||||
|
|
||||||
|
void CreateWall(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources, const std::string& name,
|
||||||
|
const glm::vec3& floorCenter, float length, float height, bool alongX, MaterialID materialID,
|
||||||
|
const std::optional<WallOpening>& opening = std::nullopt)
|
||||||
|
{
|
||||||
|
constexpr float thickness = 0.3f;
|
||||||
|
|
||||||
|
const auto createPiece = [&](const std::string& suffix, float from, float to, float bottom, float top)
|
||||||
|
{
|
||||||
|
if (to <= from || top <= bottom) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const float along = (from + to) * 0.5f;
|
||||||
|
const glm::vec3 alongOffset = alongX ? glm::vec3{along, 0.0f, 0.0f} : glm::vec3{0.0f, 0.0f, along};
|
||||||
|
const glm::vec3 center = floorCenter + alongOffset + glm::vec3{0.0f, (bottom + top) * 0.5f, 0.0f};
|
||||||
|
const glm::vec3 size = alongX
|
||||||
|
? glm::vec3{to - from, top - bottom, thickness}
|
||||||
|
: glm::vec3{thickness, top - bottom, to - from};
|
||||||
|
CreateBox(scene, gfxDevice, resources, name + suffix, center, size, materialID, RigidbodyType::Static);
|
||||||
|
};
|
||||||
|
|
||||||
|
const float halfLength = length * 0.5f;
|
||||||
|
if (!opening) {
|
||||||
|
createPiece("", -halfLength, halfLength, 0.0f, height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float openingStart = opening->offset - opening->width * 0.5f;
|
||||||
|
const float openingEnd = opening->offset + opening->width * 0.5f;
|
||||||
|
createPiece("_Left", -halfLength, openingStart, 0.0f, height);
|
||||||
|
createPiece("_Right", openingEnd, halfLength, 0.0f, height);
|
||||||
|
createPiece("_Below", openingStart, openingEnd, 0.0f, opening->bottom);
|
||||||
|
createPiece("_Above", openingStart, openingEnd, opening->top, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject* CreateCeilingLight(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources,
|
||||||
|
const glm::vec3& position)
|
||||||
|
{
|
||||||
|
GameObject* lightObj = scene.CreateGameObject("CeilingLight");
|
||||||
|
lightObj->GetTransform().SetWorldPosition(position);
|
||||||
|
|
||||||
|
// Every bulb needs its own material, the toggle changes its glow
|
||||||
|
Material bulbMaterial = Material::SimpleColor(glm::vec3{1.0f, 0.95f, 0.85f}, "BulbMaterial");
|
||||||
|
auto* bulbMesh = lightObj->AddComponent<MeshRendererComponent>();
|
||||||
|
bulbMesh->SetMeshID(resources.meshes().addMesh(gfxDevice, PrimitiveMeshes::MakeBox({0.35f, 0.12f, 0.35f})));
|
||||||
|
bulbMesh->SetMaterialID(resources.materials().addMaterial(std::move(bulbMaterial)));
|
||||||
|
|
||||||
|
auto* light = lightObj->AddComponent<LightComponent>(LightType::Point);
|
||||||
|
light->SetColor(glm::vec3{1.0f, 0.82f, 0.55f});
|
||||||
|
light->SetIntensity(4.0f);
|
||||||
|
light->SetRange(7.0f);
|
||||||
|
|
||||||
|
lightObj->AddComponent<LightToggle>();
|
||||||
|
return lightObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateHouse(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
||||||
|
{
|
||||||
|
const MaterialID wallMaterial = AddPrototypeMaterial(gfxDevice, resources, "Light", 0.8f);
|
||||||
|
const MaterialID floorMaterial = AddPrototypeMaterial(gfxDevice, resources, "Orange", 0.7f);
|
||||||
|
const MaterialID roofMaterial = AddPrototypeMaterial(gfxDevice, resources, "Red", 0.8f);
|
||||||
|
|
||||||
|
const glm::vec3 center{0.0f, GroundHeight, -16.0f};
|
||||||
|
constexpr float width = 8.0f;
|
||||||
|
constexpr float depth = 6.0f;
|
||||||
|
constexpr float wallHeight = 3.0f;
|
||||||
|
constexpr float doorOffset = -1.5f;
|
||||||
|
constexpr float doorWidth = 1.2f;
|
||||||
|
|
||||||
|
CreateBox(scene, gfxDevice, resources, "House_Floor", center + glm::vec3{0.0f, 0.03f, 0.0f},
|
||||||
|
{width, 0.06f, depth}, floorMaterial, RigidbodyType::Static);
|
||||||
|
|
||||||
|
CreateWall(scene, gfxDevice, resources, "House_FrontWall", center + glm::vec3{0.0f, 0.0f, depth * 0.5f},
|
||||||
|
width + 0.3f, wallHeight, true, wallMaterial,
|
||||||
|
WallOpening{.offset = doorOffset, .width = doorWidth, .bottom = 0.0f, .top = 2.2f});
|
||||||
|
CreateWall(scene, gfxDevice, resources, "House_BackWall", center - glm::vec3{0.0f, 0.0f, depth * 0.5f},
|
||||||
|
width + 0.3f, wallHeight, true, wallMaterial,
|
||||||
|
WallOpening{.offset = 1.5f, .width = 1.4f, .bottom = 1.0f, .top = 2.0f});
|
||||||
|
CreateWall(scene, gfxDevice, resources, "House_LeftWall", center - glm::vec3{width * 0.5f, 0.0f, 0.0f},
|
||||||
|
depth - 0.3f, wallHeight, false, wallMaterial);
|
||||||
|
CreateWall(scene, gfxDevice, resources, "House_RightWall", center + glm::vec3{width * 0.5f, 0.0f, 0.0f},
|
||||||
|
depth - 0.3f, wallHeight, false, wallMaterial,
|
||||||
|
WallOpening{.offset = 0.0f, .width = 1.6f, .bottom = 1.0f, .top = 2.0f});
|
||||||
|
|
||||||
|
CreateBox(scene, gfxDevice, resources, "House_Roof", center + glm::vec3{0.0f, wallHeight + 0.15f, 0.0f},
|
||||||
|
{width + 0.8f, 0.3f, depth + 0.8f}, roofMaterial, RigidbodyType::Static);
|
||||||
|
|
||||||
|
const GameObject* ceilingLight =
|
||||||
|
CreateCeilingLight(scene, gfxDevice, resources, center + glm::vec3{0.0f, wallHeight - 0.06f, 0.0f});
|
||||||
|
|
||||||
|
// Inside, next to the door
|
||||||
|
Material switchMaterial = Material::SimpleColor(glm::vec3{0.9f}, "LightSwitchMaterial");
|
||||||
|
switchMaterial.roughnessFactor = 0.5f;
|
||||||
|
const glm::vec3 switchPosition =
|
||||||
|
center + glm::vec3{doorOffset + doorWidth * 0.5f + 0.4f, 1.3f, depth * 0.5f - 0.15f - 0.03f};
|
||||||
|
GameObject* lightSwitch = CreateBox(scene, gfxDevice, resources, "LightSwitch", switchPosition,
|
||||||
|
{0.12f, 0.2f, 0.06f},
|
||||||
|
resources.materials().addMaterial(std::move(switchMaterial)),
|
||||||
|
RigidbodyType::Static);
|
||||||
|
auto* switchButton = lightSwitch->AddComponent<InteractableButton>();
|
||||||
|
switchButton->SetCycleColors(false);
|
||||||
|
switchButton->AddTarget(*ceilingLight);
|
||||||
|
|
||||||
|
GameObject* sofa = ModelSpawner::SpawnStaticModel(
|
||||||
|
scene, gfxDevice, resources, "game://sofa.glb", StaticModelOptions(),
|
||||||
|
"Sofa"
|
||||||
|
);
|
||||||
|
sofa->GetTransform().SetWorldPosition({-3.7, -1, -17.5});
|
||||||
|
sofa->GetTransform().SetWorldRotation({180, 0, 180});
|
||||||
|
sofa->GetTransform().SetWorldScale({3, 3, 3});
|
||||||
|
}
|
||||||
|
|
||||||
void CreateFlightHelmet(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
void CreateFlightHelmet(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
|
||||||
{
|
{
|
||||||
GameObject* helmetObj = ModelSpawner::SpawnStaticModel(
|
GameObject* helmetObj = ModelSpawner::SpawnStaticModel(
|
||||||
@@ -323,10 +444,11 @@ MainSceneObjects BuildMainScene(Scene& scene, GfxDevice& gfxDevice, RenderResour
|
|||||||
CreateParkourCourse(scene, gfxDevice, resources);
|
CreateParkourCourse(scene, gfxDevice, resources);
|
||||||
CreateFlightHelmet(scene, gfxDevice, resources);
|
CreateFlightHelmet(scene, gfxDevice, resources);
|
||||||
CreateTree(scene, gfxDevice, resources);
|
CreateTree(scene, gfxDevice, resources);
|
||||||
CreateCapybara(scene, gfxDevice, resources);
|
// CreateCapybara(scene, gfxDevice, resources);
|
||||||
CreateTriggerZone(scene);
|
CreateTriggerZone(scene);
|
||||||
const GameObject* door = CreateDoorway(scene, gfxDevice, resources);
|
const GameObject* door = CreateDoorway(scene, gfxDevice, resources);
|
||||||
CreateButton(scene, gfxDevice, resources, *door);
|
CreateButton(scene, gfxDevice, resources, *door);
|
||||||
|
CreateHouse(scene, gfxDevice, resources);
|
||||||
CreatePlayer(scene, objects);
|
CreatePlayer(scene, objects);
|
||||||
LoadSpawnBallResources(gfxDevice, resources, objects);
|
LoadSpawnBallResources(gfxDevice, resources, objects);
|
||||||
|
|
||||||
|
|||||||
@@ -453,6 +453,64 @@ namespace {
|
|||||||
SceneManager::GetInstance().Destroy();
|
SceneManager::GetInstance().Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TriggerCounter final : public Component {
|
||||||
|
public:
|
||||||
|
explicit TriggerCounter(GameObject& parent)
|
||||||
|
: Component(parent, "TriggerCounter") {}
|
||||||
|
|
||||||
|
std::string GetTypeName() const override { return "TriggerCounter"; }
|
||||||
|
void OnTriggerEnter(GameObject*) override { ++enters; }
|
||||||
|
void OnTriggerExit(GameObject*) override { ++exits; }
|
||||||
|
|
||||||
|
int enters{0};
|
||||||
|
int exits{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
void testNonPhysicsChangesKeepBodies()
|
||||||
|
{
|
||||||
|
Scene& scene = SceneManager::GetInstance().CreateScene("physics-refresh");
|
||||||
|
|
||||||
|
GameObject* trigger = scene.CreateGameObject("trigger");
|
||||||
|
trigger->AddComponent<BoxCollider>(glm::vec3{3.0f})->SetTrigger(true);
|
||||||
|
trigger->AddComponent<Rigidbody>()->SetType(RigidbodyType::Static);
|
||||||
|
auto* counter = trigger->AddComponent<TriggerCounter>();
|
||||||
|
|
||||||
|
GameObject* crate = scene.CreateGameObject("crate");
|
||||||
|
crate->AddComponent<BoxCollider>(glm::vec3{0.5f});
|
||||||
|
auto* crateBody = crate->AddComponent<Rigidbody>();
|
||||||
|
crateBody->SetUseGravity(false);
|
||||||
|
crateBody->SetAllowSleep(false);
|
||||||
|
|
||||||
|
GameObject* lamp = scene.CreateGameObject("lamp");
|
||||||
|
auto* light = lamp->AddComponent<LightComponent>();
|
||||||
|
|
||||||
|
scene.CommitPendingAdditions();
|
||||||
|
scene.GetPhysics().RefreshGameObject(*trigger);
|
||||||
|
scene.GetPhysics().RefreshGameObject(*crate);
|
||||||
|
|
||||||
|
const auto step = [&scene] {
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
scene.FixedUpdate(1.0f / 60.0f);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
step();
|
||||||
|
check(counter->enters == 1 && counter->exits == 0, "a body inside a trigger must enter it once");
|
||||||
|
|
||||||
|
light->SetEnabled(false);
|
||||||
|
light->SetEnabled(true);
|
||||||
|
crate->AddComponent<LightComponent>()->SetEnabled(false);
|
||||||
|
step();
|
||||||
|
check(counter->enters == 1 && counter->exits == 0,
|
||||||
|
"non physics components must not rebuild physics bodies");
|
||||||
|
|
||||||
|
crateBody->SetMass(5.0f);
|
||||||
|
step();
|
||||||
|
check(counter->enters == 2 && counter->exits == 1,
|
||||||
|
"rebuilding a body inside a trigger must exit the old body and enter the new one");
|
||||||
|
|
||||||
|
SceneManager::GetInstance().Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
void testRenderer2DShapes()
|
void testRenderer2DShapes()
|
||||||
{
|
{
|
||||||
auto& r2d = Renderer2D::GetInstance();
|
auto& r2d = Renderer2D::GetInstance();
|
||||||
@@ -1230,6 +1288,7 @@ int main()
|
|||||||
testPhysicsWorldUnits();
|
testPhysicsWorldUnits();
|
||||||
testJoltSphereCollisions();
|
testJoltSphereCollisions();
|
||||||
testSceneJoltSphereCollisions();
|
testSceneJoltSphereCollisions();
|
||||||
|
testNonPhysicsChangesKeepBodies();
|
||||||
testAnimatorAssetReferences();
|
testAnimatorAssetReferences();
|
||||||
testLightComponentRoundTrip();
|
testLightComponentRoundTrip();
|
||||||
testModelFileRoundTrip();
|
testModelFileRoundTrip();
|
||||||
|
|||||||
Reference in New Issue
Block a user