From 5f7784ff1451d26ab7c4ef2541c541c1aa496075 Mon Sep 17 00:00:00 2001 From: Panos Karabelas Date: Wed, 30 Nov 2022 23:45:34 +0000 Subject: [PATCH] [Editor] Added a fully functional transformation gizmo (position, rotation, scale) --- editor/Editor.cpp | 1 + editor/ImGui/ImGuiExtension.cpp | 12 +- editor/ImGui/ImGuiExtension.h | 45 +- .../Implementation/ImGui_TransformGizmo.h | 120 + editor/ImGui/Source/ImGuizmo/GraphEditor.cpp | 1111 ++++++ editor/ImGui/Source/ImGuizmo/GraphEditor.h | 149 + editor/ImGui/Source/ImGuizmo/ImCurveEdit.cpp | 457 +++ editor/ImGui/Source/ImGuizmo/ImCurveEdit.h | 80 + editor/ImGui/Source/ImGuizmo/ImGradient.cpp | 116 + editor/ImGui/Source/ImGuizmo/ImGradient.h | 44 + editor/ImGui/Source/ImGuizmo/ImGuizmo.cpp | 2975 +++++++++++++++++ editor/ImGui/Source/ImGuizmo/ImGuizmo.h | 264 ++ editor/ImGui/Source/ImGuizmo/ImSequencer.cpp | 695 ++++ editor/ImGui/Source/ImGuizmo/ImSequencer.h | 77 + editor/ImGui/Source/ImGuizmo/ImZoomSlider.h | 245 ++ editor/ImGui/Source/ImGuizmo/LICENSE | 21 + editor/ImGui/Source/ImGuizmo/README.md | 192 ++ editor/Widgets/RenderOptions.cpp | 8 +- editor/Widgets/Viewport.cpp | 30 +- editor/Widgets/WorldViewer.cpp | 65 +- editor/Widgets/WorldViewer.h | 2 +- runtime/Rendering/Renderer.h | 1 - runtime/Rendering/Renderer_Passes.cpp | 104 +- runtime/Rendering/Renderer_Primitives.cpp | 86 +- runtime/World/Components/Camera.cpp | 32 +- runtime/World/Components/Camera.h | 8 +- .../World/TransformHandle/TransformEnums.h | 36 - .../World/TransformHandle/TransformHandle.cpp | 150 - .../World/TransformHandle/TransformHandle.h | 72 - .../TransformHandle/TransformOperator.cpp | 237 -- .../World/TransformHandle/TransformOperator.h | 88 - .../TransformHandle/TransformOperatorAxis.cpp | 89 - .../TransformHandle/TransformOperatorAxis.h | 66 - .../TransformHandle/TransformPosition.cpp | 137 - .../World/TransformHandle/TransformPosition.h | 45 - .../TransformHandle/TransformRotation.cpp | 123 - .../World/TransformHandle/TransformRotation.h | 47 - .../World/TransformHandle/TransformScale.cpp | 141 - .../World/TransformHandle/TransformScale.h | 45 - runtime/World/World.cpp | 14 - runtime/World/World.h | 6 - 41 files changed, 6690 insertions(+), 1546 deletions(-) create mode 100644 editor/ImGui/Implementation/ImGui_TransformGizmo.h create mode 100644 editor/ImGui/Source/ImGuizmo/GraphEditor.cpp create mode 100644 editor/ImGui/Source/ImGuizmo/GraphEditor.h create mode 100644 editor/ImGui/Source/ImGuizmo/ImCurveEdit.cpp create mode 100644 editor/ImGui/Source/ImGuizmo/ImCurveEdit.h create mode 100644 editor/ImGui/Source/ImGuizmo/ImGradient.cpp create mode 100644 editor/ImGui/Source/ImGuizmo/ImGradient.h create mode 100644 editor/ImGui/Source/ImGuizmo/ImGuizmo.cpp create mode 100644 editor/ImGui/Source/ImGuizmo/ImGuizmo.h create mode 100644 editor/ImGui/Source/ImGuizmo/ImSequencer.cpp create mode 100644 editor/ImGui/Source/ImGuizmo/ImSequencer.h create mode 100644 editor/ImGui/Source/ImGuizmo/ImZoomSlider.h create mode 100644 editor/ImGui/Source/ImGuizmo/LICENSE create mode 100644 editor/ImGui/Source/ImGuizmo/README.md delete mode 100644 runtime/World/TransformHandle/TransformEnums.h delete mode 100644 runtime/World/TransformHandle/TransformHandle.cpp delete mode 100644 runtime/World/TransformHandle/TransformHandle.h delete mode 100644 runtime/World/TransformHandle/TransformOperator.cpp delete mode 100644 runtime/World/TransformHandle/TransformOperator.h delete mode 100644 runtime/World/TransformHandle/TransformOperatorAxis.cpp delete mode 100644 runtime/World/TransformHandle/TransformOperatorAxis.h delete mode 100644 runtime/World/TransformHandle/TransformPosition.cpp delete mode 100644 runtime/World/TransformHandle/TransformPosition.h delete mode 100644 runtime/World/TransformHandle/TransformRotation.cpp delete mode 100644 runtime/World/TransformHandle/TransformRotation.h delete mode 100644 runtime/World/TransformHandle/TransformScale.cpp delete mode 100644 runtime/World/TransformHandle/TransformScale.h diff --git a/editor/Editor.cpp b/editor/Editor.cpp index f6d63c68d..6f5a58982 100644 --- a/editor/Editor.cpp +++ b/editor/Editor.cpp @@ -41,6 +41,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "Widgets/Profiler.h" #include "Widgets/RenderOptions.h" #include "Widgets/TextureViewer.h" +#include "Rendering/Renderer_Definitions.h" //============================================== //= NAMESPACES ===== diff --git a/editor/ImGui/ImGuiExtension.cpp b/editor/ImGui/ImGuiExtension.cpp index d820487cc..b4103696f 100644 --- a/editor/ImGui/ImGuiExtension.cpp +++ b/editor/ImGui/ImGuiExtension.cpp @@ -29,10 +29,8 @@ using namespace std; using namespace Spartan; //====================== -Editor* EditorHelper::editor = nullptr; -Context* EditorHelper::context = nullptr; -World* EditorHelper::world = nullptr; -Renderer* EditorHelper::renderer = nullptr; -Input* EditorHelper::input = nullptr; -function EditorHelper::on_entity_selected = nullptr; -weak_ptr EditorHelper::selected_entity; +Editor* EditorHelper::editor = nullptr; +Context* EditorHelper::context = nullptr; +World* EditorHelper::world = nullptr; +Renderer* EditorHelper::renderer = nullptr; +Input* EditorHelper::input = nullptr; diff --git a/editor/ImGui/ImGuiExtension.h b/editor/ImGui/ImGuiExtension.h index 5e5b6f9b5..ea02a73b5 100644 --- a/editor/ImGui/ImGuiExtension.h +++ b/editor/ImGui/ImGuiExtension.h @@ -21,7 +21,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #pragma once -//= INCLUDES ===================================== +//= INCLUDES =============================== #include #include #include @@ -33,12 +33,11 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "Input/Input.h" #include "World/World.h" #include "World/Components/Camera.h" -#include "World/TransformHandle/TransformHandle.h" #include "Display/Display.h" #include "../WidgetsDeferred/IconProvider.h" #include "Source/imgui_internal.h" #include "../Editor.h" -//================================================ +//========================================== class EditorHelper { @@ -82,41 +81,11 @@ class EditorHelper }); } - static void PickEntity() - { - // If the transform handle hasn't finished editing don't do anything. - if (world->GetTransformHandle()->IsEditing()) - return; - - // Get camera - std::shared_ptr camera = renderer->GetCamera(); - if (!camera) - return; - - // Pick the world - std::shared_ptr entity; - camera->Pick(entity); - - // Set the transform handle to the selected entity - SetSelectedEntity(entity); - - // Fire callback - on_entity_selected(); - } - - static void SetSelectedEntity(const std::shared_ptr& entity) - { - // keep returned entity instead as the transform handle can decide to reject it - selected_entity = world->GetTransformHandle()->SetSelectedEntity(entity); - } - - static Editor* editor; - static Spartan::Context* context; - static Spartan::World* world; - static Spartan::Renderer* renderer; - static Spartan::Input* input; - static std::function on_entity_selected; - static std::weak_ptr selected_entity; + static Editor* editor; + static Spartan::Context* context; + static Spartan::World* world; + static Spartan::Renderer* renderer; + static Spartan::Input* input; }; namespace ImGui_SP diff --git a/editor/ImGui/Implementation/ImGui_TransformGizmo.h b/editor/ImGui/Implementation/ImGui_TransformGizmo.h new file mode 100644 index 000000000..57f15ae1b --- /dev/null +++ b/editor/ImGui/Implementation/ImGui_TransformGizmo.h @@ -0,0 +1,120 @@ +/* +Copyright(c) 2016-2022 Panos Karabelas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#pragma once + +//= INCLUDES =========================== +#include "../Source/ImGuizmo/ImGuizmo.h" +#include "../Source/imgui.h" +#include "World/Components/Transform.h" +#include "World/Entity.h" +#include "Context.h" +#include "Rendering/Renderer.h" +#include "Input/Input.h" +//====================================== + +namespace ImGui::TransformGizmo +{ + static void apply_style() + { + ImGuizmo::Style& style = ImGuizmo::GetStyle(); + style.TranslationLineThickness = 6.0f; + style.TranslationLineArrowSize = 10.0f; + style.RotationLineThickness = 4.0f; + style.RotationOuterLineThickness = 5.0f; + style.ScaleLineThickness = 6.0f; + style.ScaleLineCircleSize = 6.0f; + style.HatchedAxisLineThickness = 6.0f; + style.CenterCircleSize = 6.0f; + } + + static void tick(Spartan::Context* context) + { + std::shared_ptr camera = context->GetSystem()->GetCamera(); + if (!camera) + return; + + // Get selected entity + std::shared_ptr entity = camera->GetSelectedEntity(); + + // Enable/disable gizmo + ImGuizmo::Enable(entity != nullptr); + if (!entity) + return; + + // Switch between position, rotation and scale operations, with W, E and R respectively + static ImGuizmo::OPERATION transform_operation = ImGuizmo::TRANSLATE; + if (!camera->IsControledInFirstPerson()) + { + if (context->GetSystem()->GetKeyDown(Spartan::KeyCode::W)) + { + transform_operation = ImGuizmo::TRANSLATE; + } + else if (context->GetSystem()->GetKeyDown(Spartan::KeyCode::E)) + { + transform_operation = ImGuizmo::ROTATE; + } + else if (context->GetSystem()->GetKeyDown(Spartan::KeyCode::R)) + { + transform_operation = ImGuizmo::SCALE; + } + } + + ImGuizmo::MODE transform_space = ImGuizmo::WORLD; + + // Get some data + const Spartan::Math::Matrix& matrix_projection = camera->GetProjectionMatrix().Transposed(); + const Spartan::Math::Matrix& matrix_view = camera->GetViewMatrix().Transposed(); + Spartan::Transform* transform = entity->GetComponent(); + + // Begin + const bool is_orthographic = false; + ImGuizmo::SetOrthographic(is_orthographic); + ImGuizmo::BeginFrame(); + + // Map transform to ImGuizmo + float matrix_delta[16]; + float translation[3] = { transform->GetPosition().x, transform->GetPosition().y, transform->GetPosition().z }; + float rotation[3] = { transform->GetRotation().ToEulerAngles().x, transform->GetRotation().ToEulerAngles().y, transform->GetRotation().ToEulerAngles().z }; + float scale[3] = { transform->GetScale().x, transform->GetScale().y, transform->GetScale().z }; + ImGuizmo::RecomposeMatrixFromComponents(&translation[0], rotation, scale, &matrix_delta[0]); + + // Set viewport rectangle + ImGuizmo::SetDrawlist(); + ImGuizmo::SetRect(ImGui::GetWindowPos().x, ImGui::GetWindowPos().y, ImGui::GetWindowWidth(), ImGui::GetWindowHeight()); + ImGuizmo::Manipulate(&matrix_view.m00, &matrix_projection.m00, transform_operation, transform_space, &matrix_delta[0], 0, 0); + + // Map ImGuizmo to transform + if (ImGuizmo::IsUsing()) + { + ImGuizmo::DecomposeMatrixToComponents(&matrix_delta[0], translation, rotation, scale); + + transform->SetPosition(Spartan::Math::Vector3(translation[0], translation[1], translation[2])); + transform->SetRotation(Spartan::Math::Quaternion::FromEulerAngles(rotation[0], rotation[1], rotation[2])); + transform->SetScale(Spartan::Math::Vector3(scale[0], scale[1], scale[2])); + } + } + + static bool allow_picking() + { + return !ImGuizmo::IsOver() && !ImGuizmo::IsUsing(); + } +} diff --git a/editor/ImGui/Source/ImGuizmo/GraphEditor.cpp b/editor/ImGui/Source/ImGuizmo/GraphEditor.cpp new file mode 100644 index 000000000..1f8d95b41 --- /dev/null +++ b/editor/ImGui/Source/ImGuizmo/GraphEditor.cpp @@ -0,0 +1,1111 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#include "../../Source/imgui.h" +#define IMGUI_DEFINE_MATH_OPERATORS +#include "../../Source/imgui_internal.h" +#include +#include +#include +#include +#include "GraphEditor.h" + +namespace GraphEditor { + +static inline float Distance(const ImVec2& a, const ImVec2& b) +{ + return sqrtf((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); +} + +static inline float sign(float v) +{ + return (v >= 0.f) ? 1.f : -1.f; +} + +static ImVec2 GetInputSlotPos(Delegate& delegate, const Node& node, SlotIndex slotIndex, float factor) +{ + ImVec2 Size = node.mRect.GetSize() * factor; + size_t InputsCount = delegate.GetTemplate(node.mTemplateIndex).mInputCount; + return ImVec2(node.mRect.Min.x * factor, + node.mRect.Min.y * factor + Size.y * ((float)slotIndex + 1) / ((float)InputsCount + 1) + 8.f); +} + +static ImVec2 GetOutputSlotPos(Delegate& delegate, const Node& node, SlotIndex slotIndex, float factor) +{ + ImVec2 Size = node.mRect.GetSize() * factor; + size_t OutputsCount = delegate.GetTemplate(node.mTemplateIndex).mOutputCount; + return ImVec2(node.mRect.Min.x * factor + Size.x, + node.mRect.Min.y * factor + Size.y * ((float)slotIndex + 1) / ((float)OutputsCount + 1) + 8.f); +} + +static ImRect GetNodeRect(const Node& node, float factor) +{ + ImVec2 Size = node.mRect.GetSize() * factor; + return ImRect(node.mRect.Min * factor, node.mRect.Min * factor + Size); +} + +static ImVec2 editingNodeSource; +static bool editingInput = false; +static ImVec2 captureOffset; + +enum NodeOperation +{ + NO_None, + NO_EditingLink, + NO_QuadSelecting, + NO_MovingNodes, + NO_EditInput, + NO_PanView, +}; +static NodeOperation nodeOperation = NO_None; + +static void HandleZoomScroll(ImRect regionRect, ViewState& viewState, const Options& options) +{ + ImGuiIO& io = ImGui::GetIO(); + + if (regionRect.Contains(io.MousePos)) + { + if (io.MouseWheel < -FLT_EPSILON) + { + viewState.mFactorTarget *= 1.f - options.mZoomRatio; + } + + if (io.MouseWheel > FLT_EPSILON) + { + viewState.mFactorTarget *= 1.0f + options.mZoomRatio; + } + } + + ImVec2 mouseWPosPre = (io.MousePos - ImGui::GetCursorScreenPos()) / viewState.mFactor; + viewState.mFactorTarget = ImClamp(viewState.mFactorTarget, options.mMinZoom, options.mMaxZoom); + viewState.mFactor = ImLerp(viewState.mFactor, viewState.mFactorTarget, options.mZoomLerpFactor); + ImVec2 mouseWPosPost = (io.MousePos - ImGui::GetCursorScreenPos()) / viewState.mFactor; + if (ImGui::IsMousePosValid()) + { + viewState.mPosition += mouseWPosPost - mouseWPosPre; + } +} + +void GraphEditorClear() +{ + nodeOperation = NO_None; +} + +static void FitNodes(Delegate& delegate, ViewState& viewState, const ImVec2 viewSize, bool selectedNodesOnly) +{ + const size_t nodeCount = delegate.GetNodeCount(); + + if (!nodeCount) + { + return; + } + + bool validNode = false; + ImVec2 min(FLT_MAX, FLT_MAX); + ImVec2 max(-FLT_MAX, -FLT_MAX); + for (NodeIndex nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) + { + const Node& node = delegate.GetNode(nodeIndex); + + if (selectedNodesOnly && !node.mSelected) + { + continue; + } + + min = ImMin(min, node.mRect.Min); + min = ImMin(min, node.mRect.Max); + max = ImMax(max, node.mRect.Min); + max = ImMax(max, node.mRect.Max); + validNode = true; + } + + if (!validNode) + { + return; + } + + min -= viewSize * 0.05f; + max += viewSize * 0.05f; + ImVec2 nodesSize = max - min; + ImVec2 nodeCenter = (max + min) * 0.5f; + + float ratioY = viewSize.y / nodesSize.y; + float ratioX = viewSize.x / nodesSize.x; + + viewState.mFactor = viewState.mFactorTarget = ImMin(ImMin(ratioY, ratioX), 1.f); + viewState.mPosition = ImVec2(-nodeCenter.x, -nodeCenter.y) + (viewSize * 0.5f) / viewState.mFactorTarget; +} + +static void DisplayLinks(Delegate& delegate, + ImDrawList* drawList, + const ImVec2 offset, + const float factor, + const ImRect regionRect, + NodeIndex hoveredNode, + const Options& options) +{ + const size_t linkCount = delegate.GetLinkCount(); + for (LinkIndex linkIndex = 0; linkIndex < linkCount; linkIndex++) + { + const auto link = delegate.GetLink(linkIndex); + const auto nodeInput = delegate.GetNode(link.mInputNodeIndex); + const auto nodeOutput = delegate.GetNode(link.mOutputNodeIndex); + ImVec2 p1 = offset + GetOutputSlotPos(delegate, nodeInput, link.mInputSlotIndex, factor); + ImVec2 p2 = offset + GetInputSlotPos(delegate, nodeOutput, link.mOutputSlotIndex, factor); + + // con. view clipping + if ((p1.y < 0.f && p2.y < 0.f) || (p1.y > regionRect.Max.y && p2.y > regionRect.Max.y) || + (p1.x < 0.f && p2.x < 0.f) || (p1.x > regionRect.Max.x && p2.x > regionRect.Max.x)) + continue; + + bool highlightCons = hoveredNode == link.mInputNodeIndex || hoveredNode == link.mOutputNodeIndex; + uint32_t col = delegate.GetTemplate(nodeInput.mTemplateIndex).mHeaderColor | (highlightCons ? 0xF0F0F0 : 0); + if (options.mDisplayLinksAsCurves) + { + // curves + drawList->AddBezierCubic(p1, p1 + ImVec2(50, 0) * factor, p2 + ImVec2(-50, 0) * factor, p2, 0xFF000000, options.mLineThickness * 1.5f * factor); + drawList->AddBezierCubic(p1, p1 + ImVec2(50, 0) * factor, p2 + ImVec2(-50, 0) * factor, p2, col, options.mLineThickness * 1.5f * factor); + /* + ImVec2 p10 = p1 + ImVec2(20.f * factor, 0.f); + ImVec2 p20 = p2 - ImVec2(20.f * factor, 0.f); + + ImVec2 dif = p20 - p10; + ImVec2 p1a, p1b; + if (fabsf(dif.x) > fabsf(dif.y)) + { + p1a = p10 + ImVec2(fabsf(fabsf(dif.x) - fabsf(dif.y)) * 0.5 * sign(dif.x), 0.f); + p1b = p1a + ImVec2(fabsf(dif.y) * sign(dif.x) , dif.y); + } + else + { + p1a = p10 + ImVec2(0.f, fabsf(fabsf(dif.y) - fabsf(dif.x)) * 0.5 * sign(dif.y)); + p1b = p1a + ImVec2(dif.x, fabsf(dif.x) * sign(dif.y)); + } + drawList->AddLine(p1, p10, col, 3.f * factor); + drawList->AddLine(p10, p1a, col, 3.f * factor); + drawList->AddLine(p1a, p1b, col, 3.f * factor); + drawList->AddLine(p1b, p20, col, 3.f * factor); + drawList->AddLine(p20, p2, col, 3.f * factor); + */ + } + else + { + // straight lines + std::array pts; + int ptCount = 0; + ImVec2 dif = p2 - p1; + + ImVec2 p1a, p1b; + const float limitx = 12.f * factor; + if (dif.x < limitx) + { + ImVec2 p10 = p1 + ImVec2(limitx, 0.f); + ImVec2 p20 = p2 - ImVec2(limitx, 0.f); + + dif = p20 - p10; + p1a = p10 + ImVec2(0.f, dif.y * 0.5f); + p1b = p1a + ImVec2(dif.x, 0.f); + + pts = { p1, p10, p1a, p1b, p20, p2 }; + ptCount = 6; + } + else + { + if (fabsf(dif.y) < 1.f) + { + pts = { p1, (p1 + p2) * 0.5f, p2 }; + ptCount = 3; + } + else + { + if (fabsf(dif.y) < 10.f) + { + if (fabsf(dif.x) > fabsf(dif.y)) + { + p1a = p1 + ImVec2(fabsf(fabsf(dif.x) - fabsf(dif.y)) * 0.5f * sign(dif.x), 0.f); + p1b = p1a + ImVec2(fabsf(dif.y) * sign(dif.x), dif.y); + } + else + { + p1a = p1 + ImVec2(0.f, fabsf(fabsf(dif.y) - fabsf(dif.x)) * 0.5f * sign(dif.y)); + p1b = p1a + ImVec2(dif.x, fabsf(dif.x) * sign(dif.y)); + } + } + else + { + if (fabsf(dif.x) > fabsf(dif.y)) + { + float d = fabsf(dif.y) * sign(dif.x) * 0.5f; + p1a = p1 + ImVec2(d, dif.y * 0.5f); + p1b = p1a + ImVec2(fabsf(fabsf(dif.x) - fabsf(d) * 2.f) * sign(dif.x), 0.f); + } + else + { + float d = fabsf(dif.x) * sign(dif.y) * 0.5f; + p1a = p1 + ImVec2(dif.x * 0.5f, d); + p1b = p1a + ImVec2(0.f, fabsf(fabsf(dif.y) - fabsf(d) * 2.f) * sign(dif.y)); + } + } + pts = { p1, p1a, p1b, p2 }; + ptCount = 4; + } + } + float highLightFactor = factor * (highlightCons ? 2.0f : 1.f); + for (int pass = 0; pass < 2; pass++) + { + drawList->AddPolyline(pts.data(), ptCount, pass ? col : 0xFF000000, false, (pass ? options.mLineThickness : (options.mLineThickness * 1.5f)) * highLightFactor); + } + } + } +} + +static void HandleQuadSelection(Delegate& delegate, ImDrawList* drawList, const ImVec2 offset, const float factor, ImRect contentRect, const Options& options) +{ + if (!options.mAllowQuadSelection) + { + return; + } + ImGuiIO& io = ImGui::GetIO(); + static ImVec2 quadSelectPos; + //auto& nodes = delegate->GetNodes(); + auto nodeCount = delegate.GetNodeCount(); + + if (nodeOperation == NO_QuadSelecting && ImGui::IsWindowFocused()) + { + const ImVec2 bmin = ImMin(quadSelectPos, io.MousePos); + const ImVec2 bmax = ImMax(quadSelectPos, io.MousePos); + drawList->AddRectFilled(bmin, bmax, options.mQuadSelection, 1.f); + drawList->AddRect(bmin, bmax, options.mQuadSelectionBorder, 1.f); + if (!io.MouseDown[0]) + { + if (!io.KeyCtrl && !io.KeyShift) + { + for (size_t nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) + { + delegate.SelectNode(nodeIndex, false); + } + } + + nodeOperation = NO_None; + ImRect selectionRect(bmin, bmax); + for (int nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) + { + const auto node = delegate.GetNode(nodeIndex); + ImVec2 nodeRectangleMin = offset + node.mRect.Min * factor; + ImVec2 nodeRectangleMax = nodeRectangleMin + node.mRect.GetSize() * factor; + if (selectionRect.Overlaps(ImRect(nodeRectangleMin, nodeRectangleMax))) + { + if (io.KeyCtrl) + { + delegate.SelectNode(nodeIndex, false); + } + else + { + delegate.SelectNode(nodeIndex, true); + } + } + else + { + if (!io.KeyShift) + { + delegate.SelectNode(nodeIndex, false); + } + } + } + } + } + else if (nodeOperation == NO_None && io.MouseDown[0] && ImGui::IsWindowFocused() && + contentRect.Contains(io.MousePos)) + { + nodeOperation = NO_QuadSelecting; + quadSelectPos = io.MousePos; + } +} + +static bool HandleConnections(ImDrawList* drawList, + NodeIndex nodeIndex, + const ImVec2 offset, + const float factor, + Delegate& delegate, + const Options& options, + bool bDrawOnly, + SlotIndex& inputSlotOver, + SlotIndex& outputSlotOver, + const bool inMinimap) +{ + static NodeIndex editingNodeIndex; + static SlotIndex editingSlotIndex; + + ImGuiIO& io = ImGui::GetIO(); + const auto node = delegate.GetNode(nodeIndex); + const auto nodeTemplate = delegate.GetTemplate(node.mTemplateIndex); + const auto linkCount = delegate.GetLinkCount(); + + size_t InputsCount = nodeTemplate.mInputCount; + size_t OutputsCount = nodeTemplate.mOutputCount; + inputSlotOver = -1; + outputSlotOver = -1; + + // draw/use inputs/outputs + bool hoverSlot = false; + for (int i = 0; i < 2; i++) + { + float closestDistance = FLT_MAX; + SlotIndex closestConn = -1; + ImVec2 closestTextPos; + ImVec2 closestPos; + const size_t slotCount[2] = {InputsCount, OutputsCount}; + + for (SlotIndex slotIndex = 0; slotIndex < slotCount[i]; slotIndex++) + { + const char** con = i ? nodeTemplate.mOutputNames : nodeTemplate.mInputNames; + const char* conText = (con && con[slotIndex]) ? con[slotIndex] : ""; + + ImVec2 p = + offset + (i ? GetOutputSlotPos(delegate, node, slotIndex, factor) : GetInputSlotPos(delegate, node, slotIndex, factor)); + float distance = Distance(p, io.MousePos); + bool overCon = (nodeOperation == NO_None || nodeOperation == NO_EditingLink) && + (distance < options.mNodeSlotRadius * 2.f) && (distance < closestDistance); + + + ImVec2 textSize; + textSize = ImGui::CalcTextSize(conText); + ImVec2 textPos = + p + ImVec2(-options.mNodeSlotRadius * (i ? -1.f : 1.f) * (overCon ? 3.f : 2.f) - (i ? 0 : textSize.x), + -textSize.y / 2); + + ImRect nodeRect = GetNodeRect(node, factor); + if (!inMinimap && (overCon || (nodeRect.Contains(io.MousePos - offset) && closestConn == -1 && + (editingInput == (i != 0)) && nodeOperation == NO_EditingLink))) + { + closestDistance = distance; + closestConn = slotIndex; + closestTextPos = textPos; + closestPos = p; + + if (i) + { + outputSlotOver = slotIndex; + } + else + { + inputSlotOver = slotIndex; + } + } + else + { + const ImU32* slotColorSource = i ? nodeTemplate.mOutputColors : nodeTemplate.mInputColors; + const ImU32 slotColor = slotColorSource ? slotColorSource[slotIndex] : options.mDefaultSlotColor; + drawList->AddCircleFilled(p, options.mNodeSlotRadius, IM_COL32(0, 0, 0, 200)); + drawList->AddCircleFilled(p, options.mNodeSlotRadius * 0.75f, slotColor); + if (!options.mDrawIONameOnHover) + { + drawList->AddText(io.FontDefault, 14, textPos + ImVec2(2, 2), IM_COL32(0, 0, 0, 255), conText); + drawList->AddText(io.FontDefault, 14, textPos, IM_COL32(150, 150, 150, 255), conText); + } + } + } + + if (closestConn != -1) + { + const char** con = i ? nodeTemplate.mOutputNames : nodeTemplate.mInputNames; + const char* conText = (con && con[closestConn]) ? con[closestConn] : ""; + const ImU32* slotColorSource = i ? nodeTemplate.mOutputColors : nodeTemplate.mInputColors; + const ImU32 slotColor = slotColorSource ? slotColorSource[closestConn] : options.mDefaultSlotColor; + hoverSlot = true; + drawList->AddCircleFilled(closestPos, options.mNodeSlotRadius * options.mNodeSlotHoverFactor * 0.75f, IM_COL32(0, 0, 0, 200)); + drawList->AddCircleFilled(closestPos, options.mNodeSlotRadius * options.mNodeSlotHoverFactor, slotColor); + drawList->AddText(io.FontDefault, 16, closestTextPos + ImVec2(1, 1), IM_COL32(0, 0, 0, 255), conText); + drawList->AddText(io.FontDefault, 16, closestTextPos, IM_COL32(250, 250, 250, 255), conText); + bool inputToOutput = (!editingInput && !i) || (editingInput && i); + if (nodeOperation == NO_EditingLink && !io.MouseDown[0] && !bDrawOnly) + { + if (inputToOutput) + { + // check loopback + Link nl; + if (editingInput) + nl = Link{nodeIndex, closestConn, editingNodeIndex, editingSlotIndex}; + else + nl = Link{editingNodeIndex, editingSlotIndex, nodeIndex, closestConn}; + + if (!delegate.AllowedLink(nl.mOutputNodeIndex, nl.mInputNodeIndex)) + { + break; + } + bool alreadyExisting = false; + for (size_t linkIndex = 0; linkIndex < linkCount; linkIndex++) + { + const auto link = delegate.GetLink(linkIndex); + if (!memcmp(&link, &nl, sizeof(Link))) + { + alreadyExisting = true; + break; + } + } + + if (!alreadyExisting) + { + for (int linkIndex = 0; linkIndex < linkCount; linkIndex++) + { + const auto link = delegate.GetLink(linkIndex); + if (link.mOutputNodeIndex == nl.mOutputNodeIndex && link.mOutputSlotIndex == nl.mOutputSlotIndex) + { + delegate.DelLink(linkIndex); + + break; + } + } + + delegate.AddLink(nl.mInputNodeIndex, nl.mInputSlotIndex, nl.mOutputNodeIndex, nl.mOutputSlotIndex); + } + } + } + // when ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() is uncommented, one can't click the node + // input/output when mouse is over the node itself. + if (nodeOperation == NO_None && + /*ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() &&*/ io.MouseClicked[0] && !bDrawOnly) + { + nodeOperation = NO_EditingLink; + editingInput = i == 0; + editingNodeSource = closestPos; + editingNodeIndex = nodeIndex; + editingSlotIndex = closestConn; + if (editingInput) + { + // remove existing link + for (int linkIndex = 0; linkIndex < linkCount; linkIndex++) + { + const auto link = delegate.GetLink(linkIndex); + if (link.mOutputNodeIndex == nodeIndex && link.mOutputSlotIndex == closestConn) + { + delegate.DelLink(linkIndex); + break; + } + } + } + } + } + } + return hoverSlot; +} + +static void DrawGrid(ImDrawList* drawList, ImVec2 windowPos, const ViewState& viewState, const ImVec2 canvasSize, ImU32 gridColor, ImU32 gridColor2, float gridSize) +{ + float gridSpace = gridSize * viewState.mFactor; + int divx = static_cast(-viewState.mPosition.x / gridSize); + int divy = static_cast(-viewState.mPosition.y / gridSize); + for (float x = fmodf(viewState.mPosition.x * viewState.mFactor, gridSpace); x < canvasSize.x; x += gridSpace, divx ++) + { + bool tenth = !(divx % 10); + drawList->AddLine(ImVec2(x, 0.0f) + windowPos, ImVec2(x, canvasSize.y) + windowPos, tenth ? gridColor2 : gridColor); + } + for (float y = fmodf(viewState.mPosition.y * viewState.mFactor, gridSpace); y < canvasSize.y; y += gridSpace, divy ++) + { + bool tenth = !(divy % 10); + drawList->AddLine(ImVec2(0.0f, y) + windowPos, ImVec2(canvasSize.x, y) + windowPos, tenth ? gridColor2 : gridColor); + } +} + +// return true if node is hovered +static bool DrawNode(ImDrawList* drawList, + NodeIndex nodeIndex, + const ImVec2 offset, + const float factor, + Delegate& delegate, + bool overInput, + const Options& options, + const bool inMinimap, + const ImRect& viewPort) +{ + ImGuiIO& io = ImGui::GetIO(); + const auto node = delegate.GetNode(nodeIndex); + IM_ASSERT((node.mRect.GetWidth() != 0.f) && (node.mRect.GetHeight() != 0.f) && "Nodes must have a non-zero rect."); + const auto nodeTemplate = delegate.GetTemplate(node.mTemplateIndex); + const ImVec2 nodeRectangleMin = offset + node.mRect.Min * factor; + + const bool old_any_active = ImGui::IsAnyItemActive(); + ImGui::SetCursorScreenPos(nodeRectangleMin); + const ImVec2 nodeSize = node.mRect.GetSize() * factor; + + // test nested IO + drawList->ChannelsSetCurrent(1); // Background + const size_t InputsCount = nodeTemplate.mInputCount; + const size_t OutputsCount = nodeTemplate.mOutputCount; + + /* + for (int i = 0; i < 2; i++) + { + const size_t slotCount[2] = {InputsCount, OutputsCount}; + + for (size_t slotIndex = 0; slotIndex < slotCount[i]; slotIndex++) + { + const char* con = i ? nodeTemplate.mOutputNames[slotIndex] : nodeTemplate.mInputNames[slotIndex];//node.mOutputs[slot_idx] : node->mInputs[slot_idx]; + if (!delegate->IsIOPinned(nodeIndex, slot_idx, i == 1)) + { + + } + continue; + + ImVec2 p = offset + (i ? GetOutputSlotPos(delegate, node, slotIndex, factor) : GetInputSlotPos(delegate, node, slotIndex, factor)); + const float arc = 28.f * (float(i) * 0.3f + 1.0f) * (i ? 1.f : -1.f); + const float ofs = 0.f; + + ImVec2 pts[3] = {p + ImVec2(arc + ofs, 0.f), p + ImVec2(0.f + ofs, -arc), p + ImVec2(0.f + ofs, arc)}; + drawList->AddTriangleFilled(pts[0], pts[1], pts[2], i ? 0xFFAA5030 : 0xFF30AA50); + drawList->AddTriangle(pts[0], pts[1], pts[2], 0xFF000000, 2.f); + } + } + */ + + ImGui::SetCursorScreenPos(nodeRectangleMin); + float maxHeight = ImMin(viewPort.Max.y, nodeRectangleMin.y + nodeSize.y) - nodeRectangleMin.y; + float maxWidth = ImMin(viewPort.Max.x, nodeRectangleMin.x + nodeSize.x) - nodeRectangleMin.x; + ImGui::InvisibleButton("node", ImVec2(maxWidth, maxHeight)); + // must be called right after creating the control we want to be able to move + bool nodeMovingActive = ImGui::IsItemActive(); + + // Save the size of what we have emitted and whether any of the widgets are being used + bool nodeWidgetsActive = (!old_any_active && ImGui::IsAnyItemActive()); + ImVec2 nodeRectangleMax = nodeRectangleMin + nodeSize; + + bool nodeHovered = false; + if (ImGui::IsItemHovered() && nodeOperation == NO_None && !overInput) + { + nodeHovered = true; + } + + if (ImGui::IsWindowFocused()) + { + if ((nodeWidgetsActive || nodeMovingActive) && !inMinimap) + { + if (!node.mSelected) + { + if (!io.KeyShift) + { + const auto nodeCount = delegate.GetNodeCount(); + for (size_t i = 0; i < nodeCount; i++) + { + delegate.SelectNode(i, false); + } + } + delegate.SelectNode(nodeIndex, true); + } + } + } + if (nodeMovingActive && io.MouseDown[0] && nodeHovered && !inMinimap) + { + if (nodeOperation != NO_MovingNodes) + { + nodeOperation = NO_MovingNodes; + } + } + + const bool currentSelectedNode = node.mSelected; + const ImU32 node_bg_color = nodeHovered ? nodeTemplate.mBackgroundColorOver : nodeTemplate.mBackgroundColor; + + drawList->AddRect(nodeRectangleMin, + nodeRectangleMax, + currentSelectedNode ? options.mSelectedNodeBorderColor : options.mNodeBorderColor, + options.mRounding, + ImDrawFlags_RoundCornersAll, + currentSelectedNode ? options.mBorderSelectionThickness : options.mBorderThickness); + + ImVec2 imgPos = nodeRectangleMin + ImVec2(14, 25); + ImVec2 imgSize = nodeRectangleMax + ImVec2(-5, -5) - imgPos; + float imgSizeComp = std::min(imgSize.x, imgSize.y); + + drawList->AddRectFilled(nodeRectangleMin, nodeRectangleMax, node_bg_color, options.mRounding); + /*float progress = delegate->NodeProgress(nodeIndex); + if (progress > FLT_EPSILON && progress < 1.f - FLT_EPSILON) + { + ImVec2 progressLineA = nodeRectangleMax - ImVec2(nodeSize.x - 2.f, 3.f); + ImVec2 progressLineB = progressLineA + ImVec2(nodeSize.x * factor - 4.f, 0.f); + drawList->AddLine(progressLineA, progressLineB, 0xFF400000, 3.f); + drawList->AddLine(progressLineA, ImLerp(progressLineA, progressLineB, progress), 0xFFFF0000, 3.f); + }*/ + ImVec2 imgPosMax = imgPos + ImVec2(imgSizeComp, imgSizeComp); + + //ImVec2 imageSize = delegate->GetEvaluationSize(nodeIndex); + /*float imageRatio = 1.f; + if (imageSize.x > 0.f && imageSize.y > 0.f) + { + imageRatio = imageSize.y / imageSize.x; + } + ImVec2 quadSize = imgPosMax - imgPos; + ImVec2 marge(0.f, 0.f); + if (imageRatio > 1.f) + { + marge.x = (quadSize.x - quadSize.y / imageRatio) * 0.5f; + } + else + { + marge.y = (quadSize.y - quadSize.y * imageRatio) * 0.5f; + }*/ + + //delegate->DrawNodeImage(drawList, ImRect(imgPos, imgPosMax), marge, nodeIndex); + + drawList->AddRectFilled(nodeRectangleMin, + ImVec2(nodeRectangleMax.x, nodeRectangleMin.y + 20), + nodeTemplate.mHeaderColor, options.mRounding); + + drawList->PushClipRect(nodeRectangleMin, ImVec2(nodeRectangleMax.x, nodeRectangleMin.y + 20), true); + drawList->AddText(nodeRectangleMin + ImVec2(2, 2), IM_COL32(0, 0, 0, 255), node.mName); + drawList->PopClipRect(); + + ImRect customDrawRect(nodeRectangleMin + ImVec2(options.mRounding, 20 + options.mRounding), nodeRectangleMax - ImVec2(options.mRounding, options.mRounding)); + if (customDrawRect.Max.y > customDrawRect.Min.y && customDrawRect.Max.x > customDrawRect.Min.x) + { + delegate.CustomDraw(drawList, customDrawRect, nodeIndex); + } +/* + const ImTextureID bmpInfo = (ImTextureID)(uint64_t)delegate->GetBitmapInfo(nodeIndex).idx; + if (bmpInfo) + { + ImVec2 bmpInfoPos(nodeRectangleMax - ImVec2(26, 12)); + ImVec2 bmpInfoSize(20, 20); + if (delegate->NodeIsCompute(nodeIndex)) + { + drawList->AddImageQuad(bmpInfo, + bmpInfoPos, + bmpInfoPos + ImVec2(bmpInfoSize.x, 0.f), + bmpInfoPos + bmpInfoSize, + bmpInfoPos + ImVec2(0., bmpInfoSize.y)); + } + else if (delegate->NodeIs2D(nodeIndex)) + { + drawList->AddImageQuad(bmpInfo, + bmpInfoPos, + bmpInfoPos + ImVec2(bmpInfoSize.x, 0.f), + bmpInfoPos + bmpInfoSize, + bmpInfoPos + ImVec2(0., bmpInfoSize.y)); + } + else if (delegate->NodeIsCubemap(nodeIndex)) + { + drawList->AddImageQuad(bmpInfo, + bmpInfoPos + ImVec2(0., bmpInfoSize.y), + bmpInfoPos + bmpInfoSize, + bmpInfoPos + ImVec2(bmpInfoSize.x, 0.f), + bmpInfoPos); + } + }*/ + return nodeHovered; +} + +bool DrawMiniMap(ImDrawList* drawList, Delegate& delegate, ViewState& viewState, const Options& options, const ImVec2 windowPos, const ImVec2 canvasSize) +{ + if (Distance(options.mMinimap.Min, options.mMinimap.Max) <= FLT_EPSILON) + { + return false; + } + + const size_t nodeCount = delegate.GetNodeCount(); + + if (!nodeCount) + { + return false; + } + + ImVec2 min(FLT_MAX, FLT_MAX); + ImVec2 max(-FLT_MAX, -FLT_MAX); + const ImVec2 margin(50, 50); + for (NodeIndex nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) + { + const Node& node = delegate.GetNode(nodeIndex); + min = ImMin(min, node.mRect.Min - margin); + min = ImMin(min, node.mRect.Max + margin); + max = ImMax(max, node.mRect.Min - margin); + max = ImMax(max, node.mRect.Max + margin); + } + + // add view in world space + const ImVec2 worldSizeView = canvasSize / viewState.mFactor; + const ImVec2 viewMin(-viewState.mPosition.x, -viewState.mPosition.y); + const ImVec2 viewMax = viewMin + worldSizeView; + min = ImMin(min, viewMin); + max = ImMax(max, viewMax); + const ImVec2 nodesSize = max - min; + const ImVec2 middleWorld = (min + max) * 0.5f; + const ImVec2 minScreen = windowPos + options.mMinimap.Min * canvasSize; + const ImVec2 maxScreen = windowPos + options.mMinimap.Max * canvasSize; + const ImVec2 viewSize = maxScreen - minScreen; + const ImVec2 middleScreen = (minScreen + maxScreen) * 0.5f; + const float ratioY = viewSize.y / nodesSize.y; + const float ratioX = viewSize.x / nodesSize.x; + const float factor = ImMin(ImMin(ratioY, ratioX), 1.f); + + drawList->AddRectFilled(minScreen, maxScreen, IM_COL32(30, 30, 30, 200), 3, ImDrawFlags_RoundCornersAll); + + for (NodeIndex nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) + { + const Node& node = delegate.GetNode(nodeIndex); + const auto nodeTemplate = delegate.GetTemplate(node.mTemplateIndex); + + ImRect rect = node.mRect; + rect.Min -= middleWorld; + rect.Min *= factor; + rect.Min += middleScreen; + + rect.Max -= middleWorld; + rect.Max *= factor; + rect.Max += middleScreen; + + drawList->AddRectFilled(rect.Min, rect.Max, nodeTemplate.mBackgroundColor, 1, ImDrawFlags_RoundCornersAll); + if (node.mSelected) + { + drawList->AddRect(rect.Min, rect.Max, options.mSelectedNodeBorderColor, 1, ImDrawFlags_RoundCornersAll); + } + } + + // add view + ImVec2 viewMinScreen = (viewMin - middleWorld) * factor + middleScreen; + ImVec2 viewMaxScreen = (viewMax - middleWorld) * factor + middleScreen; + drawList->AddRectFilled(viewMinScreen, viewMaxScreen, IM_COL32(255, 255, 255, 32), 1, ImDrawFlags_RoundCornersAll); + drawList->AddRect(viewMinScreen, viewMaxScreen, IM_COL32(255, 255, 255, 128), 1, ImDrawFlags_RoundCornersAll); + + ImGuiIO& io = ImGui::GetIO(); + const bool mouseInMinimap = ImRect(minScreen, maxScreen).Contains(io.MousePos); + if (mouseInMinimap && io.MouseClicked[0]) + { + const ImVec2 clickedRatio = (io.MousePos - minScreen) / viewSize; + const ImVec2 worldPosCenter = ImVec2(ImLerp(min.x, max.x, clickedRatio.x), ImLerp(min.y, max.y, clickedRatio.y)); + + ImVec2 worldPosViewMin = worldPosCenter - worldSizeView * 0.5; + ImVec2 worldPosViewMax = worldPosCenter + worldSizeView * 0.5; + if (worldPosViewMin.x < min.x) + { + worldPosViewMin.x = min.x; + worldPosViewMax.x = worldPosViewMin.x + worldSizeView.x; + } + if (worldPosViewMin.y < min.y) + { + worldPosViewMin.y = min.y; + worldPosViewMax.y = worldPosViewMin.y + worldSizeView.y; + } + if (worldPosViewMax.x > max.x) + { + worldPosViewMax.x = max.x; + worldPosViewMin.x = worldPosViewMax.x - worldSizeView.x; + } + if (worldPosViewMax.y > max.y) + { + worldPosViewMax.y = max.y; + worldPosViewMin.y = worldPosViewMax.y - worldSizeView.y; + } + viewState.mPosition = ImVec2(-worldPosViewMin.x, -worldPosViewMin.y); + } + return mouseInMinimap; +} + +void Show(Delegate& delegate, const Options& options, ViewState& viewState, bool enabled, FitOnScreen* fit) +{ + ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.f); + + const ImVec2 windowPos = ImGui::GetCursorScreenPos(); + const ImVec2 canvasSize = ImGui::GetContentRegionAvail(); + const ImVec2 scrollRegionLocalPos(0, 0); + + ImRect regionRect(windowPos, windowPos + canvasSize); + + HandleZoomScroll(regionRect, viewState, options); + ImVec2 offset = ImGui::GetCursorScreenPos() + viewState.mPosition * viewState.mFactor; + captureOffset = viewState.mPosition * viewState.mFactor; + + //ImGui::InvisibleButton("GraphEditorButton", canvasSize); + ImGui::BeginChildFrame(71711, canvasSize); + + ImGui::SetCursorPos(windowPos); + ImGui::BeginGroup(); + + ImGuiIO& io = ImGui::GetIO(); + + // Create our child canvas + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1, 1)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(30, 30, 30, 200)); + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImGui::PushClipRect(regionRect.Min, regionRect.Max, true); + drawList->AddRectFilled(windowPos, windowPos + canvasSize, options.mBackgroundColor); + + // Background or Display grid + if (options.mRenderGrid) + { + DrawGrid(drawList, windowPos, viewState, canvasSize, options.mGridColor, options.mGridColor2, options.mGridSize); + } + + // Fit view + if (fit && ((*fit == Fit_AllNodes) || (*fit == Fit_SelectedNodes))) + { + FitNodes(delegate, viewState, canvasSize, (*fit == Fit_SelectedNodes)); + } + + if (enabled) + { + static NodeIndex hoveredNode = -1; + // Display links + drawList->ChannelsSplit(3); + + // minimap + drawList->ChannelsSetCurrent(2); // minimap + const bool inMinimap = DrawMiniMap(drawList, delegate, viewState, options, windowPos, canvasSize); + + // Focus rectangle + if (ImGui::IsWindowFocused()) + { + drawList->AddRect(regionRect.Min, regionRect.Max, options.mFrameFocus, 1.f, 0, 2.f); + } + + drawList->ChannelsSetCurrent(1); // Background + + // Links + DisplayLinks(delegate, drawList, offset, viewState.mFactor, regionRect, hoveredNode, options); + + // edit node link + if (nodeOperation == NO_EditingLink) + { + ImVec2 p1 = editingNodeSource; + ImVec2 p2 = io.MousePos; + drawList->AddLine(p1, p2, IM_COL32(200, 200, 200, 255), 3.0f); + } + + // Display nodes + drawList->PushClipRect(regionRect.Min, regionRect.Max, true); + hoveredNode = -1; + + SlotIndex inputSlotOver = -1; + SlotIndex outputSlotOver = -1; + NodeIndex nodeOver = -1; + + const auto nodeCount = delegate.GetNodeCount(); + for (int i = 0; i < 2; i++) + { + for (NodeIndex nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) + { + //const auto* node = &nodes[nodeIndex]; + const auto node = delegate.GetNode(nodeIndex); + if (node.mSelected != (i != 0)) + { + continue; + } + + // node view clipping + ImRect nodeRect = GetNodeRect(node, viewState.mFactor); + nodeRect.Min += offset; + nodeRect.Max += offset; + if (!regionRect.Overlaps(nodeRect)) + { + continue; + } + + ImGui::PushID((int)nodeIndex); + SlotIndex inputSlot = -1; + SlotIndex outputSlot = -1; + + bool overInput = (!inMinimap) && HandleConnections(drawList, nodeIndex, offset, viewState.mFactor, delegate, options, false, inputSlot, outputSlot, inMinimap); + + // shadow + /* + ImVec2 shadowOffset = ImVec2(30, 30); + ImVec2 shadowPivot = (nodeRect.Min + nodeRect.Max) /2.f; + ImVec2 shadowPointMiddle = shadowPivot + shadowOffset; + ImVec2 shadowPointTop = ImVec2(shadowPivot.x, nodeRect.Min.y) + shadowOffset; + ImVec2 shadowPointBottom = ImVec2(shadowPivot.x, nodeRect.Max.y) + shadowOffset; + ImVec2 shadowPointLeft = ImVec2(nodeRect.Min.x, shadowPivot.y) + shadowOffset; + ImVec2 shadowPointRight = ImVec2(nodeRect.Max.x, shadowPivot.y) + shadowOffset; + + // top left + drawList->AddRectFilledMultiColor(nodeRect.Min + shadowOffset, shadowPointMiddle, IM_COL32(0 ,0, 0, 0), IM_COL32(0,0,0,0), IM_COL32(0, 0, 0, 255), IM_COL32(0, 0, 0, 0)); + + // top right + drawList->AddRectFilledMultiColor(shadowPointTop, shadowPointRight, IM_COL32(0 ,0, 0, 0), IM_COL32(0,0,0,0), IM_COL32(0, 0, 0, 0), IM_COL32(0, 0, 0, 255)); + + // bottom left + drawList->AddRectFilledMultiColor(shadowPointLeft, shadowPointBottom, IM_COL32(0 ,0, 0, 0), IM_COL32(0, 0, 0, 255), IM_COL32(0, 0, 0, 0), IM_COL32(0,0,0,0)); + + // bottom right + drawList->AddRectFilledMultiColor(shadowPointMiddle, nodeRect.Max + shadowOffset, IM_COL32(0, 0, 0, 255), IM_COL32(0 ,0, 0, 0), IM_COL32(0,0,0,0), IM_COL32(0, 0, 0, 0)); + */ + if (DrawNode(drawList, nodeIndex, offset, viewState.mFactor, delegate, overInput, options, inMinimap, regionRect)) + { + hoveredNode = nodeIndex; + } + + HandleConnections(drawList, nodeIndex, offset, viewState.mFactor, delegate, options, true, inputSlot, outputSlot, inMinimap); + if (inputSlot != -1 || outputSlot != -1) + { + inputSlotOver = inputSlot; + outputSlotOver = outputSlot; + nodeOver = nodeIndex; + } + + ImGui::PopID(); + } + } + + + + drawList->PopClipRect(); + + if (nodeOperation == NO_MovingNodes) + { + if (ImGui::IsMouseDragging(0, 1)) + { + ImVec2 delta = io.MouseDelta / viewState.mFactor; + if (fabsf(delta.x) >= 1.f || fabsf(delta.y) >= 1.f) + { + delegate.MoveSelectedNodes(delta); + } + } + } + + drawList->ChannelsSetCurrent(0); + + // quad selection + if (!inMinimap) + { + HandleQuadSelection(delegate, drawList, offset, viewState.mFactor, regionRect, options); + } + + drawList->ChannelsMerge(); + + // releasing mouse button means it's done in any operation + if (nodeOperation == NO_PanView) + { + if (!io.MouseDown[2]) + { + nodeOperation = NO_None; + } + } + else if (nodeOperation != NO_None && !io.MouseDown[0]) + { + nodeOperation = NO_None; + } + + // right click + if (!inMinimap && nodeOperation == NO_None && regionRect.Contains(io.MousePos) && + (ImGui::IsMouseClicked(1) /*|| (ImGui::IsWindowFocused() && ImGui::IsKeyPressedMap(ImGuiKey_Tab))*/)) + { + delegate.RightClick(nodeOver, inputSlotOver, outputSlotOver); + } + + // Scrolling + if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && io.MouseClicked[2] && nodeOperation == NO_None) + { + nodeOperation = NO_PanView; + } + if (nodeOperation == NO_PanView) + { + viewState.mPosition += io.MouseDelta / viewState.mFactor; + } + } + + ImGui::PopClipRect(); + + ImGui::PopStyleColor(1); + ImGui::PopStyleVar(2); + ImGui::EndGroup(); + ImGui::EndChildFrame(); + + ImGui::PopStyleVar(3); + + // change fit to none + if (fit) + { + *fit = Fit_None; + } +} + +bool EditOptions(Options& options) +{ + bool updated = false; + if (ImGui::CollapsingHeader("Colors", nullptr)) + { + ImColor backgroundColor(options.mBackgroundColor); + ImColor gridColor(options.mGridColor); + ImColor selectedNodeBorderColor(options.mSelectedNodeBorderColor); + ImColor nodeBorderColor(options.mNodeBorderColor); + ImColor quadSelection(options.mQuadSelection); + ImColor quadSelectionBorder(options.mQuadSelectionBorder); + ImColor defaultSlotColor(options.mDefaultSlotColor); + ImColor frameFocus(options.mFrameFocus); + + updated |= ImGui::ColorEdit4("Background", (float*)&backgroundColor); + updated |= ImGui::ColorEdit4("Grid", (float*)&gridColor); + updated |= ImGui::ColorEdit4("Selected Node Border", (float*)&selectedNodeBorderColor); + updated |= ImGui::ColorEdit4("Node Border", (float*)&nodeBorderColor); + updated |= ImGui::ColorEdit4("Quad Selection", (float*)&quadSelection); + updated |= ImGui::ColorEdit4("Quad Selection Border", (float*)&quadSelectionBorder); + updated |= ImGui::ColorEdit4("Default Slot", (float*)&defaultSlotColor); + updated |= ImGui::ColorEdit4("Frame when has focus", (float*)&frameFocus); + + options.mBackgroundColor = backgroundColor; + options.mGridColor = gridColor; + options.mSelectedNodeBorderColor = selectedNodeBorderColor; + options.mNodeBorderColor = nodeBorderColor; + options.mQuadSelection = quadSelection; + options.mQuadSelectionBorder = quadSelectionBorder; + options.mDefaultSlotColor = defaultSlotColor; + options.mFrameFocus = frameFocus; + } + + if (ImGui::CollapsingHeader("Options", nullptr)) + { + updated |= ImGui::InputFloat4("Minimap", &options.mMinimap.Min.x); + updated |= ImGui::InputFloat("Line Thickness", &options.mLineThickness); + updated |= ImGui::InputFloat("Grid Size", &options.mGridSize); + updated |= ImGui::InputFloat("Rounding", &options.mRounding); + updated |= ImGui::InputFloat("Zoom Ratio", &options.mZoomRatio); + updated |= ImGui::InputFloat("Zoom Lerp Factor", &options.mZoomLerpFactor); + updated |= ImGui::InputFloat("Border Selection Thickness", &options.mBorderSelectionThickness); + updated |= ImGui::InputFloat("Border Thickness", &options.mBorderThickness); + updated |= ImGui::InputFloat("Slot Radius", &options.mNodeSlotRadius); + updated |= ImGui::InputFloat("Slot Hover Factor", &options.mNodeSlotHoverFactor); + updated |= ImGui::InputFloat2("Zoom min/max", &options.mMinZoom); + updated |= ImGui::InputFloat("Slot Hover Factor", &options.mSnap); + + if (ImGui::RadioButton("Curved Links", options.mDisplayLinksAsCurves)) + { + options.mDisplayLinksAsCurves = !options.mDisplayLinksAsCurves; + updated = true; + } + if (ImGui::RadioButton("Straight Links", !options.mDisplayLinksAsCurves)) + { + options.mDisplayLinksAsCurves = !options.mDisplayLinksAsCurves; + updated = true; + } + + updated |= ImGui::Checkbox("Allow Quad Selection", &options.mAllowQuadSelection); + updated |= ImGui::Checkbox("Render Grid", &options.mRenderGrid); + updated |= ImGui::Checkbox("Draw IO names on hover", &options.mDrawIONameOnHover); + } + + return updated; +} + +} // namespace diff --git a/editor/ImGui/Source/ImGuizmo/GraphEditor.h b/editor/ImGui/Source/ImGuizmo/GraphEditor.h new file mode 100644 index 000000000..4a6717e8f --- /dev/null +++ b/editor/ImGui/Source/ImGuizmo/GraphEditor.h @@ -0,0 +1,149 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#pragma once + +#include +#include +#include +#include "../../Source/imgui.h" +#include "../../Source/imgui_internal.h" + +namespace GraphEditor { + +typedef size_t NodeIndex; +typedef size_t SlotIndex; +typedef size_t LinkIndex; +typedef size_t TemplateIndex; + +// Force the view to be respositionned and zoom to fit nodes with Show function. +// Parameter value will be changed to Fit_None by the function. +enum FitOnScreen +{ + Fit_None, + Fit_AllNodes, + Fit_SelectedNodes +}; + +// Display options and colors +struct Options +{ + ImRect mMinimap{{0.75f, 0.8f, 0.99f, 0.99f}}; // rectangle coordinates of minimap + ImU32 mBackgroundColor{ IM_COL32(40, 40, 40, 255) }; // full background color + ImU32 mGridColor{ IM_COL32(0, 0, 0, 60) }; // grid lines color + ImU32 mGridColor2{ IM_COL32(0, 0, 0, 160) }; // grid lines color every 10th + ImU32 mSelectedNodeBorderColor{ IM_COL32(255, 130, 30, 255) }; // node border color when it's selected + ImU32 mNodeBorderColor{ IM_COL32(100, 100, 100, 0) }; // node border color when it's not selected + ImU32 mQuadSelection{ IM_COL32(255, 32, 32, 64) }; // quad selection inside color + ImU32 mQuadSelectionBorder{ IM_COL32(255, 32, 32, 255) }; // quad selection border color + ImU32 mDefaultSlotColor{ IM_COL32(128, 128, 128, 255) }; // when no color is provided in node template, use this value + ImU32 mFrameFocus{ IM_COL32(64, 128, 255, 255) }; // rectangle border when graph editor has focus + float mLineThickness{ 5 }; // links width in pixels when zoom value is 1 + float mGridSize{ 64.f }; // background grid size in pixels when zoom value is 1 + float mRounding{ 3.f }; // rounding at node corners + float mZoomRatio{ 0.1f }; // factor per mouse wheel delta + float mZoomLerpFactor{ 0.25f }; // the smaller, the smoother + float mBorderSelectionThickness{ 6.f }; // thickness of selection border around nodes + float mBorderThickness{ 6.f }; // thickness of selection border around nodes + float mNodeSlotRadius{ 8.f }; // circle radius for inputs and outputs + float mNodeSlotHoverFactor{ 1.2f }; // increase size when hovering + float mMinZoom{ 0.2f }, mMaxZoom { 1.1f }; + float mSnap{ 5.f }; + bool mDisplayLinksAsCurves{ true }; // false is straight and 45deg lines + bool mAllowQuadSelection{ true }; // multiple selection using drag and drop + bool mRenderGrid{ true }; // grid or nothing + bool mDrawIONameOnHover{ true }; // only draw node input/output when hovering +}; + +// View state: scroll position and zoom factor +struct ViewState +{ + ImVec2 mPosition{0.0f, 0.0f}; // scroll position + float mFactor{ 1.0f }; // current zoom factor + float mFactorTarget{ 1.0f }; // targeted zoom factor interpolated using Options.mZoomLerpFactor +}; + +struct Template +{ + ImU32 mHeaderColor; + ImU32 mBackgroundColor; + ImU32 mBackgroundColorOver; + ImU8 mInputCount; + const char** mInputNames; // can be nullptr. No text displayed. + ImU32* mInputColors; // can be nullptr, default slot color will be used. + ImU8 mOutputCount; + const char** mOutputNames; // can be nullptr. No text displayed. + ImU32* mOutputColors; // can be nullptr, default slot color will be used. +}; + +struct Node +{ + const char* mName; + TemplateIndex mTemplateIndex; + ImRect mRect; + bool mSelected{ false }; +}; + +struct Link +{ + NodeIndex mInputNodeIndex; + SlotIndex mInputSlotIndex; + NodeIndex mOutputNodeIndex; + SlotIndex mOutputSlotIndex; +}; + +struct Delegate +{ + virtual bool AllowedLink(NodeIndex from, NodeIndex to) = 0; + + virtual void SelectNode(NodeIndex nodeIndex, bool selected) = 0; + virtual void MoveSelectedNodes(const ImVec2 delta) = 0; + + virtual void AddLink(NodeIndex inputNodeIndex, SlotIndex inputSlotIndex, NodeIndex outputNodeIndex, SlotIndex outputSlotIndex) = 0; + virtual void DelLink(LinkIndex linkIndex) = 0; + + // user is responsible for clipping + virtual void CustomDraw(ImDrawList* drawList, ImRect rectangle, NodeIndex nodeIndex) = 0; + + // use mouse position to open context menu + // if nodeIndex != -1, right click happens on the specified node + virtual void RightClick(NodeIndex nodeIndex, SlotIndex slotIndexInput, SlotIndex slotIndexOutput) = 0; + + virtual const size_t GetTemplateCount() = 0; + virtual const Template GetTemplate(TemplateIndex index) = 0; + + virtual const size_t GetNodeCount() = 0; + virtual const Node GetNode(NodeIndex index) = 0; + + virtual const size_t GetLinkCount() = 0; + virtual const Link GetLink(LinkIndex index) = 0; +}; + +void Show(Delegate& delegate, const Options& options, ViewState& viewState, bool enabled, FitOnScreen* fit = nullptr); +void GraphEditorClear(); + +bool EditOptions(Options& options); + +} // namespace diff --git a/editor/ImGui/Source/ImGuizmo/ImCurveEdit.cpp b/editor/ImGui/Source/ImGuizmo/ImCurveEdit.cpp new file mode 100644 index 000000000..5792143eb --- /dev/null +++ b/editor/ImGui/Source/ImGuizmo/ImCurveEdit.cpp @@ -0,0 +1,457 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include "ImCurveEdit.h" +#include "../../Source/imgui.h" +#include "../../Source/imgui_internal.h" +#include +#include +#include + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#endif +#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR) +#define _malloca(x) alloca(x) +#define _freea(x) +#endif + +namespace ImCurveEdit +{ + +#ifndef IMGUI_DEFINE_MATH_OPERATORS + static ImVec2 operator+(const ImVec2& a, const ImVec2& b) { + return ImVec2(a.x + b.x, a.y + b.y); + } + + static ImVec2 operator-(const ImVec2& a, const ImVec2& b) { + return ImVec2(a.x - b.x, a.y - b.y); + } + + static ImVec2 operator*(const ImVec2& a, const ImVec2& b) { + return ImVec2(a.x * b.x, a.y * b.y); + } + + static ImVec2 operator/(const ImVec2& a, const ImVec2& b) { + return ImVec2(a.x / b.x, a.y / b.y); + } + + static ImVec2 operator*(const ImVec2& a, const float b) { + return ImVec2(a.x * b, a.y * b); + } +#endif + + static float smoothstep(float edge0, float edge1, float x) + { + x = ImClamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); + return x * x * (3 - 2 * x); + } + + static float distance(float x, float y, float x1, float y1, float x2, float y2) + { + float A = x - x1; + float B = y - y1; + float C = x2 - x1; + float D = y2 - y1; + + float dot = A * C + B * D; + float len_sq = C * C + D * D; + float param = -1.f; + if (len_sq > FLT_EPSILON) + param = dot / len_sq; + + float xx, yy; + + if (param < 0.f) { + xx = x1; + yy = y1; + } + else if (param > 1.f) { + xx = x2; + yy = y2; + } + else { + xx = x1 + param * C; + yy = y1 + param * D; + } + + float dx = x - xx; + float dy = y - yy; + return sqrtf(dx * dx + dy * dy); + } + + static int DrawPoint(ImDrawList* draw_list, ImVec2 pos, const ImVec2 size, const ImVec2 offset, bool edited) + { + int ret = 0; + ImGuiIO& io = ImGui::GetIO(); + + static const ImVec2 localOffsets[4] = { ImVec2(1,0), ImVec2(0,1), ImVec2(-1,0), ImVec2(0,-1) }; + ImVec2 offsets[4]; + for (int i = 0; i < 4; i++) + { + offsets[i] = pos * size + localOffsets[i] * 4.5f + offset; + } + + const ImVec2 center = pos * size + offset; + const ImRect anchor(center - ImVec2(5, 5), center + ImVec2(5, 5)); + draw_list->AddConvexPolyFilled(offsets, 4, 0xFF000000); + if (anchor.Contains(io.MousePos)) + { + ret = 1; + if (io.MouseDown[0]) + ret = 2; + } + if (edited) + draw_list->AddPolyline(offsets, 4, 0xFFFFFFFF, true, 3.0f); + else if (ret) + draw_list->AddPolyline(offsets, 4, 0xFF80B0FF, true, 2.0f); + else + draw_list->AddPolyline(offsets, 4, 0xFF0080FF, true, 2.0f); + + return ret; + } + + int Edit(Delegate& delegate, const ImVec2& size, unsigned int id, const ImRect* clippingRect, ImVector* selectedPoints) + { + static bool selectingQuad = false; + static ImVec2 quadSelection; + static int overCurve = -1; + static int movingCurve = -1; + static bool scrollingV = false; + static std::set selection; + static bool overSelectedPoint = false; + + int ret = 0; + + ImGuiIO& io = ImGui::GetIO(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::PushStyleColor(ImGuiCol_Border, 0); + ImGui::BeginChildFrame(id, size); + delegate.focused = ImGui::IsWindowFocused(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + if (clippingRect) + draw_list->PushClipRect(clippingRect->Min, clippingRect->Max, true); + + const ImVec2 offset = ImGui::GetCursorScreenPos() + ImVec2(0.f, size.y); + const ImVec2 ssize(size.x, -size.y); + const ImRect container(offset + ImVec2(0.f, ssize.y), offset + ImVec2(ssize.x, 0.f)); + ImVec2& min = delegate.GetMin(); + ImVec2& max = delegate.GetMax(); + + // handle zoom and VScroll + if (container.Contains(io.MousePos)) + { + if (fabsf(io.MouseWheel) > FLT_EPSILON) + { + const float r = (io.MousePos.y - offset.y) / ssize.y; + float ratioY = ImLerp(min.y, max.y, r); + auto scaleValue = [&](float v) { + v -= ratioY; + v *= (1.f - io.MouseWheel * 0.05f); + v += ratioY; + return v; + }; + min.y = scaleValue(min.y); + max.y = scaleValue(max.y); + } + if (!scrollingV && ImGui::IsMouseDown(2)) + { + scrollingV = true; + } + } + ImVec2 range = max - min + ImVec2(1.f, 0.f); // +1 because of inclusive last frame + + const ImVec2 viewSize(size.x, -size.y); + const ImVec2 sizeOfPixel = ImVec2(1.f, 1.f) / viewSize; + const size_t curveCount = delegate.GetCurveCount(); + + if (scrollingV) + { + float deltaH = io.MouseDelta.y * range.y * sizeOfPixel.y; + min.y -= deltaH; + max.y -= deltaH; + if (!ImGui::IsMouseDown(2)) + scrollingV = false; + } + + draw_list->AddRectFilled(offset, offset + ssize, delegate.GetBackgroundColor()); + + auto pointToRange = [&](ImVec2 pt) { return (pt - min) / range; }; + auto rangeToPoint = [&](ImVec2 pt) { return (pt * range) + min; }; + + draw_list->AddLine(ImVec2(-1.f, -min.y / range.y) * viewSize + offset, ImVec2(1.f, -min.y / range.y) * viewSize + offset, 0xFF000000, 1.5f); + bool overCurveOrPoint = false; + + int localOverCurve = -1; + // make sure highlighted curve is rendered last + int* curvesIndex = (int*)_malloca(sizeof(int) * curveCount); + for (size_t c = 0; c < curveCount; c++) + curvesIndex[c] = int(c); + int highLightedCurveIndex = -1; + if (overCurve != -1 && curveCount) + { + ImSwap(curvesIndex[overCurve], curvesIndex[curveCount - 1]); + highLightedCurveIndex = overCurve; + } + + for (size_t cur = 0; cur < curveCount; cur++) + { + int c = curvesIndex[cur]; + if (!delegate.IsVisible(c)) + continue; + const size_t ptCount = delegate.GetPointCount(c); + if (ptCount < 1) + continue; + CurveType curveType = delegate.GetCurveType(c); + if (curveType == CurveNone) + continue; + const ImVec2* pts = delegate.GetPoints(c); + uint32_t curveColor = delegate.GetCurveColor(c); + if ((c == highLightedCurveIndex && selection.empty() && !selectingQuad) || movingCurve == c) + curveColor = 0xFFFFFFFF; + + for (size_t p = 0; p < ptCount - 1; p++) + { + const ImVec2 p1 = pointToRange(pts[p]); + const ImVec2 p2 = pointToRange(pts[p + 1]); + + if (curveType == CurveSmooth || curveType == CurveLinear) + { + size_t subStepCount = (curveType == CurveSmooth) ? 20 : 2; + float step = 1.f / float(subStepCount - 1); + for (size_t substep = 0; substep < subStepCount - 1; substep++) + { + float t = float(substep) * step; + + const ImVec2 sp1 = ImLerp(p1, p2, t); + const ImVec2 sp2 = ImLerp(p1, p2, t + step); + + const float rt1 = smoothstep(p1.x, p2.x, sp1.x); + const float rt2 = smoothstep(p1.x, p2.x, sp2.x); + + const ImVec2 pos1 = ImVec2(sp1.x, ImLerp(p1.y, p2.y, rt1)) * viewSize + offset; + const ImVec2 pos2 = ImVec2(sp2.x, ImLerp(p1.y, p2.y, rt2)) * viewSize + offset; + + if (distance(io.MousePos.x, io.MousePos.y, pos1.x, pos1.y, pos2.x, pos2.y) < 8.f && !scrollingV) + { + localOverCurve = int(c); + overCurve = int(c); + overCurveOrPoint = true; + } + + draw_list->AddLine(pos1, pos2, curveColor, 1.3f); + } // substep + } + else if (curveType == CurveDiscrete) + { + ImVec2 dp1 = p1 * viewSize + offset; + ImVec2 dp2 = ImVec2(p2.x, p1.y) * viewSize + offset; + ImVec2 dp3 = p2 * viewSize + offset; + draw_list->AddLine(dp1, dp2, curveColor, 1.3f); + draw_list->AddLine(dp2, dp3, curveColor, 1.3f); + + if ((distance(io.MousePos.x, io.MousePos.y, dp1.x, dp1.y, dp3.x, dp1.y) < 8.f || + distance(io.MousePos.x, io.MousePos.y, dp3.x, dp1.y, dp3.x, dp3.y) < 8.f) + /*&& localOverCurve == -1*/) + { + localOverCurve = int(c); + overCurve = int(c); + overCurveOrPoint = true; + } + } + } // point loop + + for (size_t p = 0; p < ptCount; p++) + { + const int drawState = DrawPoint(draw_list, pointToRange(pts[p]), viewSize, offset, (selection.find({ int(c), int(p) }) != selection.end() && movingCurve == -1 && !scrollingV)); + if (drawState && movingCurve == -1 && !selectingQuad) + { + overCurveOrPoint = true; + overSelectedPoint = true; + overCurve = -1; + if (drawState == 2) + { + if (!io.KeyShift && selection.find({ int(c), int(p) }) == selection.end()) + selection.clear(); + selection.insert({ int(c), int(p) }); + } + } + } + } // curves loop + + if (localOverCurve == -1) + overCurve = -1; + + // move selection + static bool pointsMoved = false; + static ImVec2 mousePosOrigin; + static std::vector originalPoints; + if (overSelectedPoint && io.MouseDown[0]) + { + if ((fabsf(io.MouseDelta.x) > 0.f || fabsf(io.MouseDelta.y) > 0.f) && !selection.empty()) + { + if (!pointsMoved) + { + delegate.BeginEdit(0); + mousePosOrigin = io.MousePos; + originalPoints.resize(selection.size()); + int index = 0; + for (auto& sel : selection) + { + const ImVec2* pts = delegate.GetPoints(sel.curveIndex); + originalPoints[index++] = pts[sel.pointIndex]; + } + } + pointsMoved = true; + ret = 1; + auto prevSelection = selection; + int originalIndex = 0; + for (auto& sel : prevSelection) + { + const ImVec2 p = rangeToPoint(pointToRange(originalPoints[originalIndex]) + (io.MousePos - mousePosOrigin) * sizeOfPixel); + const int newIndex = delegate.EditPoint(sel.curveIndex, sel.pointIndex, p); + if (newIndex != sel.pointIndex) + { + selection.erase(sel); + selection.insert({ sel.curveIndex, newIndex }); + } + originalIndex++; + } + } + } + + if (overSelectedPoint && !io.MouseDown[0]) + { + overSelectedPoint = false; + if (pointsMoved) + { + pointsMoved = false; + delegate.EndEdit(); + } + } + + // add point + if (overCurve != -1 && io.MouseDoubleClicked[0]) + { + const ImVec2 np = rangeToPoint((io.MousePos - offset) / viewSize); + delegate.BeginEdit(overCurve); + delegate.AddPoint(overCurve, np); + delegate.EndEdit(); + ret = 1; + } + + // move curve + + if (movingCurve != -1) + { + const size_t ptCount = delegate.GetPointCount(movingCurve); + const ImVec2* pts = delegate.GetPoints(movingCurve); + if (!pointsMoved) + { + mousePosOrigin = io.MousePos; + pointsMoved = true; + originalPoints.resize(ptCount); + for (size_t index = 0; index < ptCount; index++) + { + originalPoints[index] = pts[index]; + } + } + if (ptCount >= 1) + { + for (size_t p = 0; p < ptCount; p++) + { + delegate.EditPoint(movingCurve, int(p), rangeToPoint(pointToRange(originalPoints[p]) + (io.MousePos - mousePosOrigin) * sizeOfPixel)); + } + ret = 1; + } + if (!io.MouseDown[0]) + { + movingCurve = -1; + pointsMoved = false; + delegate.EndEdit(); + } + } + if (movingCurve == -1 && overCurve != -1 && ImGui::IsMouseClicked(0) && selection.empty() && !selectingQuad) + { + movingCurve = overCurve; + delegate.BeginEdit(overCurve); + } + + // quad selection + if (selectingQuad) + { + const ImVec2 bmin = ImMin(quadSelection, io.MousePos); + const ImVec2 bmax = ImMax(quadSelection, io.MousePos); + draw_list->AddRectFilled(bmin, bmax, 0x40FF0000, 1.f); + draw_list->AddRect(bmin, bmax, 0xFFFF0000, 1.f); + const ImRect selectionQuad(bmin, bmax); + if (!io.MouseDown[0]) + { + if (!io.KeyShift) + selection.clear(); + // select everythnig is quad + for (size_t c = 0; c < curveCount; c++) + { + if (!delegate.IsVisible(c)) + continue; + + const size_t ptCount = delegate.GetPointCount(c); + if (ptCount < 1) + continue; + + const ImVec2* pts = delegate.GetPoints(c); + for (size_t p = 0; p < ptCount; p++) + { + const ImVec2 center = pointToRange(pts[p]) * viewSize + offset; + if (selectionQuad.Contains(center)) + selection.insert({ int(c), int(p) }); + } + } + // done + selectingQuad = false; + } + } + if (!overCurveOrPoint && ImGui::IsMouseClicked(0) && !selectingQuad && movingCurve == -1 && !overSelectedPoint && container.Contains(io.MousePos)) + { + selectingQuad = true; + quadSelection = io.MousePos; + } + if (clippingRect) + draw_list->PopClipRect(); + + ImGui::EndChildFrame(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(1); + + if (selectedPoints) + { + selectedPoints->resize(int(selection.size())); + int index = 0; + for (auto& point : selection) + (*selectedPoints)[index++] = point; + } + return ret; + } +} diff --git a/editor/ImGui/Source/ImGuizmo/ImCurveEdit.h b/editor/ImGui/Source/ImGuizmo/ImCurveEdit.h new file mode 100644 index 000000000..4863cbfdb --- /dev/null +++ b/editor/ImGui/Source/ImGuizmo/ImCurveEdit.h @@ -0,0 +1,80 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#pragma once +#include +#include "../../Source/imgui.h" + +struct ImRect; + +namespace ImCurveEdit +{ + enum CurveType + { + CurveNone, + CurveDiscrete, + CurveLinear, + CurveSmooth, + CurveBezier, + }; + + struct EditPoint + { + int curveIndex; + int pointIndex; + bool operator <(const EditPoint& other) const + { + if (curveIndex < other.curveIndex) + return true; + if (curveIndex > other.curveIndex) + return false; + + if (pointIndex < other.pointIndex) + return true; + return false; + } + }; + + struct Delegate + { + bool focused = false; + virtual size_t GetCurveCount() = 0; + virtual bool IsVisible(size_t /*curveIndex*/) { return true; } + virtual CurveType GetCurveType(size_t /*curveIndex*/) const { return CurveLinear; } + virtual ImVec2& GetMin() = 0; + virtual ImVec2& GetMax() = 0; + virtual size_t GetPointCount(size_t curveIndex) = 0; + virtual uint32_t GetCurveColor(size_t curveIndex) = 0; + virtual ImVec2* GetPoints(size_t curveIndex) = 0; + virtual int EditPoint(size_t curveIndex, int pointIndex, ImVec2 value) = 0; + virtual void AddPoint(size_t curveIndex, ImVec2 value) = 0; + virtual unsigned int GetBackgroundColor() { return 0xFF202020; } + // handle undo/redo thru this functions + virtual void BeginEdit(int /*index*/) {} + virtual void EndEdit() {} + }; + + int Edit(Delegate& delegate, const ImVec2& size, unsigned int id, const ImRect* clippingRect = NULL, ImVector* selectedPoints = NULL); +} diff --git a/editor/ImGui/Source/ImGuizmo/ImGradient.cpp b/editor/ImGui/Source/ImGuizmo/ImGradient.cpp new file mode 100644 index 000000000..725435f44 --- /dev/null +++ b/editor/ImGui/Source/ImGuizmo/ImGradient.cpp @@ -0,0 +1,116 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include "ImGradient.h" +#include "../../Source/imgui.h" +#include "../../Source/imgui_internal.h" + +namespace ImGradient +{ +#ifndef IMGUI_DEFINE_MATH_OPERATORS + static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } + static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } + static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } + static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } + static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } + static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } +#endif + + static int DrawPoint(ImDrawList* draw_list, ImVec4 color, const ImVec2 size, bool editing, ImVec2 pos) + { + ImGuiIO& io = ImGui::GetIO(); + + ImVec2 p1 = ImLerp(pos, ImVec2(pos + ImVec2(size.x - size.y, 0.f)), color.w) + ImVec2(3, 3); + ImVec2 p2 = ImLerp(pos + ImVec2(size.y, size.y), ImVec2(pos + size), color.w) - ImVec2(3, 3); + ImRect rc(p1, p2); + + color.w = 1.f; + draw_list->AddRectFilled(p1, p2, ImColor(color)); + if (editing) + draw_list->AddRect(p1, p2, 0xFFFFFFFF, 2.f, 15, 2.5f); + else + draw_list->AddRect(p1, p2, 0x80FFFFFF, 2.f, 15, 1.25f); + + if (rc.Contains(io.MousePos)) + { + if (io.MouseClicked[0]) + return 2; + return 1; + } + return 0; + } + + bool Edit(Delegate& delegate, const ImVec2& size, int& selection) + { + bool ret = false; + ImGuiIO& io = ImGui::GetIO(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::BeginChildFrame(137, size); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const ImVec2 offset = ImGui::GetCursorScreenPos(); + + const ImVec4* pts = delegate.GetPoints(); + static int currentSelection = -1; + static int movingPt = -1; + if (currentSelection >= int(delegate.GetPointCount())) + currentSelection = -1; + if (movingPt != -1) + { + ImVec4 current = pts[movingPt]; + current.w += io.MouseDelta.x / size.x; + current.w = ImClamp(current.w, 0.f, 1.f); + delegate.EditPoint(movingPt, current); + ret = true; + if (!io.MouseDown[0]) + movingPt = -1; + } + for (size_t i = 0; i < delegate.GetPointCount(); i++) + { + int ptSel = DrawPoint(draw_list, pts[i], size, i == currentSelection, offset); + if (ptSel == 2) + { + currentSelection = int(i); + ret = true; + } + if (ptSel == 1 && io.MouseDown[0] && movingPt == -1) + { + movingPt = int(i); + } + } + ImRect rc(offset, offset + size); + if (rc.Contains(io.MousePos) && io.MouseDoubleClicked[0]) + { + float t = (io.MousePos.x - offset.x) / size.x; + delegate.AddPoint(delegate.GetPoint(t)); + ret = true; + } + ImGui::EndChildFrame(); + ImGui::PopStyleVar(); + + selection = currentSelection; + return ret; + } +} diff --git a/editor/ImGui/Source/ImGuizmo/ImGradient.h b/editor/ImGui/Source/ImGuizmo/ImGradient.h new file mode 100644 index 000000000..dc0418cb3 --- /dev/null +++ b/editor/ImGui/Source/ImGuizmo/ImGradient.h @@ -0,0 +1,44 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#pragma once +#include + +struct ImVec4; +struct ImVec2; + +namespace ImGradient +{ + struct Delegate + { + virtual size_t GetPointCount() = 0; + virtual ImVec4* GetPoints() = 0; + virtual int EditPoint(int pointIndex, ImVec4 value) = 0; + virtual ImVec4 GetPoint(float t) = 0; + virtual void AddPoint(ImVec4 value) = 0; + }; + + bool Edit(Delegate& delegate, const ImVec2& size, int& selection); +} diff --git a/editor/ImGui/Source/ImGuizmo/ImGuizmo.cpp b/editor/ImGui/Source/ImGuizmo/ImGuizmo.cpp new file mode 100644 index 000000000..60e105cfc --- /dev/null +++ b/editor/ImGui/Source/ImGuizmo/ImGuizmo.cpp @@ -0,0 +1,2975 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#include "../../Source/imgui.h" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "../../Source/imgui_internal.h" +#include "ImGuizmo.h" + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#endif +#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR) +#define _malloca(x) alloca(x) +#define _freea(x) +#endif + +// includes patches for multiview from +// https://github.com/CedricGuillemet/ImGuizmo/issues/15 + +namespace IMGUIZMO_NAMESPACE +{ + static const float ZPI = 3.14159265358979323846f; + static const float RAD2DEG = (180.f / ZPI); + static const float DEG2RAD = (ZPI / 180.f); + const float screenRotateSize = 0.06f; + // scale a bit so translate axis do not touch when in universal + const float rotationDisplayFactor = 1.2f; + + static OPERATION operator&(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + static bool operator!=(OPERATION lhs, int rhs) + { + return static_cast(lhs) != rhs; + } + + static bool Intersects(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) != 0; + } + + // True if lhs contains rhs + static bool Contains(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) == rhs; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // utility and math + + void FPU_MatrixF_x_MatrixF(const float* a, const float* b, float* r) + { + r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; + r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; + r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; + r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; + + r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; + r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; + r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; + r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; + + r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; + r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; + r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; + r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; + + r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; + r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; + r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; + r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; + } + + void Frustum(float left, float right, float bottom, float top, float znear, float zfar, float* m16) + { + float temp, temp2, temp3, temp4; + temp = 2.0f * znear; + temp2 = right - left; + temp3 = top - bottom; + temp4 = zfar - znear; + m16[0] = temp / temp2; + m16[1] = 0.0; + m16[2] = 0.0; + m16[3] = 0.0; + m16[4] = 0.0; + m16[5] = temp / temp3; + m16[6] = 0.0; + m16[7] = 0.0; + m16[8] = (right + left) / temp2; + m16[9] = (top + bottom) / temp3; + m16[10] = (-zfar - znear) / temp4; + m16[11] = -1.0f; + m16[12] = 0.0; + m16[13] = 0.0; + m16[14] = (-temp * zfar) / temp4; + m16[15] = 0.0; + } + + void Perspective(float fovyInDegrees, float aspectRatio, float znear, float zfar, float* m16) + { + float ymax, xmax; + ymax = znear * tanf(fovyInDegrees * DEG2RAD); + xmax = ymax * aspectRatio; + Frustum(-xmax, xmax, -ymax, ymax, znear, zfar, m16); + } + + void Cross(const float* a, const float* b, float* r) + { + r[0] = a[1] * b[2] - a[2] * b[1]; + r[1] = a[2] * b[0] - a[0] * b[2]; + r[2] = a[0] * b[1] - a[1] * b[0]; + } + + float Dot(const float* a, const float* b) + { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + + void Normalize(const float* a, float* r) + { + float il = 1.f / (sqrtf(Dot(a, a)) + FLT_EPSILON); + r[0] = a[0] * il; + r[1] = a[1] * il; + r[2] = a[2] * il; + } + + void LookAt(const float* eye, const float* at, const float* up, float* m16) + { + float X[3], Y[3], Z[3], tmp[3]; + + tmp[0] = eye[0] - at[0]; + tmp[1] = eye[1] - at[1]; + tmp[2] = eye[2] - at[2]; + Normalize(tmp, Z); + Normalize(up, Y); + Cross(Y, Z, tmp); + Normalize(tmp, X); + Cross(Z, X, tmp); + Normalize(tmp, Y); + + m16[0] = X[0]; + m16[1] = Y[0]; + m16[2] = Z[0]; + m16[3] = 0.0f; + m16[4] = X[1]; + m16[5] = Y[1]; + m16[6] = Z[1]; + m16[7] = 0.0f; + m16[8] = X[2]; + m16[9] = Y[2]; + m16[10] = Z[2]; + m16[11] = 0.0f; + m16[12] = -Dot(X, eye); + m16[13] = -Dot(Y, eye); + m16[14] = -Dot(Z, eye); + m16[15] = 1.0f; + } + + template T Clamp(T x, T y, T z) { return ((x < y) ? y : ((x > z) ? z : x)); } + template T max(T x, T y) { return (x > y) ? x : y; } + template T min(T x, T y) { return (x < y) ? x : y; } + template bool IsWithin(T x, T y, T z) { return (x >= y) && (x <= z); } + + struct matrix_t; + struct vec_t + { + public: + float x, y, z, w; + + void Lerp(const vec_t& v, float t) + { + x += (v.x - x) * t; + y += (v.y - y) * t; + z += (v.z - z) * t; + w += (v.w - w) * t; + } + + void Set(float v) { x = y = z = w = v; } + void Set(float _x, float _y, float _z = 0.f, float _w = 0.f) { x = _x; y = _y; z = _z; w = _w; } + + vec_t& operator -= (const vec_t& v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; } + vec_t& operator += (const vec_t& v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; } + vec_t& operator *= (const vec_t& v) { x *= v.x; y *= v.y; z *= v.z; w *= v.w; return *this; } + vec_t& operator *= (float v) { x *= v; y *= v; z *= v; w *= v; return *this; } + + vec_t operator * (float f) const; + vec_t operator - () const; + vec_t operator - (const vec_t& v) const; + vec_t operator + (const vec_t& v) const; + vec_t operator * (const vec_t& v) const; + + const vec_t& operator + () const { return (*this); } + float Length() const { return sqrtf(x * x + y * y + z * z); }; + float LengthSq() const { return (x * x + y * y + z * z); }; + vec_t Normalize() { (*this) *= (1.f / ( Length() > FLT_EPSILON ? Length() : FLT_EPSILON ) ); return (*this); } + vec_t Normalize(const vec_t& v) { this->Set(v.x, v.y, v.z, v.w); this->Normalize(); return (*this); } + vec_t Abs() const; + + void Cross(const vec_t& v) + { + vec_t res; + res.x = y * v.z - z * v.y; + res.y = z * v.x - x * v.z; + res.z = x * v.y - y * v.x; + + x = res.x; + y = res.y; + z = res.z; + w = 0.f; + } + + void Cross(const vec_t& v1, const vec_t& v2) + { + x = v1.y * v2.z - v1.z * v2.y; + y = v1.z * v2.x - v1.x * v2.z; + z = v1.x * v2.y - v1.y * v2.x; + w = 0.f; + } + + float Dot(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w); + } + + float Dot3(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z); + } + + void Transform(const matrix_t& matrix); + void Transform(const vec_t& s, const matrix_t& matrix); + + void TransformVector(const matrix_t& matrix); + void TransformPoint(const matrix_t& matrix); + void TransformVector(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformVector(matrix); } + void TransformPoint(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformPoint(matrix); } + + float& operator [] (size_t index) { return ((float*)&x)[index]; } + const float& operator [] (size_t index) const { return ((float*)&x)[index]; } + bool operator!=(const vec_t& other) const { return memcmp(this, &other, sizeof(vec_t)) != 0; } + }; + + vec_t makeVect(float _x, float _y, float _z = 0.f, float _w = 0.f) { vec_t res; res.x = _x; res.y = _y; res.z = _z; res.w = _w; return res; } + vec_t makeVect(ImVec2 v) { vec_t res; res.x = v.x; res.y = v.y; res.z = 0.f; res.w = 0.f; return res; } + vec_t vec_t::operator * (float f) const { return makeVect(x * f, y * f, z * f, w * f); } + vec_t vec_t::operator - () const { return makeVect(-x, -y, -z, -w); } + vec_t vec_t::operator - (const vec_t& v) const { return makeVect(x - v.x, y - v.y, z - v.z, w - v.w); } + vec_t vec_t::operator + (const vec_t& v) const { return makeVect(x + v.x, y + v.y, z + v.z, w + v.w); } + vec_t vec_t::operator * (const vec_t& v) const { return makeVect(x * v.x, y * v.y, z * v.z, w * v.w); } + vec_t vec_t::Abs() const { return makeVect(fabsf(x), fabsf(y), fabsf(z)); } + + vec_t Normalized(const vec_t& v) { vec_t res; res = v; res.Normalize(); return res; } + vec_t Cross(const vec_t& v1, const vec_t& v2) + { + vec_t res; + res.x = v1.y * v2.z - v1.z * v2.y; + res.y = v1.z * v2.x - v1.x * v2.z; + res.z = v1.x * v2.y - v1.y * v2.x; + res.w = 0.f; + return res; + } + + float Dot(const vec_t& v1, const vec_t& v2) + { + return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); + } + + vec_t BuildPlan(const vec_t& p_point1, const vec_t& p_normal) + { + vec_t normal, res; + normal.Normalize(p_normal); + res.w = normal.Dot(p_point1); + res.x = normal.x; + res.y = normal.y; + res.z = normal.z; + return res; + } + + struct matrix_t + { + public: + + union + { + float m[4][4]; + float m16[16]; + struct + { + vec_t right, up, dir, position; + } v; + vec_t component[4]; + }; + + operator float* () { return m16; } + operator const float* () const { return m16; } + void Translation(float _x, float _y, float _z) { this->Translation(makeVect(_x, _y, _z)); } + + void Translation(const vec_t& vt) + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(vt.x, vt.y, vt.z, 1.f); + } + + void Scale(float _x, float _y, float _z) + { + v.right.Set(_x, 0.f, 0.f, 0.f); + v.up.Set(0.f, _y, 0.f, 0.f); + v.dir.Set(0.f, 0.f, _z, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Scale(const vec_t& s) { Scale(s.x, s.y, s.z); } + + matrix_t& operator *= (const matrix_t& mat) + { + matrix_t tmpMat; + tmpMat = *this; + tmpMat.Multiply(mat); + *this = tmpMat; + return *this; + } + matrix_t operator * (const matrix_t& mat) const + { + matrix_t matT; + matT.Multiply(*this, mat); + return matT; + } + + void Multiply(const matrix_t& matrix) + { + matrix_t tmp; + tmp = *this; + + FPU_MatrixF_x_MatrixF((float*)&tmp, (float*)&matrix, (float*)this); + } + + void Multiply(const matrix_t& m1, const matrix_t& m2) + { + FPU_MatrixF_x_MatrixF((float*)&m1, (float*)&m2, (float*)this); + } + + float GetDeterminant() const + { + return m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] - + m[0][2] * m[1][1] * m[2][0] - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1]; + } + + float Inverse(const matrix_t& srcMatrix, bool affine = false); + void SetToIdentity() + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Transpose() + { + matrix_t tmpm; + for (int l = 0; l < 4; l++) + { + for (int c = 0; c < 4; c++) + { + tmpm.m[l][c] = m[c][l]; + } + } + (*this) = tmpm; + } + + void RotationAxis(const vec_t& axis, float angle); + + void OrthoNormalize() + { + v.right.Normalize(); + v.up.Normalize(); + v.dir.Normalize(); + } + }; + + void vec_t::Transform(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + w * matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + w * matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + w * matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + w * matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::Transform(const vec_t& s, const matrix_t& matrix) + { + *this = s; + Transform(matrix); + } + + void vec_t::TransformPoint(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::TransformVector(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + float matrix_t::Inverse(const matrix_t& srcMatrix, bool affine) + { + float det = 0; + + if (affine) + { + det = GetDeterminant(); + float s = 1 / det; + m[0][0] = (srcMatrix.m[1][1] * srcMatrix.m[2][2] - srcMatrix.m[1][2] * srcMatrix.m[2][1]) * s; + m[0][1] = (srcMatrix.m[2][1] * srcMatrix.m[0][2] - srcMatrix.m[2][2] * srcMatrix.m[0][1]) * s; + m[0][2] = (srcMatrix.m[0][1] * srcMatrix.m[1][2] - srcMatrix.m[0][2] * srcMatrix.m[1][1]) * s; + m[1][0] = (srcMatrix.m[1][2] * srcMatrix.m[2][0] - srcMatrix.m[1][0] * srcMatrix.m[2][2]) * s; + m[1][1] = (srcMatrix.m[2][2] * srcMatrix.m[0][0] - srcMatrix.m[2][0] * srcMatrix.m[0][2]) * s; + m[1][2] = (srcMatrix.m[0][2] * srcMatrix.m[1][0] - srcMatrix.m[0][0] * srcMatrix.m[1][2]) * s; + m[2][0] = (srcMatrix.m[1][0] * srcMatrix.m[2][1] - srcMatrix.m[1][1] * srcMatrix.m[2][0]) * s; + m[2][1] = (srcMatrix.m[2][0] * srcMatrix.m[0][1] - srcMatrix.m[2][1] * srcMatrix.m[0][0]) * s; + m[2][2] = (srcMatrix.m[0][0] * srcMatrix.m[1][1] - srcMatrix.m[0][1] * srcMatrix.m[1][0]) * s; + m[3][0] = -(m[0][0] * srcMatrix.m[3][0] + m[1][0] * srcMatrix.m[3][1] + m[2][0] * srcMatrix.m[3][2]); + m[3][1] = -(m[0][1] * srcMatrix.m[3][0] + m[1][1] * srcMatrix.m[3][1] + m[2][1] * srcMatrix.m[3][2]); + m[3][2] = -(m[0][2] * srcMatrix.m[3][0] + m[1][2] * srcMatrix.m[3][1] + m[2][2] * srcMatrix.m[3][2]); + } + else + { + // transpose matrix + float src[16]; + for (int i = 0; i < 4; ++i) + { + src[i] = srcMatrix.m16[i * 4]; + src[i + 4] = srcMatrix.m16[i * 4 + 1]; + src[i + 8] = srcMatrix.m16[i * 4 + 2]; + src[i + 12] = srcMatrix.m16[i * 4 + 3]; + } + + // calculate pairs for first 8 elements (cofactors) + float tmp[12]; // temp array for pairs + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + // calculate first 8 elements (cofactors) + m16[0] = (tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]) - (tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]); + m16[1] = (tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]) - (tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]); + m16[2] = (tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]) - (tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]); + m16[3] = (tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]) - (tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]); + m16[4] = (tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]) - (tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]); + m16[5] = (tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]) - (tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]); + m16[6] = (tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]) - (tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]); + m16[7] = (tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]) - (tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]); + + // calculate pairs for second 8 elements (cofactors) + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + // calculate second 8 elements (cofactors) + m16[8] = (tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]) - (tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]); + m16[9] = (tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]) - (tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]); + m16[10] = (tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]) - (tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]); + m16[11] = (tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]) - (tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]); + m16[12] = (tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]) - (tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]); + m16[13] = (tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]) - (tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]); + m16[14] = (tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]) - (tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]); + m16[15] = (tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]) - (tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]); + + // calculate determinant + det = src[0] * m16[0] + src[1] * m16[1] + src[2] * m16[2] + src[3] * m16[3]; + + // calculate matrix inverse + float invdet = 1 / det; + for (int j = 0; j < 16; ++j) + { + m16[j] *= invdet; + } + } + + return det; + } + + void matrix_t::RotationAxis(const vec_t& axis, float angle) + { + float length2 = axis.LengthSq(); + if (length2 < FLT_EPSILON) + { + SetToIdentity(); + return; + } + + vec_t n = axis * (1.f / sqrtf(length2)); + float s = sinf(angle); + float c = cosf(angle); + float k = 1.f - c; + + float xx = n.x * n.x * k + c; + float yy = n.y * n.y * k + c; + float zz = n.z * n.z * k + c; + float xy = n.x * n.y * k; + float yz = n.y * n.z * k; + float zx = n.z * n.x * k; + float xs = n.x * s; + float ys = n.y * s; + float zs = n.z * s; + + m[0][0] = xx; + m[0][1] = xy + zs; + m[0][2] = zx - ys; + m[0][3] = 0.f; + m[1][0] = xy - zs; + m[1][1] = yy; + m[1][2] = yz + xs; + m[1][3] = 0.f; + m[2][0] = zx + ys; + m[2][1] = yz - xs; + m[2][2] = zz; + m[2][3] = 0.f; + m[3][0] = 0.f; + m[3][1] = 0.f; + m[3][2] = 0.f; + m[3][3] = 1.f; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + enum MOVETYPE + { + MT_NONE, + MT_MOVE_X, + MT_MOVE_Y, + MT_MOVE_Z, + MT_MOVE_YZ, + MT_MOVE_ZX, + MT_MOVE_XY, + MT_MOVE_SCREEN, + MT_ROTATE_X, + MT_ROTATE_Y, + MT_ROTATE_Z, + MT_ROTATE_SCREEN, + MT_SCALE_X, + MT_SCALE_Y, + MT_SCALE_Z, + MT_SCALE_XYZ + }; + + static bool IsTranslateType(int type) + { + return type >= MT_MOVE_X && type <= MT_MOVE_SCREEN; + } + + static bool IsRotateType(int type) + { + return type >= MT_ROTATE_X && type <= MT_ROTATE_SCREEN; + } + + static bool IsScaleType(int type) + { + return type >= MT_SCALE_X && type <= MT_SCALE_XYZ; + } + + // Matches MT_MOVE_AB order + static const OPERATION TRANSLATE_PLANS[3] = { TRANSLATE_Y | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Y }; + + Style::Style() + { + // default values + TranslationLineThickness = 3.0f; + TranslationLineArrowSize = 6.0f; + RotationLineThickness = 2.0f; + RotationOuterLineThickness = 3.0f; + ScaleLineThickness = 3.0f; + ScaleLineCircleSize = 6.0f; + HatchedAxisLineThickness = 6.0f; + CenterCircleSize = 6.0f; + + // initialize default colors + Colors[DIRECTION_X] = ImVec4(0.666f, 0.000f, 0.000f, 1.000f); + Colors[DIRECTION_Y] = ImVec4(0.000f, 0.666f, 0.000f, 1.000f); + Colors[DIRECTION_Z] = ImVec4(0.000f, 0.000f, 0.666f, 1.000f); + Colors[PLANE_X] = ImVec4(0.666f, 0.000f, 0.000f, 0.380f); + Colors[PLANE_Y] = ImVec4(0.000f, 0.666f, 0.000f, 0.380f); + Colors[PLANE_Z] = ImVec4(0.000f, 0.000f, 0.666f, 0.380f); + Colors[SELECTION] = ImVec4(1.000f, 0.500f, 0.062f, 0.541f); + Colors[INACTIVE] = ImVec4(0.600f, 0.600f, 0.600f, 0.600f); + Colors[TRANSLATION_LINE] = ImVec4(0.666f, 0.666f, 0.666f, 0.666f); + Colors[SCALE_LINE] = ImVec4(0.250f, 0.250f, 0.250f, 1.000f); + Colors[ROTATION_USING_BORDER] = ImVec4(1.000f, 0.500f, 0.062f, 1.000f); + Colors[ROTATION_USING_FILL] = ImVec4(1.000f, 0.500f, 0.062f, 0.500f); + Colors[HATCHED_AXIS_LINES] = ImVec4(0.000f, 0.000f, 0.000f, 0.500f); + Colors[TEXT] = ImVec4(1.000f, 1.000f, 1.000f, 1.000f); + Colors[TEXT_SHADOW] = ImVec4(0.000f, 0.000f, 0.000f, 1.000f); + } + + struct Context + { + Context() : mbUsing(false), mbEnable(true), mbUsingBounds(false) + { + } + + ImDrawList* mDrawList; + Style mStyle; + + MODE mMode; + matrix_t mViewMat; + matrix_t mProjectionMat; + matrix_t mModel; + matrix_t mModelLocal; // orthonormalized model + matrix_t mModelInverse; + matrix_t mModelSource; + matrix_t mModelSourceInverse; + matrix_t mMVP; + matrix_t mMVPLocal; // MVP with full model matrix whereas mMVP's model matrix might only be translation in case of World space edition + matrix_t mViewProjection; + + vec_t mModelScaleOrigin; + vec_t mCameraEye; + vec_t mCameraRight; + vec_t mCameraDir; + vec_t mCameraUp; + vec_t mRayOrigin; + vec_t mRayVector; + + float mRadiusSquareCenter; + ImVec2 mScreenSquareCenter; + ImVec2 mScreenSquareMin; + ImVec2 mScreenSquareMax; + + float mScreenFactor; + vec_t mRelativeOrigin; + + bool mbUsing; + bool mbEnable; + bool mbMouseOver; + bool mReversed; // reversed projection matrix + + // translation + vec_t mTranslationPlan; + vec_t mTranslationPlanOrigin; + vec_t mMatrixOrigin; + vec_t mTranslationLastDelta; + + // rotation + vec_t mRotationVectorSource; + float mRotationAngle; + float mRotationAngleOrigin; + //vec_t mWorldToLocalAxis; + + // scale + vec_t mScale; + vec_t mScaleValueOrigin; + vec_t mScaleLast; + float mSaveMousePosx; + + // save axis factor when using gizmo + bool mBelowAxisLimit[3]; + bool mBelowPlaneLimit[3]; + float mAxisFactor[3]; + + // bounds stretching + vec_t mBoundsPivot; + vec_t mBoundsAnchor; + vec_t mBoundsPlan; + vec_t mBoundsLocalPivot; + int mBoundsBestAxis; + int mBoundsAxis[2]; + bool mbUsingBounds; + matrix_t mBoundsMatrix; + + // + int mCurrentOperation; + + float mX = 0.f; + float mY = 0.f; + float mWidth = 0.f; + float mHeight = 0.f; + float mXMax = 0.f; + float mYMax = 0.f; + float mDisplayRatio = 1.f; + + bool mIsOrthographic = false; + + int mActualID = -1; + int mEditingID = -1; + OPERATION mOperation = OPERATION(-1); + + bool mAllowAxisFlip = true; + float mGizmoSizeClipSpace = 0.1f; + }; + + static Context gContext; + + static const vec_t directionUnary[3] = { makeVect(1.f, 0.f, 0.f), makeVect(0.f, 1.f, 0.f), makeVect(0.f, 0.f, 1.f) }; + static const char* translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f", + "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %5.3f", "X : %5.3f Y : %5.3f", + "X : %5.3f Y : %5.3f Z : %5.3f" }; + static const char* scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" }; + static const char* rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" }; + static const int translationInfoIndex[] = { 0,0,0, 1,0,0, 2,0,0, 1,2,0, 0,2,0, 0,1,0, 0,1,2 }; + static const float quadMin = 0.5f; + static const float quadMax = 0.8f; + static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin }; + static const int halfCircleSegmentCount = 64; + static const float snapTension = 0.5f; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion); + static int GetRotateType(OPERATION op); + static int GetScaleType(OPERATION op); + + Style& GetStyle() + { + return gContext.mStyle; + } + + static ImU32 GetColorU32(int idx) + { + IM_ASSERT(idx < COLOR::COUNT); + return ImGui::ColorConvertFloat4ToU32(gContext.mStyle.Colors[idx]); + } + + static ImVec2 worldToPos(const vec_t& worldPos, const matrix_t& mat, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + vec_t trans; + trans.TransformPoint(worldPos, mat); + trans *= 0.5f / trans.w; + trans += makeVect(0.5f, 0.5f); + trans.y = 1.f - trans.y; + trans.x *= size.x; + trans.y *= size.y; + trans.x += position.x; + trans.y += position.y; + return ImVec2(trans.x, trans.y); + } + + static void ComputeCameraRay(vec_t& rayOrigin, vec_t& rayDir, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + ImGuiIO& io = ImGui::GetIO(); + + matrix_t mViewProjInverse; + mViewProjInverse.Inverse(gContext.mViewMat * gContext.mProjectionMat); + + const float mox = ((io.MousePos.x - position.x) / size.x) * 2.f - 1.f; + const float moy = (1.f - ((io.MousePos.y - position.y) / size.y)) * 2.f - 1.f; + + const float zNear = gContext.mReversed ? (1.f - FLT_EPSILON) : 0.f; + const float zFar = gContext.mReversed ? 0.f : (1.f - FLT_EPSILON); + + rayOrigin.Transform(makeVect(mox, moy, zNear, 1.f), mViewProjInverse); + rayOrigin *= 1.f / rayOrigin.w; + vec_t rayEnd; + rayEnd.Transform(makeVect(mox, moy, zFar, 1.f), mViewProjInverse); + rayEnd *= 1.f / rayEnd.w; + rayDir = Normalized(rayEnd - rayOrigin); + } + + static float GetSegmentLengthClipSpace(const vec_t& start, const vec_t& end, const bool localCoordinates = false) + { + vec_t startOfSegment = start; + const matrix_t& mvp = localCoordinates ? gContext.mMVPLocal : gContext.mMVP; + startOfSegment.TransformPoint(mvp); + if (fabsf(startOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + startOfSegment *= 1.f / startOfSegment.w; + } + + vec_t endOfSegment = end; + endOfSegment.TransformPoint(mvp); + if (fabsf(endOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + endOfSegment *= 1.f / endOfSegment.w; + } + + vec_t clipSpaceAxis = endOfSegment - startOfSegment; + clipSpaceAxis.y /= gContext.mDisplayRatio; + float segmentLengthInClipSpace = sqrtf(clipSpaceAxis.x * clipSpaceAxis.x + clipSpaceAxis.y * clipSpaceAxis.y); + return segmentLengthInClipSpace; + } + + static float GetParallelogram(const vec_t& ptO, const vec_t& ptA, const vec_t& ptB) + { + vec_t pts[] = { ptO, ptA, ptB }; + for (unsigned int i = 0; i < 3; i++) + { + pts[i].TransformPoint(gContext.mMVP); + if (fabsf(pts[i].w) > FLT_EPSILON) // check for axis aligned with camera direction + { + pts[i] *= 1.f / pts[i].w; + } + } + vec_t segA = pts[1] - pts[0]; + vec_t segB = pts[2] - pts[0]; + segA.y /= gContext.mDisplayRatio; + segB.y /= gContext.mDisplayRatio; + vec_t segAOrtho = makeVect(-segA.y, segA.x); + segAOrtho.Normalize(); + float dt = segAOrtho.Dot3(segB); + float surface = sqrtf(segA.x * segA.x + segA.y * segA.y) * fabsf(dt); + return surface; + } + + inline vec_t PointOnSegment(const vec_t& point, const vec_t& vertPos1, const vec_t& vertPos2) + { + vec_t c = point - vertPos1; + vec_t V; + + V.Normalize(vertPos2 - vertPos1); + float d = (vertPos2 - vertPos1).Length(); + float t = V.Dot3(c); + + if (t < 0.f) + { + return vertPos1; + } + + if (t > d) + { + return vertPos2; + } + + return vertPos1 + V * t; + } + + static float IntersectRayPlane(const vec_t& rOrigin, const vec_t& rVector, const vec_t& plan) + { + const float numer = plan.Dot3(rOrigin) - plan.w; + const float denom = plan.Dot3(rVector); + + if (fabsf(denom) < FLT_EPSILON) // normal is orthogonal to vector, cant intersect + { + return -1.0f; + } + + return -(numer / denom); + } + + static float DistanceToPlane(const vec_t& point, const vec_t& plan) + { + return plan.Dot3(point) + plan.w; + } + + static bool IsInContextRect(ImVec2 p) + { + return IsWithin(p.x, gContext.mX, gContext.mXMax) && IsWithin(p.y, gContext.mY, gContext.mYMax); + } + + static bool IsHoveringWindow() + { + ImGuiContext& g = *ImGui::GetCurrentContext(); + ImGuiWindow* window = ImGui::FindWindowByName(gContext.mDrawList->_OwnerName); + if (g.HoveredWindow == window) // Mouse hovering drawlist window + return true; + if (g.HoveredWindow != NULL) // Any other window is hovered + return false; + if (ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max, false)) // Hovering drawlist window rect, while no other window is hovered (for _NoInputs windows) + return true; + return false; + } + + void SetRect(float x, float y, float width, float height) + { + gContext.mX = x; + gContext.mY = y; + gContext.mWidth = width; + gContext.mHeight = height; + gContext.mXMax = gContext.mX + gContext.mWidth; + gContext.mYMax = gContext.mY + gContext.mXMax; + gContext.mDisplayRatio = width / height; + } + + void SetOrthographic(bool isOrthographic) + { + gContext.mIsOrthographic = isOrthographic; + } + + void SetDrawlist(ImDrawList* drawlist) + { + gContext.mDrawList = drawlist ? drawlist : ImGui::GetWindowDrawList(); + } + + void SetImGuiContext(ImGuiContext* ctx) + { + ImGui::SetCurrentContext(ctx); + } + + void BeginFrame() + { + const ImU32 flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus; + +#ifdef IMGUI_HAS_VIEWPORT + ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos); +#else + ImGuiIO& io = ImGui::GetIO(); + ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::SetNextWindowPos(ImVec2(0, 0)); +#endif + + ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); + ImGui::PushStyleColor(ImGuiCol_Border, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + + ImGui::Begin("gizmo", NULL, flags); + gContext.mDrawList = ImGui::GetWindowDrawList(); + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + } + + bool IsUsing() + { + return gContext.mbUsing || gContext.mbUsingBounds; + } + + bool IsOver() + { + return (Intersects(gContext.mOperation, TRANSLATE) && GetMoveType(gContext.mOperation, NULL) != MT_NONE) || + (Intersects(gContext.mOperation, ROTATE) && GetRotateType(gContext.mOperation) != MT_NONE) || + (Intersects(gContext.mOperation, SCALE) && GetScaleType(gContext.mOperation) != MT_NONE) || IsUsing(); + } + + bool IsOver(OPERATION op) + { + if(IsUsing()) + { + return true; + } + if(Intersects(op, SCALE) && GetScaleType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, ROTATE) && GetRotateType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, TRANSLATE) && GetMoveType(op, NULL) != MT_NONE) + { + return true; + } + return false; + } + + void Enable(bool enable) + { + gContext.mbEnable = enable; + if (!enable) + { + gContext.mbUsing = false; + gContext.mbUsingBounds = false; + } + } + + static void ComputeContext(const float* view, const float* projection, float* matrix, MODE mode) + { + gContext.mMode = mode; + gContext.mViewMat = *(matrix_t*)view; + gContext.mProjectionMat = *(matrix_t*)projection; + gContext.mbMouseOver = IsHoveringWindow(); + + gContext.mModelLocal = *(matrix_t*)matrix; + gContext.mModelLocal.OrthoNormalize(); + + if (mode == LOCAL) + { + gContext.mModel = gContext.mModelLocal; + } + else + { + gContext.mModel.Translation(((matrix_t*)matrix)->v.position); + } + gContext.mModelSource = *(matrix_t*)matrix; + gContext.mModelScaleOrigin.Set(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + + gContext.mModelInverse.Inverse(gContext.mModel); + gContext.mModelSourceInverse.Inverse(gContext.mModelSource); + gContext.mViewProjection = gContext.mViewMat * gContext.mProjectionMat; + gContext.mMVP = gContext.mModel * gContext.mViewProjection; + gContext.mMVPLocal = gContext.mModelLocal * gContext.mViewProjection; + + matrix_t viewInverse; + viewInverse.Inverse(gContext.mViewMat); + gContext.mCameraDir = viewInverse.v.dir; + gContext.mCameraEye = viewInverse.v.position; + gContext.mCameraRight = viewInverse.v.right; + gContext.mCameraUp = viewInverse.v.up; + + // projection reverse + vec_t nearPos, farPos; + nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat); + farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat); + + gContext.mReversed = (nearPos.z/nearPos.w) > (farPos.z / farPos.w); + + // compute scale from the size of camera right vector projected on screen at the matrix position + vec_t pointRight = viewInverse.v.right; + pointRight.TransformPoint(gContext.mViewProjection); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / (pointRight.x / pointRight.w - gContext.mMVP.v.position.x / gContext.mMVP.v.position.w); + + vec_t rightViewInverse = viewInverse.v.right; + rightViewInverse.TransformVector(gContext.mModelInverse); + float rightLength = GetSegmentLengthClipSpace(makeVect(0.f, 0.f), rightViewInverse); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength; + + ImVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP); + gContext.mScreenSquareCenter = centerSSpace; + gContext.mScreenSquareMin = ImVec2(centerSSpace.x - 10.f, centerSSpace.y - 10.f); + gContext.mScreenSquareMax = ImVec2(centerSSpace.x + 10.f, centerSSpace.y + 10.f); + + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector); + } + + static void ComputeColors(ImU32* colors, int type, OPERATION operation) + { + if (gContext.mbEnable) + { + ImU32 selectionColor = GetColorU32(SELECTION); + + switch (operation) + { + case TRANSLATE: + colors[0] = (type == MT_MOVE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_MOVE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + colors[i + 4] = (type == (int)(MT_MOVE_YZ + i)) ? selectionColor : GetColorU32(PLANE_X + i); + colors[i + 4] = (type == MT_MOVE_SCREEN) ? selectionColor : colors[i + 4]; + } + break; + case ROTATE: + colors[0] = (type == MT_ROTATE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_ROTATE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + } + break; + case SCALEU: + case SCALE: + colors[0] = (type == MT_SCALE_XYZ) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_SCALE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + } + break; + // note: this internal function is only called with three possible values for operation + default: + break; + } + } + else + { + ImU32 inactiveColor = GetColorU32(INACTIVE); + for (int i = 0; i < 7; i++) + { + colors[i] = inactiveColor; + } + } + } + + static void ComputeTripodAxisAndVisibility(const int axisIndex, vec_t& dirAxis, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit, const bool localCoordinates = false) + { + dirAxis = directionUnary[axisIndex]; + dirPlaneX = directionUnary[(axisIndex + 1) % 3]; + dirPlaneY = directionUnary[(axisIndex + 2) % 3]; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + // when using, use stored factors so the gizmo doesn't flip when we translate + belowAxisLimit = gContext.mBelowAxisLimit[axisIndex]; + belowPlaneLimit = gContext.mBelowPlaneLimit[axisIndex]; + + dirAxis *= gContext.mAxisFactor[axisIndex]; + dirPlaneX *= gContext.mAxisFactor[(axisIndex + 1) % 3]; + dirPlaneY *= gContext.mAxisFactor[(axisIndex + 2) % 3]; + } + else + { + // new method + float lenDir = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis, localCoordinates); + float lenDirMinus = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirAxis, localCoordinates); + + float lenDirPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneX, localCoordinates); + float lenDirMinusPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneX, localCoordinates); + + float lenDirPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneY, localCoordinates); + float lenDirMinusPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneY, localCoordinates); + + // For readability + bool & allowFlip = gContext.mAllowAxisFlip; + float mulAxis = (allowFlip && lenDir < lenDirMinus&& fabsf(lenDir - lenDirMinus) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisX = (allowFlip && lenDirPlaneX < lenDirMinusPlaneX&& fabsf(lenDirPlaneX - lenDirMinusPlaneX) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisY = (allowFlip && lenDirPlaneY < lenDirMinusPlaneY&& fabsf(lenDirPlaneY - lenDirMinusPlaneY) > FLT_EPSILON) ? -1.f : 1.f; + dirAxis *= mulAxis; + dirPlaneX *= mulAxisX; + dirPlaneY *= mulAxisY; + + // for axis + float axisLengthInClipSpace = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis * gContext.mScreenFactor, localCoordinates); + + float paraSurf = GetParallelogram(makeVect(0.f, 0.f, 0.f), dirPlaneX * gContext.mScreenFactor, dirPlaneY * gContext.mScreenFactor); + belowPlaneLimit = (paraSurf > 0.0025f); + belowAxisLimit = (axisLengthInClipSpace > 0.02f); + + // and store values + gContext.mAxisFactor[axisIndex] = mulAxis; + gContext.mAxisFactor[(axisIndex + 1) % 3] = mulAxisX; + gContext.mAxisFactor[(axisIndex + 2) % 3] = mulAxisY; + gContext.mBelowAxisLimit[axisIndex] = belowAxisLimit; + gContext.mBelowPlaneLimit[axisIndex] = belowPlaneLimit; + } + } + + static void ComputeSnap(float* value, float snap) + { + if (snap <= FLT_EPSILON) + { + return; + } + + float modulo = fmodf(*value, snap); + float moduloRatio = fabsf(modulo) / snap; + if (moduloRatio < snapTension) + { + *value -= modulo; + } + else if (moduloRatio > (1.f - snapTension)) + { + *value = *value - modulo + snap * ((*value < 0.f) ? -1.f : 1.f); + } + } + static void ComputeSnap(vec_t& value, const float* snap) + { + for (int i = 0; i < 3; i++) + { + ComputeSnap(&value[i], snap[i]); + } + } + + static float ComputeAngleOnPlan() + { + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = Normalized(gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position); + + vec_t perpendicularVector; + perpendicularVector.Cross(gContext.mRotationVectorSource, gContext.mTranslationPlan); + perpendicularVector.Normalize(); + float acosAngle = Clamp(Dot(localPos, gContext.mRotationVectorSource), -1.f, 1.f); + float angle = acosf(acosAngle); + angle *= (Dot(localPos, perpendicularVector) < 0.f) ? 1.f : -1.f; + return angle; + } + + static void DrawRotationGizmo(OPERATION op, int type) + { + if(!Intersects(op, ROTATE)) + { + return; + } + ImDrawList* drawList = gContext.mDrawList; + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, ROTATE); + + vec_t cameraToModelNormalized; + if (gContext.mIsOrthographic) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)&gContext.mViewMat); + cameraToModelNormalized = -viewInverse.v.dir; + } + else + { + cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + } + + cameraToModelNormalized.TransformVector(gContext.mModelInverse); + + gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight; + + bool hasRSC = Intersects(op, ROTATE_SCREEN); + for (int axis = 0; axis < 3; axis++) + { + if(!Intersects(op, static_cast(ROTATE_Z >> axis))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_ROTATE_Z - axis); + const int circleMul = (hasRSC && !usingAxis ) ? 1 : 2; + + ImVec2* circlePos = (ImVec2*)alloca(sizeof(ImVec2) * (circleMul * halfCircleSegmentCount + 1)); + + float angleStart = atan2f(cameraToModelNormalized[(4 - axis) % 3], cameraToModelNormalized[(3 - axis) % 3]) + ZPI * 0.5f; + + for (int i = 0; i < circleMul * halfCircleSegmentCount + 1; i++) + { + float ng = angleStart + (float)circleMul * ZPI * ((float)i / (float)halfCircleSegmentCount); + vec_t axisPos = makeVect(cosf(ng), sinf(ng), 0.f); + vec_t pos = makeVect(axisPos[axis], axisPos[(axis + 1) % 3], axisPos[(axis + 2) % 3]) * gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos, gContext.mMVP); + } + if (!gContext.mbUsing || usingAxis) + { + drawList->AddPolyline(circlePos, circleMul* halfCircleSegmentCount + 1, colors[3 - axis], false, gContext.mStyle.RotationLineThickness); + } + + float radiusAxis = sqrtf((ImLengthSqr(worldToPos(gContext.mModel.v.position, gContext.mViewProjection) - circlePos[0]))); + if (radiusAxis > gContext.mRadiusSquareCenter) + { + gContext.mRadiusSquareCenter = radiusAxis; + } + } + if(hasRSC && (!gContext.mbUsing || type == MT_ROTATE_SCREEN)) + { + drawList->AddCircle(worldToPos(gContext.mModel.v.position, gContext.mViewProjection), gContext.mRadiusSquareCenter, colors[0], 64, gContext.mStyle.RotationOuterLineThickness); + } + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(type)) + { + ImVec2 circlePos[halfCircleSegmentCount + 1]; + + circlePos[0] = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + for (unsigned int i = 1; i < halfCircleSegmentCount; i++) + { + float ng = gContext.mRotationAngle * ((float)(i - 1) / (float)(halfCircleSegmentCount - 1)); + matrix_t rotateVectorMatrix; + rotateVectorMatrix.RotationAxis(gContext.mTranslationPlan, ng); + vec_t pos; + pos.TransformPoint(gContext.mRotationVectorSource, rotateVectorMatrix); + pos *= gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos + gContext.mModel.v.position, gContext.mViewProjection); + } + drawList->AddConvexPolyFilled(circlePos, halfCircleSegmentCount, GetColorU32(ROTATION_USING_FILL)); + drawList->AddPolyline(circlePos, halfCircleSegmentCount, GetColorU32(ROTATION_USING_BORDER), true, gContext.mStyle.RotationLineThickness); + + ImVec2 destinationPosOnScreen = circlePos[1]; + char tmps[512]; + ImFormatString(tmps, sizeof(tmps), rotationInfoMask[type - MT_ROTATE_X], (gContext.mRotationAngle / ZPI) * 180.f, gContext.mRotationAngle); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static void DrawHatchedAxis(const vec_t& axis) + { + if (gContext.mStyle.HatchedAxisLineThickness <= 0.0f) + { + return; + } + + for (int j = 1; j < 10; j++) + { + ImVec2 baseSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2) * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2 + 1) * gContext.mScreenFactor, gContext.mMVP); + gContext.mDrawList->AddLine(baseSSpace2, worldDirSSpace2, GetColorU32(HATCHED_AXIS_LINES), gContext.mStyle.HatchedAxisLineThickness); + } + } + + static void DrawScaleGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + + if(!Intersects(op, SCALE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALE); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVP); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + ImU32 scaleLineColor = GetColorU32(SCALE_LINE); + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, scaleLineColor, gContext.mStyle.ScaleLineThickness); + drawList->AddCircleFilled(worldDirSSpaceNoScale, gContext.mStyle.ScaleLineCircleSize, scaleLineColor); + } + + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.ScaleLineThickness); + } + drawList->AddCircleFilled(worldDirSSpace, gContext.mStyle.ScaleLineCircleSize, colors[i + 1]); + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis * scaleDisplay[i]); + } + } + } + } + + // draw screen cirle + drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) + { + //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + + static void DrawScaleUniveralGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + + if (!Intersects(op, SCALEU)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALEU); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + //ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + //ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVPLocal); + +#if 0 + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, IM_COL32(0x40, 0x40, 0x40, 0xFF), 3.f); + drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, IM_COL32(0x40, 0x40, 0x40, 0xFF)); + } + /* + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + } + */ +#endif + drawList->AddCircleFilled(worldDirSSpace, 12.f, colors[i + 1]); + } + } + } + + // draw screen cirle + drawList->AddCircle(gContext.mScreenSquareCenter, 20.f, colors[0], 32, gContext.mStyle.CenterCircleSize); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) + { + //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static void DrawTranslationGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + if (!drawList) + { + return; + } + + if(!Intersects(op, TRANSLATE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, TRANSLATE); + + const ImVec2 origin = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + + // draw + bool belowAxisLimit = false; + bool belowPlaneLimit = false; + for (int i = 0; i < 3; ++i) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_X + i)) + { + // draw axis + if (belowAxisLimit && Intersects(op, static_cast(TRANSLATE_X << i))) + { + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos(dirAxis * gContext.mScreenFactor, gContext.mMVP); + + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.TranslationLineThickness); + + // Arrow head begin + ImVec2 dir(origin - worldDirSSpace); + + float d = sqrtf(ImLengthSqr(dir)); + dir /= d; // Normalize + dir *= gContext.mStyle.TranslationLineArrowSize; + + ImVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector + ImVec2 a(worldDirSSpace + dir); + drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, colors[i + 1]); + // Arrow head end + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis); + } + } + } + // draw plane + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_YZ + i)) + { + if (belowPlaneLimit && Contains(op, TRANSLATE_PLANS[i])) + { + ImVec2 screenQuadPts[4]; + for (int j = 0; j < 4; ++j) + { + vec_t cornerWorldPos = (dirPlaneX * quadUV[j * 2] + dirPlaneY * quadUV[j * 2 + 1]) * gContext.mScreenFactor; + screenQuadPts[j] = worldToPos(cornerWorldPos, gContext.mMVP); + } + drawList->AddPolyline(screenQuadPts, 4, GetColorU32(DIRECTION_X + i), true, 1.0f); + drawList->AddConvexPolyFilled(screenQuadPts, 4, colors[i + 4]); + } + } + } + + drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(type)) + { + ImU32 translationLineColor = GetColorU32(TRANSLATION_LINE); + + ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + vec_t dif = { destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y, 0.f, 0.f }; + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + + char tmps[512]; + vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_MOVE_X) * 3; + ImFormatString(tmps, sizeof(tmps), translationInfoMask[type - MT_MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static bool CanActivate() + { + if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemActive()) + { + return true; + } + return false; + } + + static void HandleAndDrawLocalBounds(const float* bounds, matrix_t* matrix, const float* snapValues, OPERATION operation) + { + ImGuiIO& io = ImGui::GetIO(); + ImDrawList* drawList = gContext.mDrawList; + + // compute best projection axis + vec_t axesWorldDirections[3]; + vec_t bestAxisWorldDirection = { 0.0f, 0.0f, 0.0f, 0.0f }; + int axes[3]; + unsigned int numAxes = 1; + axes[0] = gContext.mBoundsBestAxis; + int bestAxis = axes[0]; + if (!gContext.mbUsingBounds) + { + numAxes = 0; + float bestDot = 0.f; + for (int i = 0; i < 3; i++) + { + vec_t dirPlaneNormalWorld; + dirPlaneNormalWorld.TransformVector(directionUnary[i], gContext.mModelSource); + dirPlaneNormalWorld.Normalize(); + + float dt = fabsf(Dot(Normalized(gContext.mCameraEye - gContext.mModelSource.v.position), dirPlaneNormalWorld)); + if (dt >= bestDot) + { + bestDot = dt; + bestAxis = i; + bestAxisWorldDirection = dirPlaneNormalWorld; + } + + if (dt >= 0.1f) + { + axes[numAxes] = i; + axesWorldDirections[numAxes] = dirPlaneNormalWorld; + ++numAxes; + } + } + } + + if (numAxes == 0) + { + axes[0] = bestAxis; + axesWorldDirections[0] = bestAxisWorldDirection; + numAxes = 1; + } + + else if (bestAxis != axes[0]) + { + unsigned int bestIndex = 0; + for (unsigned int i = 0; i < numAxes; i++) + { + if (axes[i] == bestAxis) + { + bestIndex = i; + break; + } + } + int tempAxis = axes[0]; + axes[0] = axes[bestIndex]; + axes[bestIndex] = tempAxis; + vec_t tempDirection = axesWorldDirections[0]; + axesWorldDirections[0] = axesWorldDirections[bestIndex]; + axesWorldDirections[bestIndex] = tempDirection; + } + + for (unsigned int axisIndex = 0; axisIndex < numAxes; ++axisIndex) + { + bestAxis = axes[axisIndex]; + bestAxisWorldDirection = axesWorldDirections[axisIndex]; + + // corners + vec_t aabb[4]; + + int secondAxis = (bestAxis + 1) % 3; + int thirdAxis = (bestAxis + 2) % 3; + + for (int i = 0; i < 4; i++) + { + aabb[i][3] = aabb[i][bestAxis] = 0.f; + aabb[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)]; + aabb[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))]; + } + + // draw bounds + unsigned int anchorAlpha = gContext.mbEnable ? IM_COL32_BLACK : IM_COL32(0, 0, 0, 0x80); + + matrix_t boundsMVP = gContext.mModelSource * gContext.mViewProjection; + for (int i = 0; i < 4; i++) + { + ImVec2 worldBound1 = worldToPos(aabb[i], boundsMVP); + ImVec2 worldBound2 = worldToPos(aabb[(i + 1) % 4], boundsMVP); + if (!IsInContextRect(worldBound1) || !IsInContextRect(worldBound2)) + { + continue; + } + float boundDistance = sqrtf(ImLengthSqr(worldBound1 - worldBound2)); + int stepCount = (int)(boundDistance / 10.f); + stepCount = min(stepCount, 1000); + for (int j = 0; j < stepCount; j++) + { + float stepLength = 1.f / (float)stepCount; + float t1 = (float)j * stepLength; + float t2 = (float)j * stepLength + stepLength * 0.5f; + ImVec2 worldBoundSS1 = ImLerp(worldBound1, worldBound2, ImVec2(t1, t1)); + ImVec2 worldBoundSS2 = ImLerp(worldBound1, worldBound2, ImVec2(t2, t2)); + //drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0, 0, 0, 0) + anchorAlpha, 3.f); + drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha, 2.f); + } + vec_t midPoint = (aabb[i] + aabb[(i + 1) % 4]) * 0.5f; + ImVec2 midBound = worldToPos(midPoint, boundsMVP); + static const float AnchorBigRadius = 8.f; + static const float AnchorSmallRadius = 6.f; + bool overBigAnchor = ImLengthSqr(worldBound1 - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + bool overSmallAnchor = ImLengthSqr(midBound - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + + int type = MT_NONE; + vec_t gizmoHitProportion; + + if(Intersects(operation, TRANSLATE)) + { + type = GetMoveType(operation, &gizmoHitProportion); + } + if(Intersects(operation, ROTATE) && type == MT_NONE) + { + type = GetRotateType(operation); + } + if(Intersects(operation, SCALE) && type == MT_NONE) + { + type = GetScaleType(operation); + } + + if (type != MT_NONE) + { + overBigAnchor = false; + overSmallAnchor = false; + } + + ImU32 selectionColor = GetColorU32(SELECTION); + + unsigned int bigAnchorColor = overBigAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + + drawList->AddCircleFilled(worldBound1, AnchorBigRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(worldBound1, AnchorBigRadius - 1.2f, bigAnchorColor); + + drawList->AddCircleFilled(midBound, AnchorSmallRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(midBound, AnchorSmallRadius - 1.2f, smallAnchorColor); + int oppositeIndex = (i + 2) % 4; + // big anchor on corners + if (!gContext.mbUsingBounds && gContext.mbEnable && overBigAnchor && CanActivate()) + { + gContext.mBoundsPivot.TransformPoint(aabb[(i + 2) % 4], gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(aabb[i], gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + gContext.mBoundsAxis[0] = secondAxis; + gContext.mBoundsAxis[1] = thirdAxis; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[secondAxis] = aabb[oppositeIndex][secondAxis]; + gContext.mBoundsLocalPivot[thirdAxis] = aabb[oppositeIndex][thirdAxis]; + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + } + // small anchor on middle of segment + if (!gContext.mbUsingBounds && gContext.mbEnable && overSmallAnchor && CanActivate()) + { + vec_t midPointOpposite = (aabb[(i + 2) % 4] + aabb[(i + 3) % 4]) * 0.5f; + gContext.mBoundsPivot.TransformPoint(midPointOpposite, gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(midPoint, gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + int indices[] = { secondAxis , thirdAxis }; + gContext.mBoundsAxis[0] = indices[i % 2]; + gContext.mBoundsAxis[1] = -1; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[gContext.mBoundsAxis[0]] = aabb[oppositeIndex][indices[i % 2]];// bounds[gContext.mBoundsAxis[0]] * (((i + 1) & 2) ? 1.f : -1.f); + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + } + } + + if (gContext.mbUsingBounds && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + matrix_t scale; + scale.SetToIdentity(); + + // compute projected mouse position on plan + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mBoundsPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute a reference and delta vectors base on mouse move + vec_t deltaVector = (newPos - gContext.mBoundsPivot).Abs(); + vec_t referenceVector = (gContext.mBoundsAnchor - gContext.mBoundsPivot).Abs(); + + // for 1 or 2 axes, compute a ratio that's used for scale and snap it based on resulting length + for (int i = 0; i < 2; i++) + { + int axisIndex1 = gContext.mBoundsAxis[i]; + if (axisIndex1 == -1) + { + continue; + } + + float ratioAxis = 1.f; + vec_t axisDir = gContext.mBoundsMatrix.component[axisIndex1].Abs(); + + float dtAxis = axisDir.Dot(referenceVector); + float boundSize = bounds[axisIndex1 + 3] - bounds[axisIndex1]; + if (dtAxis > FLT_EPSILON) + { + ratioAxis = axisDir.Dot(deltaVector) / dtAxis; + } + + if (snapValues) + { + float length = boundSize * ratioAxis; + ComputeSnap(&length, snapValues[axisIndex1]); + if (boundSize > FLT_EPSILON) + { + ratioAxis = length / boundSize; + } + } + scale.component[axisIndex1] *= ratioAxis; + } + + // transform matrix + matrix_t preScale, postScale; + preScale.Translation(-gContext.mBoundsLocalPivot); + postScale.Translation(gContext.mBoundsLocalPivot); + matrix_t res = preScale * scale * postScale * gContext.mBoundsMatrix; + *matrix = res; + + // info text + char tmps[512]; + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + ImFormatString(tmps, sizeof(tmps), "X: %.2f Y: %.2f Z: %.2f" + , (bounds[3] - bounds[0]) * gContext.mBoundsMatrix.component[0].Length() * scale.component[0].Length() + , (bounds[4] - bounds[1]) * gContext.mBoundsMatrix.component[1].Length() * scale.component[1].Length() + , (bounds[5] - bounds[2]) * gContext.mBoundsMatrix.component[2].Length() * scale.component[2].Length() + ); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + + if (!io.MouseDown[0]) { + gContext.mbUsingBounds = false; + gContext.mEditingID = -1; + } + if (gContext.mbUsingBounds) + { + break; + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + static int GetScaleType(OPERATION op) + { + if (gContext.mbUsing) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, SCALE)) + { + type = MT_SCALE_XYZ; + } + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + dirAxis.TransformVector(gContext.mModelLocal); + dirPlaneX.TransformVector(gContext.mModelLocal); + dirPlaneY.TransformVector(gContext.mModelLocal); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModelLocal.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const float startOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.0f : 0.1f; + const float endOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.4f : 1.0f; + const ImVec2 posOnPlanScreen = worldToPos(posOnPlan, gContext.mViewProjection); + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * startOffset, gContext.mViewProjection); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * endOffset, gContext.mViewProjection); + + vec_t closestPointOnAxis = PointOnSegment(makeVect(posOnPlanScreen), makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + + if ((closestPointOnAxis - makeVect(posOnPlanScreen)).Length() < 12.f) // pixel size + { + type = MT_SCALE_X + i; + } + } + + // universal + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Contains(op, SCALEU) && dist >= 17.0f && dist < 23.0f) + { + type = MT_SCALE_XYZ; + } + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + //ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + //ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale) * gContext.mScreenFactor, gContext.mMVPLocal); + + float distance = sqrtf(ImLengthSqr(worldDirSSpace - io.MousePos)); + if (distance < 12.f) + { + type = MT_SCALE_X + i; + } + } + } + return type; + } + + static int GetRotateType(OPERATION op) + { + if (gContext.mbUsing) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Intersects(op, ROTATE_SCREEN) && dist >= (gContext.mRadiusSquareCenter - 4.0f) && dist < (gContext.mRadiusSquareCenter + 4.0f)) + { + type = MT_ROTATE_SCREEN; + } + + const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; + + vec_t modelViewPos; + modelViewPos.TransformPoint(gContext.mModel.v.position, gContext.mViewMat); + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(ROTATE_X << i))) + { + continue; + } + // pickup plan + vec_t pickupPlan = BuildPlan(gContext.mModel.v.position, planNormals[i]); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, pickupPlan); + const vec_t intersectWorldPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t intersectViewPos; + intersectViewPos.TransformPoint(intersectWorldPos, gContext.mViewMat); + + if (ImAbs(modelViewPos.z) - ImAbs(intersectViewPos.z) < -FLT_EPSILON) + { + continue; + } + + const vec_t localPos = intersectWorldPos - gContext.mModel.v.position; + vec_t idealPosOnCircle = Normalized(localPos); + idealPosOnCircle.TransformVector(gContext.mModelInverse); + const ImVec2 idealPosOnCircleScreen = worldToPos(idealPosOnCircle * rotationDisplayFactor * gContext.mScreenFactor, gContext.mMVP); + + //gContext.mDrawList->AddCircle(idealPosOnCircleScreen, 5.f, IM_COL32_WHITE); + const ImVec2 distanceOnScreen = idealPosOnCircleScreen - io.MousePos; + + const float distance = makeVect(distanceOnScreen).Length(); + if (distance < 8.f) // pixel size + { + type = MT_ROTATE_X + i; + } + } + + return type; + } + + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion) + { + if(!Intersects(op, TRANSLATE) || gContext.mbUsing || !gContext.mbMouseOver) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, TRANSLATE)) + { + type = MT_MOVE_SCREEN; + } + + const vec_t screenCoord = makeVect(io.MousePos - ImVec2(gContext.mX, gContext.mY)); + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + dirAxis.TransformVector(gContext.mModel); + dirPlaneX.TransformVector(gContext.mModel); + dirPlaneY.TransformVector(gContext.mModel); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * 0.1f, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + + vec_t closestPointOnAxis = PointOnSegment(screenCoord, makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + if ((closestPointOnAxis - screenCoord).Length() < 12.f && Intersects(op, static_cast(TRANSLATE_X << i))) // pixel size + { + type = MT_MOVE_X + i; + } + + const float dx = dirPlaneX.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + const float dy = dirPlaneY.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + if (belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3] && Contains(op, TRANSLATE_PLANS[i])) + { + type = MT_MOVE_YZ + i; + } + + if (gizmoHitProportion) + { + *gizmoHitProportion = makeVect(dx, dy, 0.f); + } + } + return type; + } + + static bool HandleTranslation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, TRANSLATE) || type != MT_NONE) + { + return false; + } + const ImGuiIO& io = ImGui::GetIO(); + const bool applyRotationLocaly = gContext.mMode == LOCAL || type == MT_MOVE_SCREEN; + bool modified = false; + + // move + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + const float signedLength = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + const float len = fabsf(signedLength); // near plan + const vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute delta + const vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModel.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_MOVE_X && gContext.mCurrentOperation <= MT_MOVE_Z) + { + const int axisIndex = gContext.mCurrentOperation - MT_MOVE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; + const float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + } + + // snap + if (snap) + { + vec_t cumulativeDelta = gContext.mModel.v.position + delta - gContext.mMatrixOrigin; + if (applyRotationLocaly) + { + matrix_t modelSourceNormalized = gContext.mModelSource; + modelSourceNormalized.OrthoNormalize(); + matrix_t modelSourceNormalizedInverse; + modelSourceNormalizedInverse.Inverse(modelSourceNormalized); + cumulativeDelta.TransformVector(modelSourceNormalizedInverse); + ComputeSnap(cumulativeDelta, snap); + cumulativeDelta.TransformVector(modelSourceNormalized); + } + else + { + ComputeSnap(cumulativeDelta, snap); + } + delta = gContext.mMatrixOrigin + cumulativeDelta - gContext.mModel.v.position; + + } + + if (delta != gContext.mTranslationLastDelta) + { + modified = true; + } + gContext.mTranslationLastDelta = delta; + + // compute matrix & delta + matrix_t deltaMatrixTranslation; + deltaMatrixTranslation.Translation(delta); + if (deltaMatrix) + { + memcpy(deltaMatrix, deltaMatrixTranslation.m16, sizeof(float) * 16); + } + + const matrix_t res = gContext.mModelSource * deltaMatrixTranslation; + *(matrix_t*)matrix = res; + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + } + + type = gContext.mCurrentOperation; + } + else + { + // find new possible way to move + vec_t gizmoHitProportion; + type = GetMoveType(op, &gizmoHitProportion); + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + vec_t movePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + -gContext.mCameraDir }; + + vec_t cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + for (unsigned int i = 0; i < 3; i++) + { + vec_t orthoVector = Cross(movePlanNormal[i], cameraToModelNormalized); + movePlanNormal[i].Cross(orthoVector); + movePlanNormal[i].Normalize(); + } + // pickup plan + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + } + } + return modified; + } + + static bool HandleScale(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if((!Intersects(op, SCALE) && !Intersects(op, SCALEU)) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool modified = false; + + if (!gContext.mbUsing) + { + // find new possible way to scale + type = GetScaleType(op); + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t movePlanNormal[] = { gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.dir, gContext.mModel.v.up, gContext.mModel.v.right, -gContext.mCameraDir }; + // pickup plan + + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_SCALE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + gContext.mScale.Set(1.f, 1.f, 1.f); + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + gContext.mScaleValueOrigin = makeVect(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + gContext.mSaveMousePosx = io.MousePos.x; + } + } + // scale + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModelLocal.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_SCALE_X && gContext.mCurrentOperation <= MT_SCALE_Z) + { + int axisIndex = gContext.mCurrentOperation - MT_SCALE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModelLocal.m[axisIndex]; + float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + + vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModelLocal.v.position; + float ratio = Dot(axisValue, baseVector + delta) / Dot(axisValue, baseVector); + + gContext.mScale[axisIndex] = max(ratio, 0.001f); + } + else + { + float scaleDelta = (io.MousePos.x - gContext.mSaveMousePosx) * 0.01f; + gContext.mScale.Set(max(1.f + scaleDelta, 0.001f)); + } + + // snap + if (snap) + { + float scaleSnap[] = { snap[0], snap[0], snap[0] }; + ComputeSnap(gContext.mScale, scaleSnap); + } + + // no 0 allowed + for (int i = 0; i < 3; i++) + gContext.mScale[i] = max(gContext.mScale[i], 0.001f); + + if (gContext.mScaleLast != gContext.mScale) + { + modified = true; + } + gContext.mScaleLast = gContext.mScale; + + // compute matrix & delta + matrix_t deltaMatrixScale; + deltaMatrixScale.Scale(gContext.mScale * gContext.mScaleValueOrigin); + + matrix_t res = deltaMatrixScale * gContext.mModelLocal; + *(matrix_t*)matrix = res; + + if (deltaMatrix) + { + vec_t deltaScale = gContext.mScale * gContext.mScaleValueOrigin; + + vec_t originalScaleDivider; + originalScaleDivider.x = 1 / gContext.mModelScaleOrigin.x; + originalScaleDivider.y = 1 / gContext.mModelScaleOrigin.y; + originalScaleDivider.z = 1 / gContext.mModelScaleOrigin.z; + + deltaScale = deltaScale * originalScaleDivider; + + deltaMatrixScale.Scale(deltaScale); + memcpy(deltaMatrix, deltaMatrixScale.m16, sizeof(float) * 16); + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mScale.Set(1.f, 1.f, 1.f); + } + + type = gContext.mCurrentOperation; + } + return modified; + } + + static bool HandleRotation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, ROTATE) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool applyRotationLocaly = gContext.mMode == LOCAL; + bool modified = false; + + if (!gContext.mbUsing) + { + type = GetRotateType(op); + + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + + if (type == MT_ROTATE_SCREEN) + { + applyRotationLocaly = true; + } + + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t rotatePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir }; + // pickup plan + if (applyRotationLocaly) + { + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, rotatePlanNormal[type - MT_ROTATE_X]); + } + else + { + gContext.mTranslationPlan = BuildPlan(gContext.mModelSource.v.position, directionUnary[type - MT_ROTATE_X]); + } + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position; + gContext.mRotationVectorSource = Normalized(localPos); + gContext.mRotationAngleOrigin = ComputeAngleOnPlan(); + } + } + + // rotation + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + gContext.mRotationAngle = ComputeAngleOnPlan(); + if (snap) + { + float snapInRadian = snap[0] * DEG2RAD; + ComputeSnap(&gContext.mRotationAngle, snapInRadian); + } + vec_t rotationAxisLocalSpace; + + rotationAxisLocalSpace.TransformVector(makeVect(gContext.mTranslationPlan.x, gContext.mTranslationPlan.y, gContext.mTranslationPlan.z, 0.f), gContext.mModelInverse); + rotationAxisLocalSpace.Normalize(); + + matrix_t deltaRotation; + deltaRotation.RotationAxis(rotationAxisLocalSpace, gContext.mRotationAngle - gContext.mRotationAngleOrigin); + if (gContext.mRotationAngle != gContext.mRotationAngleOrigin) + { + modified = true; + } + gContext.mRotationAngleOrigin = gContext.mRotationAngle; + + matrix_t scaleOrigin; + scaleOrigin.Scale(gContext.mModelScaleOrigin); + + if (applyRotationLocaly) + { + *(matrix_t*)matrix = scaleOrigin * deltaRotation * gContext.mModelLocal; + } + else + { + matrix_t res = gContext.mModelSource; + res.v.position.Set(0.f); + + *(matrix_t*)matrix = res * deltaRotation; + ((matrix_t*)matrix)->v.position = gContext.mModelSource.v.position; + } + + if (deltaMatrix) + { + *(matrix_t*)deltaMatrix = gContext.mModelInverse * deltaRotation * gContext.mModel; + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mEditingID = -1; + } + type = gContext.mCurrentOperation; + } + return modified; + } + + void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale) + { + matrix_t mat = *(matrix_t*)matrix; + + scale[0] = mat.v.right.Length(); + scale[1] = mat.v.up.Length(); + scale[2] = mat.v.dir.Length(); + + mat.OrthoNormalize(); + + rotation[0] = RAD2DEG * atan2f(mat.m[1][2], mat.m[2][2]); + rotation[1] = RAD2DEG * atan2f(-mat.m[0][2], sqrtf(mat.m[1][2] * mat.m[1][2] + mat.m[2][2] * mat.m[2][2])); + rotation[2] = RAD2DEG * atan2f(mat.m[0][1], mat.m[0][0]); + + translation[0] = mat.v.position.x; + translation[1] = mat.v.position.y; + translation[2] = mat.v.position.z; + } + + void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix) + { + matrix_t& mat = *(matrix_t*)matrix; + + matrix_t rot[3]; + for (int i = 0; i < 3; i++) + { + rot[i].RotationAxis(directionUnary[i], rotation[i] * DEG2RAD); + } + + mat = rot[0] * rot[1] * rot[2]; + + float validScale[3]; + for (int i = 0; i < 3; i++) + { + if (fabsf(scale[i]) < FLT_EPSILON) + { + validScale[i] = 0.001f; + } + else + { + validScale[i] = scale[i]; + } + } + mat.v.right *= validScale[0]; + mat.v.up *= validScale[1]; + mat.v.dir *= validScale[2]; + mat.v.position.Set(translation[0], translation[1], translation[2], 1.f); + } + + void SetID(int id) + { + gContext.mActualID = id; + } + + void AllowAxisFlip(bool value) + { + gContext.mAllowAxisFlip = value; + } + + bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix, const float* snap, const float* localBounds, const float* boundsSnap) + { + // Scale is always local or matrix will be skewed when applying world scale or oriented matrix + ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); + + // set delta to identity + if (deltaMatrix) + { + ((matrix_t*)deltaMatrix)->SetToIdentity(); + } + + // behind camera + vec_t camSpacePosition; + camSpacePosition.TransformPoint(makeVect(0.f, 0.f, 0.f), gContext.mMVP); + if (!gContext.mIsOrthographic && camSpacePosition.z < 0.001f) + { + return false; + } + + // -- + int type = MT_NONE; + bool manipulated = false; + if (gContext.mbEnable) + { + if (!gContext.mbUsingBounds) + { + manipulated = HandleTranslation(matrix, deltaMatrix, operation, type, snap) || + HandleScale(matrix, deltaMatrix, operation, type, snap) || + HandleRotation(matrix, deltaMatrix, operation, type, snap); + } + } + + if (localBounds && !gContext.mbUsing) + { + HandleAndDrawLocalBounds(localBounds, (matrix_t*)matrix, boundsSnap, operation); + } + + gContext.mOperation = operation; + if (!gContext.mbUsingBounds) + { + DrawRotationGizmo(operation, type); + DrawTranslationGizmo(operation, type); + DrawScaleGizmo(operation, type); + DrawScaleUniveralGizmo(operation, type); + } + return manipulated; + } + + void SetGizmoSizeClipSpace(float value) + { + gContext.mGizmoSizeClipSpace = value; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////// + void ComputeFrustumPlanes(vec_t* frustum, const float* clip) + { + frustum[0].x = clip[3] - clip[0]; + frustum[0].y = clip[7] - clip[4]; + frustum[0].z = clip[11] - clip[8]; + frustum[0].w = clip[15] - clip[12]; + + frustum[1].x = clip[3] + clip[0]; + frustum[1].y = clip[7] + clip[4]; + frustum[1].z = clip[11] + clip[8]; + frustum[1].w = clip[15] + clip[12]; + + frustum[2].x = clip[3] + clip[1]; + frustum[2].y = clip[7] + clip[5]; + frustum[2].z = clip[11] + clip[9]; + frustum[2].w = clip[15] + clip[13]; + + frustum[3].x = clip[3] - clip[1]; + frustum[3].y = clip[7] - clip[5]; + frustum[3].z = clip[11] - clip[9]; + frustum[3].w = clip[15] - clip[13]; + + frustum[4].x = clip[3] - clip[2]; + frustum[4].y = clip[7] - clip[6]; + frustum[4].z = clip[11] - clip[10]; + frustum[4].w = clip[15] - clip[14]; + + frustum[5].x = clip[3] + clip[2]; + frustum[5].y = clip[7] + clip[6]; + frustum[5].z = clip[11] + clip[10]; + frustum[5].w = clip[15] + clip[14]; + + for (int i = 0; i < 6; i++) + { + frustum[i].Normalize(); + } + } + + void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + struct CubeFace + { + float z; + ImVec2 faceCoordsScreen[4]; + ImU32 color; + }; + CubeFace* faces = (CubeFace*)_malloca(sizeof(CubeFace) * matrixCount * 6); + + if (!faces) + { + return; + } + + vec_t frustum[6]; + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + ComputeFrustumPlanes(frustum, viewProjection.m16); + + int cubeFaceCount = 0; + for (int cube = 0; cube < matrixCount; cube++) + { + const float* matrix = &matrices[cube * 16]; + + matrix_t res = *(matrix_t*)matrix * *(matrix_t*)view * *(matrix_t*)projection; + + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + + const vec_t faceCoords[4] = { directionUnary[normalIndex] + directionUnary[perpXIndex] + directionUnary[perpYIndex], + directionUnary[normalIndex] + directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] + directionUnary[perpYIndex], + }; + + // clipping + /* + bool skipFace = false; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + vec_t camSpacePosition; + camSpacePosition.TransformPoint(faceCoords[iCoord] * 0.5f * invert, res); + if (camSpacePosition.z < 0.001f) + { + skipFace = true; + break; + } + } + if (skipFace) + { + continue; + } + */ + vec_t centerPosition, centerPositionVP; + centerPosition.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, *(matrix_t*)matrix); + centerPositionVP.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, res); + + bool inFrustum = true; + for (int iFrustum = 0; iFrustum < 6; iFrustum++) + { + float dist = DistanceToPlane(centerPosition, frustum[iFrustum]); + if (dist < 0.f) + { + inFrustum = false; + break; + } + } + + if (!inFrustum) + { + continue; + } + CubeFace& cubeFace = faces[cubeFaceCount]; + + // 3D->2D + //ImVec2 faceCoordsScreen[4]; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + cubeFace.faceCoordsScreen[iCoord] = worldToPos(faceCoords[iCoord] * 0.5f * invert, res); + } + + ImU32 directionColor = GetColorU32(DIRECTION_X + normalIndex); + cubeFace.color = directionColor | IM_COL32(0x80, 0x80, 0x80, 0); + + cubeFace.z = centerPositionVP.z / centerPositionVP.w; + cubeFaceCount++; + } + } + qsort(faces, cubeFaceCount, sizeof(CubeFace), [](void const* _a, void const* _b) { + CubeFace* a = (CubeFace*)_a; + CubeFace* b = (CubeFace*)_b; + if (a->z < b->z) + { + return 1; + } + return -1; + }); + // draw face with lighter color + for (int iFace = 0; iFace < cubeFaceCount; iFace++) + { + const CubeFace& cubeFace = faces[iFace]; + gContext.mDrawList->AddConvexPolyFilled(cubeFace.faceCoordsScreen, 4, cubeFace.color); + } + + _freea(faces); + } + + void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize) + { + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + vec_t frustum[6]; + ComputeFrustumPlanes(frustum, viewProjection.m16); + matrix_t res = *(matrix_t*)matrix * viewProjection; + + for (float f = -gridSize; f <= gridSize; f += 1.f) + { + for (int dir = 0; dir < 2; dir++) + { + vec_t ptA = makeVect(dir ? -gridSize : f, 0.f, dir ? f : -gridSize); + vec_t ptB = makeVect(dir ? gridSize : f, 0.f, dir ? f : gridSize); + bool visible = true; + for (int i = 0; i < 6; i++) + { + float dA = DistanceToPlane(ptA, frustum[i]); + float dB = DistanceToPlane(ptB, frustum[i]); + if (dA < 0.f && dB < 0.f) + { + visible = false; + break; + } + if (dA > 0.f && dB > 0.f) + { + continue; + } + if (dA < 0.f) + { + float len = fabsf(dA - dB); + float t = fabsf(dA) / len; + ptA.Lerp(ptB, t); + } + if (dB < 0.f) + { + float len = fabsf(dB - dA); + float t = fabsf(dB) / len; + ptB.Lerp(ptA, t); + } + } + if (visible) + { + ImU32 col = IM_COL32(0x80, 0x80, 0x80, 0xFF); + col = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? IM_COL32(0x90, 0x90, 0x90, 0xFF) : col; + col = (fabsf(f) < FLT_EPSILON) ? IM_COL32(0x40, 0x40, 0x40, 0xFF): col; + + float thickness = 1.f; + thickness = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? 1.5f : thickness; + thickness = (fabsf(f) < FLT_EPSILON) ? 2.3f : thickness; + + gContext.mDrawList->AddLine(worldToPos(ptA, res), worldToPos(ptB, res), col, thickness); + } + } + } + } + + void ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) + { + // Scale is always local or matrix will be skewed when applying world scale or oriented matrix + ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); + ViewManipulate(view, length, position, size, backgroundColor); + } + + void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) + { + static bool isDraging = false; + static bool isClicking = false; + static bool isInside = false; + static vec_t interpolationUp; + static vec_t interpolationDir; + static int interpolationFrames = 0; + const vec_t referenceUp = makeVect(0.f, 1.f, 0.f); + + matrix_t svgView, svgProjection; + svgView = gContext.mViewMat; + svgProjection = gContext.mProjectionMat; + + ImGuiIO& io = ImGui::GetIO(); + gContext.mDrawList->AddRectFilled(position, position + size, backgroundColor); + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + const vec_t camTarget = viewInverse.v.position - viewInverse.v.dir * length; + + // view/projection matrices + const float distance = 3.f; + matrix_t cubeProjection, cubeView; + float fov = acosf(distance / (sqrtf(distance * distance + 3.f))) * RAD2DEG; + Perspective(fov / sqrtf(2.f), size.x / size.y, 0.01f, 1000.f, cubeProjection.m16); + + vec_t dir = makeVect(viewInverse.m[2][0], viewInverse.m[2][1], viewInverse.m[2][2]); + vec_t up = makeVect(viewInverse.m[1][0], viewInverse.m[1][1], viewInverse.m[1][2]); + vec_t eye = dir * distance; + vec_t zero = makeVect(0.f, 0.f); + LookAt(&eye.x, &zero.x, &up.x, cubeView.m16); + + // set context + gContext.mViewMat = cubeView; + gContext.mProjectionMat = cubeProjection; + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector, position, size); + + const matrix_t res = cubeView * cubeProjection; + + // panels + static const ImVec2 panelPosition[9] = { ImVec2(0.75f,0.75f), ImVec2(0.25f, 0.75f), ImVec2(0.f, 0.75f), + ImVec2(0.75f, 0.25f), ImVec2(0.25f, 0.25f), ImVec2(0.f, 0.25f), + ImVec2(0.75f, 0.f), ImVec2(0.25f, 0.f), ImVec2(0.f, 0.f) }; + + static const ImVec2 panelSize[9] = { ImVec2(0.25f,0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f), + ImVec2(0.25f, 0.5f), ImVec2(0.5f, 0.5f), ImVec2(0.25f, 0.5f), + ImVec2(0.25f, 0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f) }; + + // tag faces + bool boxes[27]{}; + static int overBox = -1; + for (int iPass = 0; iPass < 2; iPass++) + { + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + const vec_t indexVectorX = directionUnary[perpXIndex] * invert; + const vec_t indexVectorY = directionUnary[perpYIndex] * invert; + const vec_t boxOrigin = directionUnary[normalIndex] * -invert - indexVectorX - indexVectorY; + + // plan local space + const vec_t n = directionUnary[normalIndex] * invert; + vec_t viewSpaceNormal = n; + vec_t viewSpacePoint = n * 0.5f; + viewSpaceNormal.TransformVector(cubeView); + viewSpaceNormal.Normalize(); + viewSpacePoint.TransformPoint(cubeView); + const vec_t viewSpaceFacePlan = BuildPlan(viewSpacePoint, viewSpaceNormal); + + // back face culling + if (viewSpaceFacePlan.w > 0.f) + { + continue; + } + + const vec_t facePlan = BuildPlan(n * 0.5f, n); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, facePlan); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len - (n * 0.5f); + + float localx = Dot(directionUnary[perpXIndex], posOnPlan) * invert + 0.5f; + float localy = Dot(directionUnary[perpYIndex], posOnPlan) * invert + 0.5f; + + // panels + const vec_t dx = directionUnary[perpXIndex]; + const vec_t dy = directionUnary[perpYIndex]; + const vec_t origin = directionUnary[normalIndex] - dx - dy; + for (int iPanel = 0; iPanel < 9; iPanel++) + { + vec_t boxCoord = boxOrigin + indexVectorX * float(iPanel % 3) + indexVectorY * float(iPanel / 3) + makeVect(1.f, 1.f, 1.f); + const ImVec2 p = panelPosition[iPanel] * 2.f; + const ImVec2 s = panelSize[iPanel] * 2.f; + ImVec2 faceCoordsScreen[4]; + vec_t panelPos[4] = { dx * p.x + dy * p.y, + dx * p.x + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * p.y }; + + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + faceCoordsScreen[iCoord] = worldToPos((panelPos[iCoord] + origin) * 0.5f * invert, res, position, size); + } + + const ImVec2 panelCorners[2] = { panelPosition[iPanel], panelPosition[iPanel] + panelSize[iPanel] }; + bool insidePanel = localx > panelCorners[0].x && localx < panelCorners[1].x && localy > panelCorners[0].y && localy < panelCorners[1].y; + int boxCoordInt = int(boxCoord.x * 9.f + boxCoord.y * 3.f + boxCoord.z); + IM_ASSERT(boxCoordInt < 27); + boxes[boxCoordInt] |= insidePanel && (!isDraging) && gContext.mbMouseOver; + + // draw face with lighter color + if (iPass) + { + ImU32 directionColor = GetColorU32(DIRECTION_X + normalIndex); + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, (directionColor | IM_COL32(0x80, 0x80, 0x80, 0x80)) | (isInside ? IM_COL32(0x08, 0x08, 0x08, 0) : 0)); + if (boxes[boxCoordInt]) + { + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, IM_COL32(0xF0, 0xA0, 0x60, 0x80)); + + if (io.MouseDown[0] && !isClicking && !isDraging) { + overBox = boxCoordInt; + isClicking = true; + isDraging = true; + } + } + } + } + } + } + if (interpolationFrames) + { + interpolationFrames--; + vec_t newDir = viewInverse.v.dir; + newDir.Lerp(interpolationDir, 0.2f); + newDir.Normalize(); + + vec_t newUp = viewInverse.v.up; + newUp.Lerp(interpolationUp, 0.3f); + newUp.Normalize(); + newUp = interpolationUp; + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &newUp.x, view); + } + isInside = gContext.mbMouseOver && ImRect(position, position + size).Contains(io.MousePos); + + if (io.MouseDown[0] && (fabsf(io.MouseDelta[0]) || fabsf(io.MouseDelta[1])) && isClicking) + { + isClicking = false; + } + + if (!io.MouseDown[0]) + { + if (isClicking) + { + // apply new view direction + int cx = overBox / 9; + int cy = (overBox - cx * 9) / 3; + int cz = overBox % 3; + interpolationDir = makeVect(1.f - (float)cx, 1.f - (float)cy, 1.f - (float)cz); + interpolationDir.Normalize(); + + if (fabsf(Dot(interpolationDir, referenceUp)) > 1.0f - 0.01f) + { + vec_t right = viewInverse.v.right; + if (fabsf(right.x) > fabsf(right.z)) + { + right.z = 0.f; + } + else + { + right.x = 0.f; + } + right.Normalize(); + interpolationUp = Cross(interpolationDir, right); + interpolationUp.Normalize(); + } + else + { + interpolationUp = referenceUp; + } + interpolationFrames = 40; + + } + isClicking = false; + isDraging = false; + } + + + if (isDraging) + { + matrix_t rx, ry, roll; + + rx.RotationAxis(referenceUp, -io.MouseDelta.x * 0.01f); + ry.RotationAxis(viewInverse.v.right, -io.MouseDelta.y * 0.01f); + + roll = rx * ry; + + vec_t newDir = viewInverse.v.dir; + newDir.TransformVector(roll); + newDir.Normalize(); + + // clamp + vec_t planDir = Cross(viewInverse.v.right, referenceUp); + planDir.y = 0.f; + planDir.Normalize(); + float dt = Dot(planDir, newDir); + if (dt < 0.0f) + { + newDir += planDir * dt; + newDir.Normalize(); + } + + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &referenceUp.x, view); + } + + // restore view/projection because it was used to compute ray + ComputeContext(svgView.m16, svgProjection.m16, gContext.mModelSource.m16, gContext.mMode); + } +}; diff --git a/editor/ImGui/Source/ImGuizmo/ImGuizmo.h b/editor/ImGui/Source/ImGuizmo/ImGuizmo.h new file mode 100644 index 000000000..cdc11ecfb --- /dev/null +++ b/editor/ImGui/Source/ImGuizmo/ImGuizmo.h @@ -0,0 +1,264 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ------------------------------------------------------------------------------------------- +// History : +// 2019/11/03 View gizmo +// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode. +// 2016/09/09 Hatched negative axis. Snapping. Documentation update. +// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved +// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results. +// 2016/08/31 First version +// +// ------------------------------------------------------------------------------------------- +// Future (no order): +// +// - Multi view +// - display rotation/translation/scale infos in local/world space and not only local +// - finish local/world matrix application +// - OPERATION as bitmask +// +// ------------------------------------------------------------------------------------------- +// Example +#if 0 +void EditTransform(const Camera& camera, matrix_t& matrix) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); + if (ImGui::IsKeyPressed(90)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(69)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + static bool useSnap(false); + if (ImGui::IsKeyPressed(83)) + useSnap = !useSnap; + ImGui::Checkbox("", &useSnap); + ImGui::SameLine(); + vec_t snap; + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + snap = config.mSnapTranslation; + ImGui::InputFloat3("Snap", &snap.x); + break; + case ImGuizmo::ROTATE: + snap = config.mSnapRotation; + ImGui::InputFloat("Angle Snap", &snap.x); + break; + case ImGuizmo::SCALE: + snap = config.mSnapScale; + ImGui::InputFloat("Scale Snap", &snap.x); + break; + } + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); +} +#endif +#pragma once + +#ifdef USE_IMGUI_API +#include "imconfig.h" +#endif +#ifndef IMGUI_API +#define IMGUI_API +#endif + +#ifndef IMGUIZMO_NAMESPACE +#define IMGUIZMO_NAMESPACE ImGuizmo +#endif + +namespace IMGUIZMO_NAMESPACE +{ + // call inside your own window and before Manipulate() in order to draw gizmo to that window. + // Or pass a specific ImDrawList to draw to (e.g. ImGui::GetForegroundDrawList()). + IMGUI_API void SetDrawlist(ImDrawList* drawlist = nullptr); + + // call BeginFrame right after ImGui_XXXX_NewFrame(); + IMGUI_API void BeginFrame(); + + // this is necessary because when imguizmo is compiled into a dll, and imgui into another + // globals are not shared between them. + // More details at https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam + // expose method to set imgui context + IMGUI_API void SetImGuiContext(ImGuiContext* ctx); + + // return true if mouse cursor is over any gizmo control (axis, plan or screen component) + IMGUI_API bool IsOver(); + + // return true if mouse IsOver or if the gizmo is in moving state + IMGUI_API bool IsUsing(); + + // enable/disable the gizmo. Stay in the state until next call to Enable. + // gizmo is rendered with gray half transparent color when disabled + IMGUI_API void Enable(bool enable); + + // helper functions for manualy editing translation/rotation/scale with an input float + // translation, rotation and scale float points to 3 floats each + // Angles are in degrees (more suitable for human editing) + // example: + // float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + // ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale); + // ImGui::InputFloat3("Tr", matrixTranslation, 3); + // ImGui::InputFloat3("Rt", matrixRotation, 3); + // ImGui::InputFloat3("Sc", matrixScale, 3); + // ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16); + // + // These functions have some numerical stability issues for now. Use with caution. + IMGUI_API void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale); + IMGUI_API void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix); + + IMGUI_API void SetRect(float x, float y, float width, float height); + // default is false + IMGUI_API void SetOrthographic(bool isOrthographic); + + // Render a cube with face color corresponding to face normal. Usefull for debug/tests + IMGUI_API void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount); + IMGUI_API void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize); + + // call it when you want a gizmo + // Needs view and projection matrices. + // matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional + // translation is applied in world space + enum OPERATION + { + TRANSLATE_X = (1u << 0), + TRANSLATE_Y = (1u << 1), + TRANSLATE_Z = (1u << 2), + ROTATE_X = (1u << 3), + ROTATE_Y = (1u << 4), + ROTATE_Z = (1u << 5), + ROTATE_SCREEN = (1u << 6), + SCALE_X = (1u << 7), + SCALE_Y = (1u << 8), + SCALE_Z = (1u << 9), + BOUNDS = (1u << 10), + SCALE_XU = (1u << 11), + SCALE_YU = (1u << 12), + SCALE_ZU = (1u << 13), + + TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z, + ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN, + SCALE = SCALE_X | SCALE_Y | SCALE_Z, + SCALEU = SCALE_XU | SCALE_YU | SCALE_ZU, // universal + UNIVERSAL = TRANSLATE | ROTATE | SCALEU + }; + + inline OPERATION operator|(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + enum MODE + { + LOCAL, + WORLD + }; + + IMGUI_API bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix = NULL, const float* snap = NULL, const float* localBounds = NULL, const float* boundsSnap = NULL); + // + // Please note that this cubeview is patented by Autodesk : https://patents.google.com/patent/US7782319B2/en + // It seems to be a defensive patent in the US. I don't think it will bring troubles using it as + // other software are using the same mechanics. But just in case, you are now warned! + // + IMGUI_API void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); + + // use this version if you did not call Manipulate before and you are just using ViewManipulate + IMGUI_API void ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); + + IMGUI_API void SetID(int id); + + // return true if the cursor is over the operation's gizmo + IMGUI_API bool IsOver(OPERATION op); + IMGUI_API void SetGizmoSizeClipSpace(float value); + + // Allow axis to flip + // When true (default), the guizmo axis flip for better visibility + // When false, they always stay along the positive world/local axis + IMGUI_API void AllowAxisFlip(bool value); + + enum COLOR + { + DIRECTION_X, // directionColor[0] + DIRECTION_Y, // directionColor[1] + DIRECTION_Z, // directionColor[2] + PLANE_X, // planeColor[0] + PLANE_Y, // planeColor[1] + PLANE_Z, // planeColor[2] + SELECTION, // selectionColor + INACTIVE, // inactiveColor + TRANSLATION_LINE, // translationLineColor + SCALE_LINE, + ROTATION_USING_BORDER, + ROTATION_USING_FILL, + HATCHED_AXIS_LINES, + TEXT, + TEXT_SHADOW, + COUNT + }; + + struct Style + { + IMGUI_API Style(); + + float TranslationLineThickness; // Thickness of lines for translation gizmo + float TranslationLineArrowSize; // Size of arrow at the end of lines for translation gizmo + float RotationLineThickness; // Thickness of lines for rotation gizmo + float RotationOuterLineThickness; // Thickness of line surrounding the rotation gizmo + float ScaleLineThickness; // Thickness of lines for scale gizmo + float ScaleLineCircleSize; // Size of circle at the end of lines for scale gizmo + float HatchedAxisLineThickness; // Thickness of hatched axis lines + float CenterCircleSize; // Size of circle at the center of the translate/scale gizmo + + ImVec4 Colors[COLOR::COUNT]; + }; + + IMGUI_API Style& GetStyle(); +} diff --git a/editor/ImGui/Source/ImGuizmo/ImSequencer.cpp b/editor/ImGui/Source/ImGuizmo/ImSequencer.cpp new file mode 100644 index 000000000..f0fa0d229 --- /dev/null +++ b/editor/ImGui/Source/ImGuizmo/ImSequencer.cpp @@ -0,0 +1,695 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include "ImSequencer.h" +#include "../../Source/imgui.h" +#include "../../Source/imgui_internal.h" +#include + +namespace ImSequencer +{ +#ifndef IMGUI_DEFINE_MATH_OPERATORS + static ImVec2 operator+(const ImVec2& a, const ImVec2& b) { + return ImVec2(a.x + b.x, a.y + b.y); + } +#endif + static bool SequencerAddDelButton(ImDrawList* draw_list, ImVec2 pos, bool add = true) + { + ImGuiIO& io = ImGui::GetIO(); + ImRect btnRect(pos, ImVec2(pos.x + 16, pos.y + 16)); + bool overBtn = btnRect.Contains(io.MousePos); + bool containedClick = overBtn && btnRect.Contains(io.MouseClickedPos[0]); + bool clickedBtn = containedClick && io.MouseReleased[0]; + int btnColor = overBtn ? 0xAAEAFFAA : 0x77A3B2AA; + if (containedClick && io.MouseDownDuration[0] > 0) + btnRect.Expand(2.0f); + + float midy = pos.y + 16 / 2 - 0.5f; + float midx = pos.x + 16 / 2 - 0.5f; + draw_list->AddRect(btnRect.Min, btnRect.Max, btnColor, 4); + draw_list->AddLine(ImVec2(btnRect.Min.x + 3, midy), ImVec2(btnRect.Max.x - 3, midy), btnColor, 2); + if (add) + draw_list->AddLine(ImVec2(midx, btnRect.Min.y + 3), ImVec2(midx, btnRect.Max.y - 3), btnColor, 2); + return clickedBtn; + } + + bool Sequencer(SequenceInterface* sequence, int* currentFrame, bool* expanded, int* selectedEntry, int* firstFrame, int sequenceOptions) + { + bool ret = false; + ImGuiIO& io = ImGui::GetIO(); + int cx = (int)(io.MousePos.x); + int cy = (int)(io.MousePos.y); + static float framePixelWidth = 10.f; + static float framePixelWidthTarget = 10.f; + int legendWidth = 200; + + static int movingEntry = -1; + static int movingPos = -1; + static int movingPart = -1; + int delEntry = -1; + int dupEntry = -1; + int ItemHeight = 20; + + bool popupOpened = false; + int sequenceCount = sequence->GetItemCount(); + if (!sequenceCount) + return false; + ImGui::BeginGroup(); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! + ImVec2 canvas_size = ImGui::GetContentRegionAvail(); // Resize canvas to what's available + int firstFrameUsed = firstFrame ? *firstFrame : 0; + + + int controlHeight = sequenceCount * ItemHeight; + for (int i = 0; i < sequenceCount; i++) + controlHeight += int(sequence->GetCustomHeight(i)); + int frameCount = ImMax(sequence->GetFrameMax() - sequence->GetFrameMin(), 1); + + static bool MovingScrollBar = false; + static bool MovingCurrentFrame = false; + struct CustomDraw + { + int index; + ImRect customRect; + ImRect legendRect; + ImRect clippingRect; + ImRect legendClippingRect; + }; + ImVector customDraws; + ImVector compactCustomDraws; + // zoom in/out + const int visibleFrameCount = (int)floorf((canvas_size.x - legendWidth) / framePixelWidth); + const float barWidthRatio = ImMin(visibleFrameCount / (float)frameCount, 1.f); + const float barWidthInPixels = barWidthRatio * (canvas_size.x - legendWidth); + + ImRect regionRect(canvas_pos, canvas_pos + canvas_size); + + static bool panningView = false; + static ImVec2 panningViewSource; + static int panningViewFrame; + if (ImGui::IsWindowFocused() && io.KeyAlt && io.MouseDown[2]) + { + if (!panningView) + { + panningViewSource = io.MousePos; + panningView = true; + panningViewFrame = *firstFrame; + } + *firstFrame = panningViewFrame - int((io.MousePos.x - panningViewSource.x) / framePixelWidth); + *firstFrame = ImClamp(*firstFrame, sequence->GetFrameMin(), sequence->GetFrameMax() - visibleFrameCount); + } + if (panningView && !io.MouseDown[2]) + { + panningView = false; + } + framePixelWidthTarget = ImClamp(framePixelWidthTarget, 0.1f, 50.f); + + framePixelWidth = ImLerp(framePixelWidth, framePixelWidthTarget, 0.33f); + + frameCount = sequence->GetFrameMax() - sequence->GetFrameMin(); + if (visibleFrameCount >= frameCount && firstFrame) + *firstFrame = sequence->GetFrameMin(); + + + // -- + if (expanded && !*expanded) + { + ImGui::InvisibleButton("canvas", ImVec2(canvas_size.x - canvas_pos.x, (float)ItemHeight)); + draw_list->AddRectFilled(canvas_pos, ImVec2(canvas_size.x + canvas_pos.x, canvas_pos.y + ItemHeight), 0xFF3D3837, 0); + char tmps[512]; + ImFormatString(tmps, IM_ARRAYSIZE(tmps), sequence->GetCollapseFmt(), frameCount, sequenceCount); + draw_list->AddText(ImVec2(canvas_pos.x + 26, canvas_pos.y + 2), 0xFFFFFFFF, tmps); + } + else + { + bool hasScrollBar(true); + /* + int framesPixelWidth = int(frameCount * framePixelWidth); + if ((framesPixelWidth + legendWidth) >= canvas_size.x) + { + hasScrollBar = true; + } + */ + // test scroll area + ImVec2 headerSize(canvas_size.x, (float)ItemHeight); + ImVec2 scrollBarSize(canvas_size.x, 14.f); + ImGui::InvisibleButton("topBar", headerSize); + draw_list->AddRectFilled(canvas_pos, canvas_pos + headerSize, 0xFFFF0000, 0); + ImVec2 childFramePos = ImGui::GetCursorScreenPos(); + ImVec2 childFrameSize(canvas_size.x, canvas_size.y - 8.f - headerSize.y - (hasScrollBar ? scrollBarSize.y : 0)); + ImGui::PushStyleColor(ImGuiCol_FrameBg, 0); + ImGui::BeginChildFrame(889, childFrameSize); + sequence->focused = ImGui::IsWindowFocused(); + ImGui::InvisibleButton("contentBar", ImVec2(canvas_size.x, float(controlHeight))); + const ImVec2 contentMin = ImGui::GetItemRectMin(); + const ImVec2 contentMax = ImGui::GetItemRectMax(); + const ImRect contentRect(contentMin, contentMax); + const float contentHeight = contentMax.y - contentMin.y; + + // full background + draw_list->AddRectFilled(canvas_pos, canvas_pos + canvas_size, 0xFF242424, 0); + + // current frame top + ImRect topRect(ImVec2(canvas_pos.x + legendWidth, canvas_pos.y), ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + ItemHeight)); + + if (!MovingCurrentFrame && !MovingScrollBar && movingEntry == -1 && sequenceOptions & SEQUENCER_CHANGE_FRAME && currentFrame && *currentFrame >= 0 && topRect.Contains(io.MousePos) && io.MouseDown[0]) + { + MovingCurrentFrame = true; + } + if (MovingCurrentFrame) + { + if (frameCount) + { + *currentFrame = (int)((io.MousePos.x - topRect.Min.x) / framePixelWidth) + firstFrameUsed; + if (*currentFrame < sequence->GetFrameMin()) + *currentFrame = sequence->GetFrameMin(); + if (*currentFrame >= sequence->GetFrameMax()) + *currentFrame = sequence->GetFrameMax(); + } + if (!io.MouseDown[0]) + MovingCurrentFrame = false; + } + + //header + draw_list->AddRectFilled(canvas_pos, ImVec2(canvas_size.x + canvas_pos.x, canvas_pos.y + ItemHeight), 0xFF3D3837, 0); + if (sequenceOptions & SEQUENCER_ADD) + { + if (SequencerAddDelButton(draw_list, ImVec2(canvas_pos.x + legendWidth - ItemHeight, canvas_pos.y + 2), true)) + ImGui::OpenPopup("addEntry"); + + if (ImGui::BeginPopup("addEntry")) + { + for (int i = 0; i < sequence->GetItemTypeCount(); i++) + if (ImGui::Selectable(sequence->GetItemTypeName(i))) + { + sequence->Add(i); + *selectedEntry = sequence->GetItemCount() - 1; + } + + ImGui::EndPopup(); + popupOpened = true; + } + } + + //header frame number and lines + int modFrameCount = 10; + int frameStep = 1; + while ((modFrameCount * framePixelWidth) < 150) + { + modFrameCount *= 2; + frameStep *= 2; + }; + int halfModFrameCount = modFrameCount / 2; + + auto drawLine = [&](int i, int regionHeight) { + bool baseIndex = ((i % modFrameCount) == 0) || (i == sequence->GetFrameMax() || i == sequence->GetFrameMin()); + bool halfIndex = (i % halfModFrameCount) == 0; + int px = (int)canvas_pos.x + int(i * framePixelWidth) + legendWidth - int(firstFrameUsed * framePixelWidth); + int tiretStart = baseIndex ? 4 : (halfIndex ? 10 : 14); + int tiretEnd = baseIndex ? regionHeight : ItemHeight; + + if (px <= (canvas_size.x + canvas_pos.x) && px >= (canvas_pos.x + legendWidth)) + { + draw_list->AddLine(ImVec2((float)px, canvas_pos.y + (float)tiretStart), ImVec2((float)px, canvas_pos.y + (float)tiretEnd - 1), 0xFF606060, 1); + + draw_list->AddLine(ImVec2((float)px, canvas_pos.y + (float)ItemHeight), ImVec2((float)px, canvas_pos.y + (float)regionHeight - 1), 0x30606060, 1); + } + + if (baseIndex && px > (canvas_pos.x + legendWidth)) + { + char tmps[512]; + ImFormatString(tmps, IM_ARRAYSIZE(tmps), "%d", i); + draw_list->AddText(ImVec2((float)px + 3.f, canvas_pos.y), 0xFFBBBBBB, tmps); + } + + }; + + auto drawLineContent = [&](int i, int /*regionHeight*/) { + int px = (int)canvas_pos.x + int(i * framePixelWidth) + legendWidth - int(firstFrameUsed * framePixelWidth); + int tiretStart = int(contentMin.y); + int tiretEnd = int(contentMax.y); + + if (px <= (canvas_size.x + canvas_pos.x) && px >= (canvas_pos.x + legendWidth)) + { + //draw_list->AddLine(ImVec2((float)px, canvas_pos.y + (float)tiretStart), ImVec2((float)px, canvas_pos.y + (float)tiretEnd - 1), 0xFF606060, 1); + + draw_list->AddLine(ImVec2(float(px), float(tiretStart)), ImVec2(float(px), float(tiretEnd)), 0x30606060, 1); + } + }; + for (int i = sequence->GetFrameMin(); i <= sequence->GetFrameMax(); i += frameStep) + { + drawLine(i, ItemHeight); + } + drawLine(sequence->GetFrameMin(), ItemHeight); + drawLine(sequence->GetFrameMax(), ItemHeight); + /* + draw_list->AddLine(canvas_pos, ImVec2(canvas_pos.x, canvas_pos.y + controlHeight), 0xFF000000, 1); + draw_list->AddLine(ImVec2(canvas_pos.x, canvas_pos.y + ItemHeight), ImVec2(canvas_size.x, canvas_pos.y + ItemHeight), 0xFF000000, 1); + */ + // clip content + + draw_list->PushClipRect(childFramePos, childFramePos + childFrameSize, true); + + // draw item names in the legend rect on the left + size_t customHeight = 0; + for (int i = 0; i < sequenceCount; i++) + { + int type; + sequence->Get(i, NULL, NULL, &type, NULL); + ImVec2 tpos(contentMin.x + 3, contentMin.y + i * ItemHeight + 2 + customHeight); + draw_list->AddText(tpos, 0xFFFFFFFF, sequence->GetItemLabel(i)); + + if (sequenceOptions & SEQUENCER_DEL) + { + if (SequencerAddDelButton(draw_list, ImVec2(contentMin.x + legendWidth - ItemHeight + 2 - 10, tpos.y + 2), false)) + delEntry = i; + + if (SequencerAddDelButton(draw_list, ImVec2(contentMin.x + legendWidth - ItemHeight - ItemHeight + 2 - 10, tpos.y + 2), true)) + dupEntry = i; + } + customHeight += sequence->GetCustomHeight(i); + } + + // slots background + customHeight = 0; + for (int i = 0; i < sequenceCount; i++) + { + unsigned int col = (i & 1) ? 0xFF3A3636 : 0xFF413D3D; + + size_t localCustomHeight = sequence->GetCustomHeight(i); + ImVec2 pos = ImVec2(contentMin.x + legendWidth, contentMin.y + ItemHeight * i + 1 + customHeight); + ImVec2 sz = ImVec2(canvas_size.x + canvas_pos.x, pos.y + ItemHeight - 1 + localCustomHeight); + if (!popupOpened && cy >= pos.y && cy < pos.y + (ItemHeight + localCustomHeight) && movingEntry == -1 && cx>contentMin.x && cx < contentMin.x + canvas_size.x) + { + col += 0x80201008; + pos.x -= legendWidth; + } + draw_list->AddRectFilled(pos, sz, col, 0); + customHeight += localCustomHeight; + } + + draw_list->PushClipRect(childFramePos + ImVec2(float(legendWidth), 0.f), childFramePos + childFrameSize, true); + + // vertical frame lines in content area + for (int i = sequence->GetFrameMin(); i <= sequence->GetFrameMax(); i += frameStep) + { + drawLineContent(i, int(contentHeight)); + } + drawLineContent(sequence->GetFrameMin(), int(contentHeight)); + drawLineContent(sequence->GetFrameMax(), int(contentHeight)); + + // selection + bool selected = selectedEntry && (*selectedEntry >= 0); + if (selected) + { + customHeight = 0; + for (int i = 0; i < *selectedEntry; i++) + customHeight += sequence->GetCustomHeight(i); + draw_list->AddRectFilled(ImVec2(contentMin.x, contentMin.y + ItemHeight * *selectedEntry + customHeight), ImVec2(contentMin.x + canvas_size.x, contentMin.y + ItemHeight * (*selectedEntry + 1) + customHeight), 0x801080FF, 1.f); + } + + // slots + customHeight = 0; + for (int i = 0; i < sequenceCount; i++) + { + int* start, * end; + unsigned int color; + sequence->Get(i, &start, &end, NULL, &color); + size_t localCustomHeight = sequence->GetCustomHeight(i); + + ImVec2 pos = ImVec2(contentMin.x + legendWidth - firstFrameUsed * framePixelWidth, contentMin.y + ItemHeight * i + 1 + customHeight); + ImVec2 slotP1(pos.x + *start * framePixelWidth, pos.y + 2); + ImVec2 slotP2(pos.x + *end * framePixelWidth + framePixelWidth, pos.y + ItemHeight - 2); + ImVec2 slotP3(pos.x + *end * framePixelWidth + framePixelWidth, pos.y + ItemHeight - 2 + localCustomHeight); + unsigned int slotColor = color | 0xFF000000; + unsigned int slotColorHalf = (color & 0xFFFFFF) | 0x40000000; + + if (slotP1.x <= (canvas_size.x + contentMin.x) && slotP2.x >= (contentMin.x + legendWidth)) + { + draw_list->AddRectFilled(slotP1, slotP3, slotColorHalf, 2); + draw_list->AddRectFilled(slotP1, slotP2, slotColor, 2); + } + if (ImRect(slotP1, slotP2).Contains(io.MousePos) && io.MouseDoubleClicked[0]) + { + sequence->DoubleClick(i); + } + // Ensure grabbable handles + const float max_handle_width = slotP2.x - slotP1.x / 3.0f; + const float min_handle_width = ImMin(10.0f, max_handle_width); + const float handle_width = ImClamp(framePixelWidth / 2.0f, min_handle_width, max_handle_width); + ImRect rects[3] = { ImRect(slotP1, ImVec2(slotP1.x + handle_width, slotP2.y)) + , ImRect(ImVec2(slotP2.x - handle_width, slotP1.y), slotP2) + , ImRect(slotP1, slotP2) }; + + const unsigned int quadColor[] = { 0xFFFFFFFF, 0xFFFFFFFF, slotColor + (selected ? 0 : 0x202020) }; + if (movingEntry == -1 && (sequenceOptions & SEQUENCER_EDIT_STARTEND))// TODOFOCUS && backgroundRect.Contains(io.MousePos)) + { + for (int j = 2; j >= 0; j--) + { + ImRect& rc = rects[j]; + if (!rc.Contains(io.MousePos)) + continue; + draw_list->AddRectFilled(rc.Min, rc.Max, quadColor[j], 2); + } + + for (int j = 0; j < 3; j++) + { + ImRect& rc = rects[j]; + if (!rc.Contains(io.MousePos)) + continue; + if (!ImRect(childFramePos, childFramePos + childFrameSize).Contains(io.MousePos)) + continue; + if (ImGui::IsMouseClicked(0) && !MovingScrollBar && !MovingCurrentFrame) + { + movingEntry = i; + movingPos = cx; + movingPart = j + 1; + sequence->BeginEdit(movingEntry); + break; + } + } + } + + // custom draw + if (localCustomHeight > 0) + { + ImVec2 rp(canvas_pos.x, contentMin.y + ItemHeight * i + 1 + customHeight); + ImRect customRect(rp + ImVec2(legendWidth - (firstFrameUsed - sequence->GetFrameMin() - 0.5f) * framePixelWidth, float(ItemHeight)), + rp + ImVec2(legendWidth + (sequence->GetFrameMax() - firstFrameUsed - 0.5f + 2.f) * framePixelWidth, float(localCustomHeight + ItemHeight))); + ImRect clippingRect(rp + ImVec2(float(legendWidth), float(ItemHeight)), rp + ImVec2(canvas_size.x, float(localCustomHeight + ItemHeight))); + + ImRect legendRect(rp + ImVec2(0.f, float(ItemHeight)), rp + ImVec2(float(legendWidth), float(localCustomHeight))); + ImRect legendClippingRect(canvas_pos + ImVec2(0.f, float(ItemHeight)), canvas_pos + ImVec2(float(legendWidth), float(localCustomHeight + ItemHeight))); + customDraws.push_back({ i, customRect, legendRect, clippingRect, legendClippingRect }); + } + else + { + ImVec2 rp(canvas_pos.x, contentMin.y + ItemHeight * i + customHeight); + ImRect customRect(rp + ImVec2(legendWidth - (firstFrameUsed - sequence->GetFrameMin() - 0.5f) * framePixelWidth, float(0.f)), + rp + ImVec2(legendWidth + (sequence->GetFrameMax() - firstFrameUsed - 0.5f + 2.f) * framePixelWidth, float(ItemHeight))); + ImRect clippingRect(rp + ImVec2(float(legendWidth), float(0.f)), rp + ImVec2(canvas_size.x, float(ItemHeight))); + + compactCustomDraws.push_back({ i, customRect, ImRect(), clippingRect, ImRect() }); + } + customHeight += localCustomHeight; + } + + + // moving + if (/*backgroundRect.Contains(io.MousePos) && */movingEntry >= 0) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + int diffFrame = int((cx - movingPos) / framePixelWidth); + if (std::abs(diffFrame) > 0) + { + int* start, * end; + sequence->Get(movingEntry, &start, &end, NULL, NULL); + if (selectedEntry) + *selectedEntry = movingEntry; + int& l = *start; + int& r = *end; + if (movingPart & 1) + l += diffFrame; + if (movingPart & 2) + r += diffFrame; + if (l < 0) + { + if (movingPart & 2) + r -= l; + l = 0; + } + if (movingPart & 1 && l > r) + l = r; + if (movingPart & 2 && r < l) + r = l; + movingPos += int(diffFrame * framePixelWidth); + } + if (!io.MouseDown[0]) + { + // single select + if (!diffFrame && movingPart && selectedEntry) + { + *selectedEntry = movingEntry; + ret = true; + } + + movingEntry = -1; + sequence->EndEdit(); + } + } + + // cursor + if (currentFrame && firstFrame && *currentFrame >= *firstFrame && *currentFrame <= sequence->GetFrameMax()) + { + static const float cursorWidth = 8.f; + float cursorOffset = contentMin.x + legendWidth + (*currentFrame - firstFrameUsed) * framePixelWidth + framePixelWidth / 2 - cursorWidth * 0.5f; + draw_list->AddLine(ImVec2(cursorOffset, canvas_pos.y), ImVec2(cursorOffset, contentMax.y), 0xA02A2AFF, cursorWidth); + char tmps[512]; + ImFormatString(tmps, IM_ARRAYSIZE(tmps), "%d", *currentFrame); + draw_list->AddText(ImVec2(cursorOffset + 10, canvas_pos.y + 2), 0xFF2A2AFF, tmps); + } + + draw_list->PopClipRect(); + draw_list->PopClipRect(); + + for (auto& customDraw : customDraws) + sequence->CustomDraw(customDraw.index, draw_list, customDraw.customRect, customDraw.legendRect, customDraw.clippingRect, customDraw.legendClippingRect); + for (auto& customDraw : compactCustomDraws) + sequence->CustomDrawCompact(customDraw.index, draw_list, customDraw.customRect, customDraw.clippingRect); + + // copy paste + if (sequenceOptions & SEQUENCER_COPYPASTE) + { + ImRect rectCopy(ImVec2(contentMin.x + 100, canvas_pos.y + 2) + , ImVec2(contentMin.x + 100 + 30, canvas_pos.y + ItemHeight - 2)); + bool inRectCopy = rectCopy.Contains(io.MousePos); + unsigned int copyColor = inRectCopy ? 0xFF1080FF : 0xFF000000; + draw_list->AddText(rectCopy.Min, copyColor, "Copy"); + + ImRect rectPaste(ImVec2(contentMin.x + 140, canvas_pos.y + 2) + , ImVec2(contentMin.x + 140 + 30, canvas_pos.y + ItemHeight - 2)); + bool inRectPaste = rectPaste.Contains(io.MousePos); + unsigned int pasteColor = inRectPaste ? 0xFF1080FF : 0xFF000000; + draw_list->AddText(rectPaste.Min, pasteColor, "Paste"); + + if (inRectCopy && io.MouseReleased[0]) + { + sequence->Copy(); + } + if (inRectPaste && io.MouseReleased[0]) + { + sequence->Paste(); + } + } + // + + ImGui::EndChildFrame(); + ImGui::PopStyleColor(); + if (hasScrollBar) + { + ImGui::InvisibleButton("scrollBar", scrollBarSize); + ImVec2 scrollBarMin = ImGui::GetItemRectMin(); + ImVec2 scrollBarMax = ImGui::GetItemRectMax(); + + // ratio = number of frames visible in control / number to total frames + + float startFrameOffset = ((float)(firstFrameUsed - sequence->GetFrameMin()) / (float)frameCount) * (canvas_size.x - legendWidth); + ImVec2 scrollBarA(scrollBarMin.x + legendWidth, scrollBarMin.y - 2); + ImVec2 scrollBarB(scrollBarMin.x + canvas_size.x, scrollBarMax.y - 1); + draw_list->AddRectFilled(scrollBarA, scrollBarB, 0xFF222222, 0); + + ImRect scrollBarRect(scrollBarA, scrollBarB); + bool inScrollBar = scrollBarRect.Contains(io.MousePos); + + draw_list->AddRectFilled(scrollBarA, scrollBarB, 0xFF101010, 8); + + + ImVec2 scrollBarC(scrollBarMin.x + legendWidth + startFrameOffset, scrollBarMin.y); + ImVec2 scrollBarD(scrollBarMin.x + legendWidth + barWidthInPixels + startFrameOffset, scrollBarMax.y - 2); + draw_list->AddRectFilled(scrollBarC, scrollBarD, (inScrollBar || MovingScrollBar) ? 0xFF606060 : 0xFF505050, 6); + + ImRect barHandleLeft(scrollBarC, ImVec2(scrollBarC.x + 14, scrollBarD.y)); + ImRect barHandleRight(ImVec2(scrollBarD.x - 14, scrollBarC.y), scrollBarD); + + bool onLeft = barHandleLeft.Contains(io.MousePos); + bool onRight = barHandleRight.Contains(io.MousePos); + + static bool sizingRBar = false; + static bool sizingLBar = false; + + draw_list->AddRectFilled(barHandleLeft.Min, barHandleLeft.Max, (onLeft || sizingLBar) ? 0xFFAAAAAA : 0xFF666666, 6); + draw_list->AddRectFilled(barHandleRight.Min, barHandleRight.Max, (onRight || sizingRBar) ? 0xFFAAAAAA : 0xFF666666, 6); + + ImRect scrollBarThumb(scrollBarC, scrollBarD); + static const float MinBarWidth = 44.f; + if (sizingRBar) + { + if (!io.MouseDown[0]) + { + sizingRBar = false; + } + else + { + float barNewWidth = ImMax(barWidthInPixels + io.MouseDelta.x, MinBarWidth); + float barRatio = barNewWidth / barWidthInPixels; + framePixelWidthTarget = framePixelWidth = framePixelWidth / barRatio; + int newVisibleFrameCount = int((canvas_size.x - legendWidth) / framePixelWidthTarget); + int lastFrame = *firstFrame + newVisibleFrameCount; + if (lastFrame > sequence->GetFrameMax()) + { + framePixelWidthTarget = framePixelWidth = (canvas_size.x - legendWidth) / float(sequence->GetFrameMax() - *firstFrame); + } + } + } + else if (sizingLBar) + { + if (!io.MouseDown[0]) + { + sizingLBar = false; + } + else + { + if (fabsf(io.MouseDelta.x) > FLT_EPSILON) + { + float barNewWidth = ImMax(barWidthInPixels - io.MouseDelta.x, MinBarWidth); + float barRatio = barNewWidth / barWidthInPixels; + float previousFramePixelWidthTarget = framePixelWidthTarget; + framePixelWidthTarget = framePixelWidth = framePixelWidth / barRatio; + int newVisibleFrameCount = int(visibleFrameCount / barRatio); + int newFirstFrame = *firstFrame + newVisibleFrameCount - visibleFrameCount; + newFirstFrame = ImClamp(newFirstFrame, sequence->GetFrameMin(), ImMax(sequence->GetFrameMax() - visibleFrameCount, sequence->GetFrameMin())); + if (newFirstFrame == *firstFrame) + { + framePixelWidth = framePixelWidthTarget = previousFramePixelWidthTarget; + } + else + { + *firstFrame = newFirstFrame; + } + } + } + } + else + { + if (MovingScrollBar) + { + if (!io.MouseDown[0]) + { + MovingScrollBar = false; + } + else + { + float framesPerPixelInBar = barWidthInPixels / (float)visibleFrameCount; + *firstFrame = int((io.MousePos.x - panningViewSource.x) / framesPerPixelInBar) - panningViewFrame; + *firstFrame = ImClamp(*firstFrame, sequence->GetFrameMin(), ImMax(sequence->GetFrameMax() - visibleFrameCount, sequence->GetFrameMin())); + } + } + else + { + if (scrollBarThumb.Contains(io.MousePos) && ImGui::IsMouseClicked(0) && firstFrame && !MovingCurrentFrame && movingEntry == -1) + { + MovingScrollBar = true; + panningViewSource = io.MousePos; + panningViewFrame = -*firstFrame; + } + if (!sizingRBar && onRight && ImGui::IsMouseClicked(0)) + sizingRBar = true; + if (!sizingLBar && onLeft && ImGui::IsMouseClicked(0)) + sizingLBar = true; + + } + } + } + } + + ImGui::EndGroup(); + + if (regionRect.Contains(io.MousePos)) + { + bool overCustomDraw = false; + for (auto& custom : customDraws) + { + if (custom.customRect.Contains(io.MousePos)) + { + overCustomDraw = true; + } + } + if (overCustomDraw) + { + } + else + { +#if 0 + frameOverCursor = *firstFrame + (int)(visibleFrameCount * ((io.MousePos.x - (float)legendWidth - canvas_pos.x) / (canvas_size.x - legendWidth))); + //frameOverCursor = max(min(*firstFrame - visibleFrameCount / 2, frameCount - visibleFrameCount), 0); + + /**firstFrame -= frameOverCursor; + *firstFrame *= framePixelWidthTarget / framePixelWidth; + *firstFrame += frameOverCursor;*/ + if (io.MouseWheel < -FLT_EPSILON) + { + *firstFrame -= frameOverCursor; + *firstFrame = int(*firstFrame * 1.1f); + framePixelWidthTarget *= 0.9f; + *firstFrame += frameOverCursor; + } + + if (io.MouseWheel > FLT_EPSILON) + { + *firstFrame -= frameOverCursor; + *firstFrame = int(*firstFrame * 0.9f); + framePixelWidthTarget *= 1.1f; + *firstFrame += frameOverCursor; + } +#endif + } + } + + if (expanded) + { + if (SequencerAddDelButton(draw_list, ImVec2(canvas_pos.x + 2, canvas_pos.y + 2), !*expanded)) + *expanded = !*expanded; + } + + if (delEntry != -1) + { + sequence->Del(delEntry); + if (selectedEntry && (*selectedEntry == delEntry || *selectedEntry >= sequence->GetItemCount())) + *selectedEntry = -1; + } + + if (dupEntry != -1) + { + sequence->Duplicate(dupEntry); + } + return ret; + } +} diff --git a/editor/ImGui/Source/ImGuizmo/ImSequencer.h b/editor/ImGui/Source/ImGuizmo/ImSequencer.h new file mode 100644 index 000000000..091b08b26 --- /dev/null +++ b/editor/ImGui/Source/ImGuizmo/ImSequencer.h @@ -0,0 +1,77 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#pragma once + +#include + +struct ImDrawList; +struct ImRect; +namespace ImSequencer +{ + enum SEQUENCER_OPTIONS + { + SEQUENCER_EDIT_NONE = 0, + SEQUENCER_EDIT_STARTEND = 1 << 1, + SEQUENCER_CHANGE_FRAME = 1 << 3, + SEQUENCER_ADD = 1 << 4, + SEQUENCER_DEL = 1 << 5, + SEQUENCER_COPYPASTE = 1 << 6, + SEQUENCER_EDIT_ALL = SEQUENCER_EDIT_STARTEND | SEQUENCER_CHANGE_FRAME + }; + + struct SequenceInterface + { + bool focused = false; + virtual int GetFrameMin() const = 0; + virtual int GetFrameMax() const = 0; + virtual int GetItemCount() const = 0; + + virtual void BeginEdit(int /*index*/) {} + virtual void EndEdit() {} + virtual int GetItemTypeCount() const { return 0; } + virtual const char* GetItemTypeName(int /*typeIndex*/) const { return ""; } + virtual const char* GetItemLabel(int /*index*/) const { return ""; } + virtual const char* GetCollapseFmt() const { return "%d Frames / %d entries"; } + + virtual void Get(int index, int** start, int** end, int* type, unsigned int* color) = 0; + virtual void Add(int /*type*/) {} + virtual void Del(int /*index*/) {} + virtual void Duplicate(int /*index*/) {} + + virtual void Copy() {} + virtual void Paste() {} + + virtual size_t GetCustomHeight(int /*index*/) { return 0; } + virtual void DoubleClick(int /*index*/) {} + virtual void CustomDraw(int /*index*/, ImDrawList* /*draw_list*/, const ImRect& /*rc*/, const ImRect& /*legendRect*/, const ImRect& /*clippingRect*/, const ImRect& /*legendClippingRect*/) {} + virtual void CustomDrawCompact(int /*index*/, ImDrawList* /*draw_list*/, const ImRect& /*rc*/, const ImRect& /*clippingRect*/) {} + }; + + + // return true if selection is made + bool Sequencer(SequenceInterface* sequence, int* currentFrame, bool* expanded, int* selectedEntry, int* firstFrame, int sequenceOptions); + +} diff --git a/editor/ImGui/Source/ImGuizmo/ImZoomSlider.h b/editor/ImGui/Source/ImGuizmo/ImZoomSlider.h new file mode 100644 index 000000000..665a4b53e --- /dev/null +++ b/editor/ImGui/Source/ImGuizmo/ImZoomSlider.h @@ -0,0 +1,245 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#pragma once + +namespace ImZoomSlider +{ + typedef int ImGuiZoomSliderFlags; + enum ImGuiPopupFlags_ + { + ImGuiZoomSliderFlags_None = 0, + ImGuiZoomSliderFlags_Vertical = 1, + ImGuiZoomSliderFlags_NoAnchors = 2, + ImGuiZoomSliderFlags_NoMiddleCarets = 4, + ImGuiZoomSliderFlags_NoWheel = 8, + }; + + template bool ImZoomSlider(const T lower, const T higher, T& viewLower, T& viewHigher, float wheelRatio = 0.01f, ImGuiZoomSliderFlags flags = ImGuiZoomSliderFlags_None) + { + bool interacted = false; + ImGuiIO& io = ImGui::GetIO(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + static const float handleSize = 12; + static const float roundRadius = 3.f; + static const char* controlName = "ImZoomSlider"; + + static bool movingScrollBarSvg = false; + static bool sizingRBarSvg = false; + static bool sizingLBarSvg = false; + static ImGuiID editingId = (ImGuiID)-1; + static float scrollingSource = 0.f; + static float saveViewLower; + static float saveViewHigher; + + const bool isVertical = flags & ImGuiZoomSliderFlags_Vertical; + const ImVec2 canvasPos = ImGui::GetCursorScreenPos(); + const ImVec2 canvasSize = ImGui::GetContentRegionAvail(); + const float canvasSizeLength = isVertical ? ImGui::GetItemRectSize().y : canvasSize.x; + const ImVec2 scrollBarSize = isVertical ? ImVec2(14.f, canvasSizeLength) : ImVec2(canvasSizeLength, 14.f); + + ImGui::InvisibleButton(controlName, scrollBarSize); + const ImGuiID currentId = ImGui::GetID(controlName); + + const bool usingEditingId = currentId == editingId; + const bool canUseControl = usingEditingId || editingId == -1; + const bool movingScrollBar = usingEditingId ? movingScrollBarSvg : false; + const bool sizingRBar = usingEditingId ? sizingRBarSvg : false; + const bool sizingLBar = usingEditingId ? sizingLBarSvg : false; + const int componentIndex = isVertical ? 1 : 0; + const ImVec2 scrollBarMin = ImGui::GetItemRectMin(); + const ImVec2 scrollBarMax = ImGui::GetItemRectMax(); + const ImVec2 scrollBarA = ImVec2(scrollBarMin.x, scrollBarMin.y) - (isVertical ? ImVec2(2,0) : ImVec2(0,2)); + const ImVec2 scrollBarB = isVertical ? ImVec2(scrollBarMax.x - 1.f, scrollBarMin.y + canvasSizeLength) : ImVec2(scrollBarMin.x + canvasSizeLength, scrollBarMax.y - 1.f); + const float scrollStart = ((viewLower - lower) / (higher - lower)) * canvasSizeLength + scrollBarMin[componentIndex]; + const float scrollEnd = ((viewHigher - lower) / (higher - lower)) * canvasSizeLength + scrollBarMin[componentIndex]; + const float screenSize = scrollEnd - scrollStart; + const ImVec2 scrollTopLeft = isVertical ? ImVec2(scrollBarMin.x, scrollStart) : ImVec2(scrollStart, scrollBarMin.y); + const ImVec2 scrollBottomRight = isVertical ? ImVec2(scrollBarMax.x - 2.f, scrollEnd) : ImVec2(scrollEnd, scrollBarMax.y - 2.f); + const bool inScrollBar = canUseControl && ImRect(scrollTopLeft, scrollBottomRight).Contains(io.MousePos); + const ImRect scrollBarRect(scrollBarA, scrollBarB); + const float deltaScreen = io.MousePos[componentIndex] - scrollingSource; + const float deltaView = ((higher - lower) / canvasSizeLength) * deltaScreen; + const uint32_t barColor = ImGui::GetColorU32((inScrollBar || movingScrollBar) ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + const float middleCoord = (scrollStart + scrollEnd) * 0.5f; + const bool insideControl = canUseControl && ImRect(scrollBarMin, scrollBarMax).Contains(io.MousePos); + const bool hasAnchors = !(flags & ImGuiZoomSliderFlags_NoAnchors); + const float viewMinSize = ((3.f * handleSize) / canvasSizeLength) * (higher - lower); + const auto ClipView = [lower, higher, &viewLower, &viewHigher]() { + if (viewLower < lower) + { + const float deltaClip = lower - viewLower; + viewLower += deltaClip; + viewHigher += deltaClip; + } + if (viewHigher > higher) + { + const float deltaClip = viewHigher - higher; + viewLower -= deltaClip; + viewHigher -= deltaClip; + } + }; + + bool onLeft = false; + bool onRight = false; + + draw_list->AddRectFilled(scrollBarA, scrollBarB, 0xFF101010, roundRadius); + draw_list->AddRectFilled(scrollBarA, scrollBarB, 0xFF222222, 0); + draw_list->AddRectFilled(scrollTopLeft, scrollBottomRight, barColor, roundRadius); + + if (!(flags & ImGuiZoomSliderFlags_NoMiddleCarets)) + { + for (float i = 0.5f; i < 3.f; i += 1.f) + { + const float coordA = middleCoord - handleSize * 0.5f; + const float coordB = middleCoord + handleSize * 0.5f; + ImVec2 base = scrollBarMin; + base.x += scrollBarSize.x * 0.25f * i; + base.y += scrollBarSize.y * 0.25f * i; + + if (isVertical) + { + draw_list->AddLine(ImVec2(base.x, coordA), ImVec2(base.x, coordB), ImGui::GetColorU32(ImGuiCol_SliderGrab)); + } + else + { + draw_list->AddLine(ImVec2(coordA, base.y), ImVec2(coordB, base.y), ImGui::GetColorU32(ImGuiCol_SliderGrab)); + } + } + } + + // Mouse wheel + if (io.MouseClicked[0] && insideControl && !inScrollBar) + { + const float ratio = (io.MousePos[componentIndex] - scrollBarMin[componentIndex]) / (scrollBarMax[componentIndex] - scrollBarMin[componentIndex]); + const float size = (higher - lower); + const float halfViewSize = (viewHigher - viewLower) * 0.5f; + const float middle = ratio * size + lower; + viewLower = middle - halfViewSize; + viewHigher = middle + halfViewSize; + ClipView(); + interacted = true; + } + + if (!(flags & ImGuiZoomSliderFlags_NoWheel) && inScrollBar && fabsf(io.MouseWheel) > 0.f) + { + const float ratio = (io.MousePos[componentIndex] - scrollStart) / (scrollEnd - scrollStart); + const float amount = io.MouseWheel * wheelRatio * (viewHigher - viewLower); + + viewLower -= ratio * amount; + viewHigher += (1.f - ratio) * amount; + ClipView(); + interacted = true; + } + + if (screenSize > handleSize * 2.f && hasAnchors) + { + const ImRect barHandleLeft(scrollTopLeft, isVertical ? ImVec2(scrollBottomRight.x, scrollTopLeft.y + handleSize) : ImVec2(scrollTopLeft.x + handleSize, scrollBottomRight.y)); + const ImRect barHandleRight(isVertical ? ImVec2(scrollTopLeft.x, scrollBottomRight.y - handleSize) : ImVec2(scrollBottomRight.x - handleSize, scrollTopLeft.y), scrollBottomRight); + + onLeft = barHandleLeft.Contains(io.MousePos); + onRight = barHandleRight.Contains(io.MousePos); + + draw_list->AddRectFilled(barHandleLeft.Min, barHandleLeft.Max, ImGui::GetColorU32((onLeft || sizingLBar) ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), roundRadius); + draw_list->AddRectFilled(barHandleRight.Min, barHandleRight.Max, ImGui::GetColorU32((onRight || sizingRBar) ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), roundRadius); + } + + if (sizingRBar) + { + if (!io.MouseDown[0]) + { + sizingRBarSvg = false; + editingId = (ImGuiID)-1; + } + else + { + viewHigher = ImMin(saveViewHigher + deltaView, higher); + } + } + else if (sizingLBar) + { + if (!io.MouseDown[0]) + { + sizingLBarSvg = false; + editingId = (ImGuiID)-1; + } + else + { + viewLower = ImMax(saveViewLower + deltaView, lower); + } + } + else + { + if (movingScrollBar) + { + if (!io.MouseDown[0]) + { + movingScrollBarSvg = false; + editingId = (ImGuiID)-1; + } + else + { + viewLower = saveViewLower + deltaView; + viewHigher = saveViewHigher + deltaView; + ClipView(); + } + } + else + { + if (inScrollBar && ImGui::IsMouseClicked(0)) + { + movingScrollBarSvg = true; + scrollingSource = io.MousePos[componentIndex]; + saveViewLower = viewLower; + saveViewHigher = viewHigher; + editingId = currentId; + } + if (!sizingRBar && onRight && ImGui::IsMouseClicked(0) && hasAnchors) + { + sizingRBarSvg = true; + editingId = currentId; + } + if (!sizingLBar && onLeft && ImGui::IsMouseClicked(0) && hasAnchors) + { + sizingLBarSvg = true; + editingId = currentId; + } + } + } + + // minimal size check + if ((viewHigher - viewLower) < viewMinSize) + { + const float middle = (viewLower + viewHigher) * 0.5f; + viewLower = middle - viewMinSize * 0.5f; + viewHigher = middle + viewMinSize * 0.5f; + ClipView(); + } + + return movingScrollBar || sizingRBar || sizingLBar || interacted; + } + +} // namespace diff --git a/editor/ImGui/Source/ImGuizmo/LICENSE b/editor/ImGui/Source/ImGuizmo/LICENSE new file mode 100644 index 000000000..dcbee451c --- /dev/null +++ b/editor/ImGui/Source/ImGuizmo/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Cedric Guillemet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/editor/ImGui/Source/ImGuizmo/README.md b/editor/ImGui/Source/ImGuizmo/README.md new file mode 100644 index 000000000..c4b081839 --- /dev/null +++ b/editor/ImGui/Source/ImGuizmo/README.md @@ -0,0 +1,192 @@ +# ImGuizmo + +Latest stable tagged version is 1.83. Current master version is 1.84 WIP. + +What started with the gizmo is now a collection of dear imgui widgets and more advanced controls. + +## Guizmos + +### ImViewGizmo + +Manipulate view orientation with 1 single line of code + +![Image of ImViewGizmo](http://i.imgur.com/7UVcyDd.gif) + +### ImGuizmo + +ImGizmo is a small (.h and .cpp) library built ontop of Dear ImGui that allow you to manipulate(Rotate & translate at the moment) 4x4 float matrices. No other dependancies. Coded with Immediate Mode (IM) philosophy in mind. + +Built against DearImgui 1.53WIP + +![Image of Rotation](http://i.imgur.com/y4mcVoT.gif) +![Image of Translation](http://i.imgur.com/o8q8iHq.gif) +![Image of Bounds](http://i.imgur.com/3Ez5LBr.gif) + +There is now a sample for Win32/OpenGL ! With a binary in bin directory. +![Image of Sample](https://i.imgur.com/nXlzyqD.png) + +### ImSequencer + +A WIP little sequencer used to edit frame start/end for different events in a timeline. +![Image of Rotation](http://i.imgur.com/BeyNwCn.png) +Check the sample for the documentation. More to come... + +### Graph Editor + +Nodes + connections. Custom draw inside nodes is possible with the delegate system in place. +![Image of GraphEditor](Images/nodeeditor.jpg) + +### API doc + +Call BeginFrame right after ImGui_XXXX_NewFrame(); + +```C++ +void BeginFrame(); +``` + +return true if mouse cursor is over any gizmo control (axis, plan or screen component) + +```C++ +bool IsOver();** +``` + +return true if mouse IsOver or if the gizmo is in moving state + +```C++ +bool IsUsing();** +``` + +enable/disable the gizmo. Stay in the state until next call to Enable. gizmo is rendered with gray half transparent color when disabled + +```C++ +void Enable(bool enable);** +``` + +helper functions for manualy editing translation/rotation/scale with an input float +translation, rotation and scale float points to 3 floats each +Angles are in degrees (more suitable for human editing) +example: + +```C++ + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16); +``` + +These functions have some numerical stability issues for now. Use with caution. + +```C++ +void DecomposeMatrixToComponents(const float *matrix, float *translation, float *rotation, float *scale); +void RecomposeMatrixFromComponents(const float *translation, const float *rotation, const float *scale, float *matrix);** +``` + +Render a cube with face color corresponding to face normal. Usefull for debug/test + +```C++ +void DrawCube(const float *view, const float *projection, float *matrix);** +``` + +Call it when you want a gizmo +Needs view and projection matrices. +Matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional. snap points to a float[3] for translation and to a single float for scale or rotation. Snap angle is in Euler Degrees. + +```C++ + enum OPERATION + { + TRANSLATE, + ROTATE, + SCALE + }; + + enum MODE + { + LOCAL, + WORLD + }; + +void Manipulate(const float *view, const float *projection, OPERATION operation, MODE mode, float *matrix, float *deltaMatrix = 0, float *snap = 0);** +``` + +### ImGui Example + +Code for : + +![Image of dialog](http://i.imgur.com/GL5flN1.png) + +```C++ +void EditTransform(const Camera& camera, matrix_t& matrix) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); + if (ImGui::IsKeyPressed(90)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(69)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + static bool useSnap(false); + if (ImGui::IsKeyPressed(83)) + useSnap = !useSnap; + ImGui::Checkbox("", &useSnap); + ImGui::SameLine(); + vec_t snap; + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + snap = config.mSnapTranslation; + ImGui::InputFloat3("Snap", &snap.x); + break; + case ImGuizmo::ROTATE: + snap = config.mSnapRotation; + ImGui::InputFloat("Angle Snap", &snap.x); + break; + case ImGuizmo::SCALE: + snap = config.mSnapScale; + ImGui::InputFloat("Scale Snap", &snap.x); + break; + } + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); +} +``` + +## Install + +ImGuizmo can be installed via [vcpkg](https://github.com/microsoft/vcpkg) and used cmake + +```bash +vcpkg install imguizmo +``` + +See the [vcpkg example](/vcpkg-example) for more details + +## License + +ImGuizmo is licensed under the MIT License, see [LICENSE](/LICENSE) for more information. diff --git a/editor/Widgets/RenderOptions.cpp b/editor/Widgets/RenderOptions.cpp index 53a4e6b90..6d5904330 100644 --- a/editor/Widgets/RenderOptions.cpp +++ b/editor/Widgets/RenderOptions.cpp @@ -430,13 +430,7 @@ void RenderOptions::TickVisible() if (helper::Option("Gizmos", false)) { - helper::CheckBox("Transform", debug_transform); - { - ImGui::BeginDisabled(!debug_transform); - helper::Float("Transform size", m_context->GetSystem()->m_gizmo_transform_size, 0.0025f); - ImGui::EndDisabled(); - } - + helper::CheckBox("Transform", debug_transform); helper::CheckBox("Selection outline", debug_selection_outline); helper::CheckBox("Physics", debug_physics); helper::CheckBox("AABBs - Axis-aligned bounding boxes", debug_aabb); diff --git a/editor/Widgets/Viewport.cpp b/editor/Widgets/Viewport.cpp index 06c7cea9c..b8ba5219b 100644 --- a/editor/Widgets/Viewport.cpp +++ b/editor/Widgets/Viewport.cpp @@ -19,14 +19,17 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -//= INCLUDES ======================= +//= INCLUDES ============================================ #include "Viewport.h" #include "AssetViewer.h" #include "Core/Timer.h" #include "Rendering/Renderer.h" -#include "../ImGui/ImGuiExtension.h" +#include "Event.h" #include "../Editor.h" -//================================== +#include "../ImGui/ImGuiExtension.h" +#include "../ImGui/Implementation/ImGui_TransformGizmo.h" +#include "WorldViewer.h" +//======================================================= //= NAMESPACES ========= using namespace std; @@ -43,6 +46,8 @@ Viewport::Viewport(Editor* editor) : Widget(editor) m_world = m_context->GetSystem(); m_renderer = m_context->GetSystem(); m_input = m_context->GetSystem(); + + //ImGui::TransformGizmo::apply_style(); } void Viewport::TickVisible() @@ -79,15 +84,22 @@ void Viewport::TickVisible() // Let the input system know if the mouse is within the viewport m_input->SetMouseIsInViewport(ImGui::IsItemHovered()); - // If this widget was released, make the engine pick an entity. - if (ImGui::IsMouseClicked(0) && ImGui::IsItemHovered()) - { - EditorHelper::PickEntity(); - } - // Handle model drop if (auto payload = ImGui_SP::receive_drag_drop_payload(ImGui_SP::DragPayloadType::Model)) { m_editor->GetWidget()->ShowMeshImportDialog(get(payload->data)); } + + // Mouse picking + if (ImGui::IsMouseClicked(0) && ImGui::IsItemHovered() && ImGui::TransformGizmo::allow_picking()) + { + m_renderer->GetCamera()->Pick(); + m_editor->GetWidget()->SetSelectedEntity(m_renderer->GetCamera()->GetSelectedEntity()); + } + + // Entity transform gizmo (will only show if an entity has been picked) + if (m_renderer->GetOption(Spartan::RendererOption::Debug_TransformHandle)) + { + ImGui::TransformGizmo::tick(m_context); + } } diff --git a/editor/Widgets/WorldViewer.cpp b/editor/Widgets/WorldViewer.cpp index d72978f89..42a9514ca 100644 --- a/editor/Widgets/WorldViewer.cpp +++ b/editor/Widgets/WorldViewer.cpp @@ -41,14 +41,15 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "World/Components/ReflectionProbe.h" //=========================================== -//= NAMESPACES ========== +//= NAMESPACES ========= using namespace std; using namespace Spartan; -//======================= +//====================== static ImGui_SP::DragDropPayload g_payload; static bool popup_rename_entity = false; static World* world = nullptr; +static Renderer* renderer = nullptr; static Input* input = nullptr; static Entity* entity_copied = nullptr; static Entity* entity_hovered = nullptr; @@ -59,11 +60,9 @@ WorldViewer::WorldViewer(Editor* editor) : Widget(editor) m_title = "World"; m_flags |= ImGuiWindowFlags_HorizontalScrollbar; - world = m_context->GetSystem(); - input = m_context->GetSystem(); - - // Subscribe to entity clicked engine event - EditorHelper::on_entity_selected = [this](){ SetSelectedEntity(EditorHelper::selected_entity.lock(), false); }; + world = m_context->GetSystem(); + renderer = m_context->GetSystem(); + input = m_context->GetSystem(); } void WorldViewer::TickVisible() @@ -182,17 +181,20 @@ void WorldViewer::TreeAddEntity(Entity* entity) node_flags |= has_visible_children ? ImGuiTreeNodeFlags_OpenOnArrow : ImGuiTreeNodeFlags_Leaf; // Flag - Is selected? - if (const shared_ptr selected_entity = EditorHelper::selected_entity.lock()) + if (shared_ptr camera = renderer->GetCamera()) { - node_flags |= selected_entity->GetObjectId() == entity->GetObjectId() ? ImGuiTreeNodeFlags_Selected : node_flags; - - if (m_expand_to_selection) + if (shared_ptr selected_entity = camera->GetSelectedEntity()) { - // If the selected entity is a descendant of the this entity, start expanding (this can happen if an entity is selected in the viewport) - if (selected_entity->GetTransform()->IsDescendantOf(entity->GetTransform())) + node_flags |= selected_entity->GetObjectId() == entity->GetObjectId() ? ImGuiTreeNodeFlags_Selected : node_flags; + + if (m_expand_to_selection) { - ImGui::SetNextItemOpen(true); - m_expanded_to_selection = true; + // If the selected entity is a descendant of the this entity, start expanding (this can happen if an entity is selected in the viewport) + if (selected_entity->GetTransform()->IsDescendantOf(entity->GetTransform())) + { + ImGui::SetNextItemOpen(true); + m_expanded_to_selection = true; + } } } } @@ -248,7 +250,7 @@ void WorldViewer::HandleClicking() // Left click on item - Don't select yet if (left_click && entity_hovered) { - entity_clicked = entity_hovered; + entity_clicked = entity_hovered; } // Right click on item - Select and show context menu @@ -293,14 +295,13 @@ void WorldViewer::EntityHandleDragDrop(Entity* entity_ptr) const } } -void WorldViewer::SetSelectedEntity(const shared_ptr& entity, const bool from_editor /*= true*/) +void WorldViewer::SetSelectedEntity(const shared_ptr& entity) { m_expand_to_selection = true; - // If the update comes from this widget, let the engine know about it - if (from_editor) + if (shared_ptr camera = renderer->GetCamera()) { - EditorHelper::SetSelectedEntity(entity); + camera->SetSelectedEntity(entity); } Properties::Inspect(entity); @@ -317,8 +318,14 @@ void WorldViewer::PopupContextMenu() const if (!ImGui::BeginPopup("##HierarchyContextMenu")) return; - const auto selected_entity = EditorHelper::selected_entity.lock(); - const auto on_entity = selected_entity != nullptr; + // Get selected entity + shared_ptr selected_entity = nullptr; + if (shared_ptr camera = renderer->GetCamera()) + { + selected_entity = camera->GetSelectedEntity(); + } + + const bool on_entity = selected_entity != nullptr; if (on_entity) if (ImGui::MenuItem("Copy")) { @@ -474,7 +481,7 @@ void WorldViewer::PopupEntityRename() const if (ImGui::BeginPopup("##RenameEntity")) { - auto selected_entity = EditorHelper::selected_entity.lock(); + shared_ptr selected_entity = renderer->GetCamera()->GetSelectedEntity(); if (!selected_entity) { ImGui::CloseCurrentPopup(); @@ -503,9 +510,9 @@ void WorldViewer::HandleKeyShortcuts() // Delete if (input->GetKey(KeyCode::Delete)) { - if (shared_ptr entity = EditorHelper::selected_entity.lock()) + if (shared_ptr selected_entity = renderer->GetCamera()->GetSelectedEntity()) { - ActionEntityDelete(entity); + ActionEntityDelete(selected_entity); } } @@ -540,9 +547,13 @@ void WorldViewer::ActionEntityDelete(const shared_ptr& entity) Entity* WorldViewer::ActionEntityCreateEmpty() { shared_ptr entity = world->CreateEntity(); - if (const shared_ptr selected_entity = EditorHelper::selected_entity.lock()) + + if (shared_ptr camera = renderer->GetCamera()) { - entity->GetTransform()->SetParent(selected_entity->GetTransform()); + if (shared_ptr selected_entity = camera->GetSelectedEntity()) + { + entity->GetTransform()->SetParent(selected_entity->GetTransform()); + } } return entity.get(); diff --git a/editor/Widgets/WorldViewer.h b/editor/Widgets/WorldViewer.h index f41981998..fbf2295d3 100644 --- a/editor/Widgets/WorldViewer.h +++ b/editor/Widgets/WorldViewer.h @@ -35,6 +35,7 @@ class WorldViewer : public Widget WorldViewer(Editor* editor); void TickVisible() override; + void SetSelectedEntity(const std::shared_ptr& entity); private: // Tree @@ -44,7 +45,6 @@ class WorldViewer : public Widget void TreeAddEntity(Spartan::Entity* entity); void HandleClicking(); void EntityHandleDragDrop(Spartan::Entity* entity_ptr) const; - void SetSelectedEntity(const std::shared_ptr& entity, bool from_editor = true); // Misc void Popups(); diff --git a/runtime/Rendering/Renderer.h b/runtime/Rendering/Renderer.h index c345926d3..f92e5e2e7 100644 --- a/runtime/Rendering/Renderer.h +++ b/runtime/Rendering/Renderer.h @@ -183,7 +183,6 @@ namespace Spartan void Pass_DebugMeshes(RHI_CommandList* cmd_list, RHI_Texture* tex_out); void Pass_Outline(RHI_CommandList* cmd_list, RHI_Texture* tex_out); void Pass_Icons(RHI_CommandList* cmd_list, RHI_Texture* tex_out); - void Pass_TransformHandle(RHI_CommandList* cmd_list, RHI_Texture* tex_out); void Pass_PeformanceMetrics(RHI_CommandList* cmd_list, RHI_Texture* tex_out); void Pass_BrdfSpecularLut(RHI_CommandList* cmd_list); void Pass_Copy(RHI_CommandList* cmd_list, RHI_Texture* tex_in, RHI_Texture* tex_out, const bool bilinear); diff --git a/runtime/Rendering/Renderer_Passes.cpp b/runtime/Rendering/Renderer_Passes.cpp index 57817291f..503dcd75b 100644 --- a/runtime/Rendering/Renderer_Passes.cpp +++ b/runtime/Rendering/Renderer_Passes.cpp @@ -19,7 +19,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -//= INCLUDES ======================================== +//= INCLUDES =================================== #include "pch.h" #include "Renderer.h" #include "Grid.h" @@ -32,7 +32,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "../World/Components/Renderable.h" #include "../World/Components/ReflectionProbe.h" #include "../World/World.h" -#include "../World/TransformHandle/TransformHandle.h" #include "../RHI/RHI_CommandList.h" #include "../RHI/RHI_Implementation.h" #include "../RHI/RHI_VertexBuffer.h" @@ -43,7 +42,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "../RHI/RHI_Shader.h" #include "../RHI/RHI_FSR2.h" #include "../RHI/RHI_StructuredBuffer.h" -//=================================================== +//============================================== //= NAMESPACES =============== using namespace std; @@ -174,7 +173,6 @@ namespace Spartan // Editor related stuff - Passes that render on top of each other Pass_DebugMeshes(cmd_list, rt_output); Pass_Outline(cmd_list, rt_output); - Pass_TransformHandle(cmd_list, rt_output); Pass_Icons(cmd_list, rt_output); Pass_PeformanceMetrics(cmd_list, rt_output); @@ -1994,102 +1992,6 @@ namespace Spartan cmd_list->EndTimeblock(); } - void Renderer::Pass_TransformHandle(RHI_CommandList* cmd_list, RHI_Texture* tex_out) - { - if (!GetOption(RendererOption::Debug_TransformHandle)) - return; - - // Acquire shaders - RHI_Shader* shader_v = shader(RendererShader::Entity_V).get(); - RHI_Shader* shader_p = shader(RendererShader::Entity_Transform_P).get(); - if (!shader_v->IsCompiled() || !shader_p->IsCompiled()) - return; - - // Get transform handle (can be null during engine startup) - shared_ptr transform_handle = m_context->GetSystem()->GetTransformHandle(); - if (!transform_handle || !transform_handle->ShouldRender()) - return; - - // The rotation transform, draws line primitives, it doesn't have a model that needs to be rendered here. - bool needs_to_render = transform_handle->GetVertexBuffer() != nullptr; - if (!needs_to_render) - return; - - cmd_list->BeginTimeblock("transform_handle"); - - // Set render state - static RHI_PipelineState pso; - pso.shader_vertex = shader_v; - pso.shader_pixel = shader_p; - pso.rasterizer_state = m_rasterizer_cull_back_solid.get(); - pso.blend_state = m_blend_alpha.get(); - pso.depth_stencil_state = m_depth_stencil_off_off.get(); - pso.render_target_color_textures[0] = tex_out; - pso.primitive_topology = RHI_PrimitiveTopology_Mode::TriangleList; - pso.viewport = tex_out->GetViewport(); - - // Axis - X - cmd_list->SetPipelineState(pso); - cmd_list->BeginRenderPass(); - { - m_cb_uber_cpu.transform = transform_handle->GetHandle()->GetTransform(Vector3::Right); - m_cb_uber_cpu.float3 = transform_handle->GetHandle()->GetColor(Vector3::Right); - Update_Cb_Uber(cmd_list); - - cmd_list->SetBufferIndex(transform_handle->GetIndexBuffer()); - cmd_list->SetBufferVertex(transform_handle->GetVertexBuffer()); - cmd_list->DrawIndexed(transform_handle->GetIndexCount()); - cmd_list->EndRenderPass(); - } - - // Axis - Y - cmd_list->SetPipelineState(pso); - cmd_list->BeginRenderPass(); - { - m_cb_uber_cpu.transform = transform_handle->GetHandle()->GetTransform(Vector3::Up); - m_cb_uber_cpu.float3 = transform_handle->GetHandle()->GetColor(Vector3::Up); - Update_Cb_Uber(cmd_list); - - cmd_list->SetBufferIndex(transform_handle->GetIndexBuffer()); - cmd_list->SetBufferVertex(transform_handle->GetVertexBuffer()); - cmd_list->DrawIndexed(transform_handle->GetIndexCount()); - cmd_list->EndRenderPass(); - } - - // Axis - Z - cmd_list->SetPipelineState(pso); - cmd_list->BeginRenderPass(); - { - m_cb_uber_cpu.transform = transform_handle->GetHandle()->GetTransform(Vector3::Forward); - m_cb_uber_cpu.float3 = transform_handle->GetHandle()->GetColor(Vector3::Forward); - Update_Cb_Uber(cmd_list); - - cmd_list->SetBufferIndex(transform_handle->GetIndexBuffer()); - cmd_list->SetBufferVertex(transform_handle->GetVertexBuffer()); - cmd_list->DrawIndexed(transform_handle->GetIndexCount()); - cmd_list->EndRenderPass(); - } - - // Axes - XYZ - if (transform_handle->DrawXYZ()) - { - cmd_list->SetPipelineState(pso); - cmd_list->BeginRenderPass(); - { - m_cb_uber_cpu.transform = transform_handle->GetHandle()->GetTransform(Vector3::One); - m_cb_uber_cpu.float3 = transform_handle->GetHandle()->GetColor(Vector3::One); - Update_Cb_Uber(cmd_list); - - cmd_list->SetBufferIndex(transform_handle->GetIndexBuffer()); - cmd_list->SetBufferVertex(transform_handle->GetVertexBuffer()); - cmd_list->DrawIndexed(transform_handle->GetIndexCount()); - cmd_list->EndRenderPass(); - } - } - - cmd_list->EndTimeblock(); - } - void Renderer::Pass_DebugMeshes(RHI_CommandList* cmd_list, RHI_Texture* tex_out) { if (!GetOption(RendererOption::Debug_ReflectionProbes)) @@ -2158,7 +2060,7 @@ namespace Spartan cmd_list->BeginTimeblock("outline"); - if (const Entity* entity = m_context->GetSystem()->GetTransformHandle()->GetSelectedEntity()) + if (shared_ptr entity = m_context->GetSystem()->GetCamera()->GetSelectedEntity()) { // Get renderable const Renderable* renderable = entity->GetRenderable(); diff --git a/runtime/Rendering/Renderer_Primitives.cpp b/runtime/Rendering/Renderer_Primitives.cpp index ee69fa882..cc747451f 100644 --- a/runtime/Rendering/Renderer_Primitives.cpp +++ b/runtime/Rendering/Renderer_Primitives.cpp @@ -19,7 +19,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -//= INCLUDES ======================================== +//= INCLUDES ============================== #include "pch.h" #include "Renderer.h" #include "../World/Components/Camera.h" @@ -28,8 +28,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "../World/Entity.h" #include "../World/World.h" #include "../World/Components/Renderable.h" -#include "../World/TransformHandle/TransformHandle.h" -//=================================================== +//========================================= //= NAMESPACES =============== using namespace std; @@ -289,46 +288,49 @@ namespace Spartan auto& lights = m_entities[RendererEntityType::Light]; for (const auto& entity : lights) { - const Entity* entity_selected = m_context->GetSystem()->GetTransformHandle()->GetSelectedEntity(); - if (entity_selected && entity_selected->GetObjectId() == entity->GetObjectId()) - { - Light* light = entity->GetComponent(); - - if (light->GetLightType() == LightType::Directional) - { - Vector3 pos_start = light->GetTransform()->GetPosition(); - Vector3 pos_end = -pos_start; - DrawLine(pos_start, pos_end); - - } - else if (light->GetLightType() == LightType::Point) - { - Vector3 center = light->GetTransform()->GetPosition(); - float radius = light->GetRange(); - uint32_t segment_count = 64; - - DrawCircle(center, Vector3::Up, radius, segment_count); - DrawCircle(center, Vector3::Right, radius, segment_count); - DrawCircle(center, Vector3::Forward, radius, segment_count); - } - else if (light->GetLightType() == LightType::Spot) + if (shared_ptr camera = m_context->GetSystem()->GetCamera()) + { + shared_ptr entity_selected = camera->GetSelectedEntity(); + if (entity_selected && entity_selected->GetObjectId() == entity->GetObjectId()) { - // tan(angle) = opposite/adjacent - // opposite = adjacent * tan(angle) - float opposite = light->GetRange() * Math::Helper::Tan(light->GetAngle()); - - Vector3 pos_end_center = light->GetTransform()->GetForward() * light->GetRange(); - Vector3 pos_end_up = pos_end_center + light->GetTransform()->GetUp() * opposite; - Vector3 pos_end_right = pos_end_center + light->GetTransform()->GetRight() * opposite; - Vector3 pos_end_down = pos_end_center + light->GetTransform()->GetDown() * opposite; - Vector3 pos_end_left = pos_end_center + light->GetTransform()->GetLeft() * opposite; - - Vector3 pos_start = light->GetTransform()->GetPosition(); - DrawLine(pos_start, pos_start + pos_end_center); - DrawLine(pos_start, pos_start + pos_end_up); - DrawLine(pos_start, pos_start + pos_end_right); - DrawLine(pos_start, pos_start + pos_end_down); - DrawLine(pos_start, pos_start + pos_end_left); + Light* light = entity->GetComponent(); + + if (light->GetLightType() == LightType::Directional) + { + Vector3 pos_start = light->GetTransform()->GetPosition(); + Vector3 pos_end = -pos_start; + DrawLine(pos_start, pos_end); + + } + else if (light->GetLightType() == LightType::Point) + { + Vector3 center = light->GetTransform()->GetPosition(); + float radius = light->GetRange(); + uint32_t segment_count = 64; + + DrawCircle(center, Vector3::Up, radius, segment_count); + DrawCircle(center, Vector3::Right, radius, segment_count); + DrawCircle(center, Vector3::Forward, radius, segment_count); + } + else if (light->GetLightType() == LightType::Spot) + { + // tan(angle) = opposite/adjacent + // opposite = adjacent * tan(angle) + float opposite = light->GetRange() * Math::Helper::Tan(light->GetAngle()); + + Vector3 pos_end_center = light->GetTransform()->GetForward() * light->GetRange(); + Vector3 pos_end_up = pos_end_center + light->GetTransform()->GetUp() * opposite; + Vector3 pos_end_right = pos_end_center + light->GetTransform()->GetRight() * opposite; + Vector3 pos_end_down = pos_end_center + light->GetTransform()->GetDown() * opposite; + Vector3 pos_end_left = pos_end_center + light->GetTransform()->GetLeft() * opposite; + + Vector3 pos_start = light->GetTransform()->GetPosition(); + DrawLine(pos_start, pos_start + pos_end_center); + DrawLine(pos_start, pos_start + pos_end_up); + DrawLine(pos_start, pos_start + pos_end_right); + DrawLine(pos_start, pos_start + pos_end_down); + DrawLine(pos_start, pos_start + pos_end_left); + } } } } diff --git a/runtime/World/Components/Camera.cpp b/runtime/World/Components/Camera.cpp index 06a88ddf1..8ebf888bb 100644 --- a/runtime/World/Components/Camera.cpp +++ b/runtime/World/Components/Camera.cpp @@ -19,7 +19,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -//= INCLUDES ================================== +//= INCLUDES ======================== #include "pch.h" #include "Camera.h" #include "Transform.h" @@ -30,8 +30,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "../../IO/FileStream.h" #include "../../Rendering/Renderer.h" #include "../../Display/Display.h" -#include "../TransformHandle/TransformHandle.h" -//============================================= +//=================================== //= NAMESPACES =============== using namespace Spartan::Math; @@ -169,11 +168,14 @@ namespace Spartan return m_frustum.IsVisible(center, extents); } - bool Camera::Pick(shared_ptr& picked) + void Camera::Pick() { // Ensure the mouse is inside the viewport if (!m_input->GetMouseIsInViewport()) - return false; + { + m_selected_entity = nullptr; + return; + } // Create mouse ray Vector3 ray_start = GetTransform()->GetPosition(); @@ -214,13 +216,16 @@ namespace Spartan // Check if there are any hits if (hits.empty()) - return false; + { + m_selected_entity = nullptr; + return; + } // If there is a single hit, return that if (hits.size() == 1) { - picked = hits.front().m_entity; - return true; + m_selected_entity = hits.front().m_entity; + return; } // If there are more hits, perform triangle intersection @@ -252,13 +257,11 @@ namespace Spartan if (distance < distance_min) { - picked = hit.m_entity; - distance_min = distance; + m_selected_entity = hit.m_entity; + distance_min = distance; } } } - - return picked != nullptr; } Vector2 Camera::WorldToScreenCoordinates(const Vector3& position_world) const @@ -328,6 +331,7 @@ namespace Spartan // FPS camera controls. // X-axis movement: W, A, S, D. // Y-axis movement: Q, E. + // Mouse look: Hold right click to enable. if (m_first_person_control_enabled) { ProcessInputFpsControl(delta_time); @@ -500,7 +504,7 @@ namespace Spartan // Set focused entity as a lerp target if (m_input->GetKeyDown(KeyCode::F)) { - if (Entity* entity = m_context->GetSystem()->GetTransformHandle()->GetSelectedEntity()) + if (shared_ptr entity = m_context->GetSystem()->GetCamera()->GetSelectedEntity()) { SP_LOG_INFO("Focusing on entity \"%s\"...", entity->GetTransform()->GetEntity()->GetName().c_str()); @@ -563,7 +567,7 @@ namespace Spartan bool Camera::IsControledInFirstPerson() const { - return m_is_controlled_by_keyboard_mouse || m_input->IsControllerConnected(); + return m_is_controlled_by_keyboard_mouse; } Matrix Camera::ComputeViewMatrix() const diff --git a/runtime/World/Components/Camera.h b/runtime/World/Components/Camera.h index a4ce60d2f..66cff0004 100644 --- a/runtime/World/Components/Camera.h +++ b/runtime/World/Components/Camera.h @@ -78,7 +78,7 @@ namespace Spartan const Math::Ray& GetPickingRay() const { return m_ray; } // Picks the nearest entity under the mouse cursor - bool Pick(std::shared_ptr& entity); + void Pick(); // Converts a world point to a screen point Math::Vector2 WorldToScreenCoordinates(const Math::Vector3& position_world) const; @@ -139,8 +139,9 @@ namespace Spartan bool IsControledInFirstPerson() const; // Misc - void MakeDirty() { m_is_dirty = true; } - + void MakeDirty() { m_is_dirty = true; } + void SetSelectedEntity(std::shared_ptr entity) { m_selected_entity = entity; } + std::shared_ptr GetSelectedEntity() { return m_selected_entity; } Math::Matrix ComputeViewMatrix() const; Math::Matrix ComputeProjection(const bool reverse_z, const float near_plane = 0.0f, const float far_plane = 0.0f); @@ -189,6 +190,7 @@ namespace Spartan Math::Ray m_ray; Math::Frustum m_frustum; std::vector m_bookmarks; + std::shared_ptr m_selected_entity = nullptr; // Dependencies Renderer* m_renderer = nullptr; diff --git a/runtime/World/TransformHandle/TransformEnums.h b/runtime/World/TransformHandle/TransformEnums.h deleted file mode 100644 index a2e389d5d..000000000 --- a/runtime/World/TransformHandle/TransformEnums.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright(c) 2016-2022 Panos Karabelas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#pragma once - -enum class TransformHandleType -{ - Unknown, - Position, - Rotation, - Scale -}; - -enum class TransformHandleSpace -{ - Local, - World -}; diff --git a/runtime/World/TransformHandle/TransformHandle.cpp b/runtime/World/TransformHandle/TransformHandle.cpp deleted file mode 100644 index 4857cc46a..000000000 --- a/runtime/World/TransformHandle/TransformHandle.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/* -Copyright(c) 2016-2022 Panos Karabelas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -//= INCLUDES ================================ -#include "pch.h" -#include "TransformHandle.h" -#include "../../RHI/RHI_IndexBuffer.h" -#include "../../Input/Input.h" -#include "../../World/World.h" -#include "../../World/Entity.h" -#include "../../World/Components/Camera.h" -#include "../../World/Components/Transform.h" -#include "TransformPosition.h" -#include "TransformScale.h" -#include "TransformRotation.h" -#include "../Components/Environment.h" -//=========================================== - -//= NAMESPACES =============== -using namespace std; -using namespace Spartan::Math; -//============================ - -namespace Spartan -{ - TransformHandle::TransformHandle(Context* context) - { - m_context = context; - m_input = context->GetSystem(); - m_world = context->GetSystem(); - m_type = TransformHandleType::Position; - m_space = TransformHandleSpace::World; - m_is_editing = false; - - m_transform_operator[TransformHandleType::Position] = make_shared(context); - m_transform_operator[TransformHandleType::Rotation] = make_shared(context); - m_transform_operator[TransformHandleType::Scale] = make_shared(context); - } - - bool TransformHandle::Tick(Camera* camera, const float handle_size) - { - shared_ptr selected_entity = m_entity_selected.lock(); - - // If there isn't a camera or an entity, ignore input - if (!camera || !selected_entity) - { - m_is_editing = false; - return false; - } - - // If the selected entity is the camera itself, ignore input - if (selected_entity->GetObjectId() == camera->GetTransform()->GetEntity()->GetObjectId()) - { - m_is_editing = false; - return false; - } - - // Switch between position, rotation and scale handles, with W, E and R respectively - if (!camera->IsControledInFirstPerson()) - { - if (m_input->GetKeyDown(KeyCode::W)) - { - m_type = TransformHandleType::Position; - } - else if (m_input->GetKeyDown(KeyCode::E)) - { - m_type = TransformHandleType::Rotation; - } - else if (m_input->GetKeyDown(KeyCode::R)) - { - m_type = TransformHandleType::Scale; - } - } - - // Update operators - m_transform_operator[m_type]->Tick(m_space, selected_entity.get(), camera, handle_size); - - m_is_editing = m_transform_operator[m_type]->IsEditing(); - - return true; - } - - weak_ptr TransformHandle::SetSelectedEntity(const shared_ptr& entity) - { - // Set a new entity only if another is not being edited - if (!m_is_editing) - { - // If in front of the entity the handles from the previous entity - // are actual being hovered, then a selection not selected the new entity. - if (!m_transform_operator[m_type]->IsHovered()) - { - m_entity_selected = entity; - } - } - - return m_entity_selected; - } - - uint32_t TransformHandle::GetIndexCount() - { - return m_transform_operator[m_type]->GetIndexBuffer()->GetIndexCount(); - } - - const RHI_VertexBuffer* TransformHandle::GetVertexBuffer() - { - return m_transform_operator[m_type]->GetVertexBuffer(); - } - - const RHI_IndexBuffer* TransformHandle::GetIndexBuffer() - { - return m_transform_operator[m_type]->GetIndexBuffer(); - } - - const TransformOperator* TransformHandle::GetHandle() - { - return m_transform_operator[m_type].get(); - } - - bool TransformHandle::ShouldRender() const - { - shared_ptr entity = m_entity_selected.lock(); - - if (!entity) - return false; - - if (entity->GetComponent()) - return false; - - return true; - } - -} diff --git a/runtime/World/TransformHandle/TransformHandle.h b/runtime/World/TransformHandle/TransformHandle.h deleted file mode 100644 index 8b10bdeb4..000000000 --- a/runtime/World/TransformHandle/TransformHandle.h +++ /dev/null @@ -1,72 +0,0 @@ -/* -Copyright(c) 2016-2022 Panos Karabelas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#pragma once - -//= INCLUDES ====================== -#include -#include "TransformOperator.h" -#include "../../Core/Definitions.h" -#include -//================================= - -namespace Spartan -{ - class World; - class Input; - class Camera; - class Context; - class Entity; - class RHI_IndexBuffer; - class RHI_VertexBuffer; - - class SP_CLASS TransformHandle - { - public: - TransformHandle(Context* context); - ~TransformHandle() = default; - - // Ticks the gizmo and returns true if it needs to be queried for rendering - bool Tick(Camera* camera, float handle_size); - - std::weak_ptr SetSelectedEntity(const std::shared_ptr& entity); - uint32_t GetIndexCount(); - const RHI_VertexBuffer* GetVertexBuffer(); - const RHI_IndexBuffer* GetIndexBuffer(); - const TransformOperator* GetHandle(); - bool DrawXYZ() const { return m_type == TransformHandleType::Scale; } - bool IsEditing() const { return m_is_editing; } - Entity* GetSelectedEntity() const { return m_entity_selected.lock().get(); } - bool ShouldRender() const; - - private: - bool m_needs_to_render = false; - bool m_is_editing = false; - - std::weak_ptr m_entity_selected; - std::unordered_map> m_transform_operator; - TransformHandleType m_type = TransformHandleType::Unknown; - TransformHandleSpace m_space; - Context* m_context = nullptr; - Input* m_input = nullptr; - World* m_world = nullptr; - }; -} diff --git a/runtime/World/TransformHandle/TransformOperator.cpp b/runtime/World/TransformHandle/TransformOperator.cpp deleted file mode 100644 index bdb07bd9e..000000000 --- a/runtime/World/TransformHandle/TransformOperator.cpp +++ /dev/null @@ -1,237 +0,0 @@ -/* -Copyright(c) 2016-2022 Panos Karabelas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -//= INCLUDES ================================= -#include "pch.h" -#include "TransformOperator.h" -#include "../../Input/Input.h" -#include "../../World/Entity.h" -#include "../../World/Components/Camera.h" -#include "../../World/Components/Transform.h" -#include "../../World/Components/Renderable.h" -#include "../../Rendering/Mesh.h" -#include "../../Rendering/Renderer.h" -//============================================ - -//= NAMESPACES =============== -using namespace std; -using namespace Spartan::Math; -//============================ - -namespace Spartan -{ - TransformOperator::TransformOperator(Context* context, const TransformHandleType transform_handle_type) - { - m_context = context; - m_type = transform_handle_type; - m_renderer = context->GetSystem(); - m_input = context->GetSystem(); - } - - void TransformOperator::Tick(const TransformHandleSpace space, Entity* entity, Camera* camera, const float handle_size) - { - SP_ASSERT(entity != nullptr); - SP_ASSERT(camera != nullptr); - - // Reflect entity transform - SnapToTransform(space, entity, camera, handle_size); - - // Allow editing only when the camera is not fps controlled - if (!camera->IsControledInFirstPerson()) - { - // Create ray starting from the camera position and pointing towards where the mouse is pointing - Vector3 ray_start = camera->GetTransform()->GetPosition(); - Vector3 ray_direction = camera->ScreenToWorldCoordinates(m_input->GetMousePositionRelativeToEditorViewport(), 1.0f); - Ray mouse_ray = Ray(ray_start, ray_direction); - - // Check if the mouse ray hits and of the handle axes - InteresectionTest(mouse_ray); - - // Update handle states - { - // Mark a handle as hovered, only if it's the only hovered handle (during the previous frame). - m_handle_x.m_is_hovered = m_handle_x_intersected && !(m_handle_y.m_is_hovered || m_handle_z.m_is_hovered); - m_handle_y.m_is_hovered = m_handle_y_intersected && !(m_handle_x.m_is_hovered || m_handle_z.m_is_hovered); - m_handle_z.m_is_hovered = m_handle_z_intersected && !(m_handle_x.m_is_hovered || m_handle_y.m_is_hovered); - m_handle_xyz.m_is_hovered = m_handle_xyz_intersected && !(m_handle_x.m_is_hovered || m_handle_y.m_is_hovered || m_handle_z.m_is_hovered); - - // Disable handle if one of the others is active (disabling turns the color of the handle to grey). - m_handle_x.m_is_disabled = !m_handle_x.m_is_editing && (m_handle_y.m_is_editing || m_handle_z.m_is_editing || m_handle_xyz.m_is_editing); - m_handle_y.m_is_disabled = !m_handle_y.m_is_editing && (m_handle_x.m_is_editing || m_handle_z.m_is_editing || m_handle_xyz.m_is_editing); - m_handle_z.m_is_disabled = !m_handle_z.m_is_editing && (m_handle_x.m_is_editing || m_handle_y.m_is_editing || m_handle_xyz.m_is_editing); - m_handle_xyz.m_is_disabled = !m_handle_xyz.m_is_editing && (m_handle_x.m_is_editing || m_handle_y.m_is_editing || m_handle_z.m_is_editing); - - // Keep old editing state - m_handle_x.m_is_editing_previous = m_handle_x.m_is_editing; - m_handle_y.m_is_editing_previous = m_handle_y.m_is_editing; - m_handle_z.m_is_editing_previous = m_handle_z.m_is_editing; - m_handle_xyz.m_is_editing_previous = m_handle_xyz.m_is_editing; - - // Detect if any of the handles should enter an editing state (on right click pressed) - bool mouse_down = m_input->GetKeyDown(KeyCode::Click_Left); - m_handle_x.m_is_editing = (m_handle_x.m_is_hovered && mouse_down) ? true : m_handle_x.m_is_editing; - m_handle_y.m_is_editing = (m_handle_y.m_is_hovered && mouse_down) ? true : m_handle_y.m_is_editing; - m_handle_z.m_is_editing = (m_handle_z.m_is_hovered && mouse_down) ? true : m_handle_z.m_is_editing; - m_handle_xyz.m_is_editing = (m_handle_xyz.m_is_hovered && mouse_down) ? true : m_handle_xyz.m_is_editing; - - // Detect if any of the handles should exit the editing state (on left click released). - bool mouse_up = m_input->GetKeyUp(KeyCode::Click_Left); - m_handle_x.m_is_editing = (m_handle_x.m_is_editing && mouse_up) ? false : m_handle_x.m_is_editing; - m_handle_y.m_is_editing = (m_handle_y.m_is_editing && mouse_up) ? false : m_handle_y.m_is_editing; - m_handle_z.m_is_editing = (m_handle_z.m_is_editing && mouse_up) ? false : m_handle_z.m_is_editing; - m_handle_xyz.m_is_editing = (m_handle_xyz.m_is_editing && mouse_up) ? false : m_handle_xyz.m_is_editing; - - // Determine if this is the first editing run - m_handle_x.m_is_first_editing_run = !m_handle_x.m_is_editing_previous && m_handle_x.m_is_editing; - m_handle_y.m_is_first_editing_run = !m_handle_y.m_is_editing_previous && m_handle_y.m_is_editing; - m_handle_z.m_is_first_editing_run = !m_handle_z.m_is_editing_previous && m_handle_z.m_is_editing; - m_handle_xyz.m_is_first_editing_run = !m_handle_xyz.m_is_editing_previous && m_handle_xyz.m_is_editing; - } - - // Compute mouse delta - if (m_handle_x.m_is_editing || m_handle_y.m_is_editing || m_handle_z.m_is_editing || m_handle_xyz.m_is_editing) - { - ComputeDelta(mouse_ray, camera); - - // Map computed delta to the entity's transform - MapToTransform(entity->GetTransform(), space); - } - } - - // Allow the handles to draw any primitives - Vector3 center = m_handle_xyz.m_position; - m_handle_x.DrawPrimitives(center); - m_handle_y.DrawPrimitives(center); - m_handle_z.DrawPrimitives(center); - m_handle_xyz.DrawPrimitives(center); - } - - void TransformOperator::SnapToTransform(const TransformHandleSpace space, Entity* entity, Camera* camera, const float handle_size) - { - // Get entity's components - Transform* entity_transform = entity->GetTransform(); // Transform alone is not enough - Renderable* entity_renderable = entity->GetComponent(); // Bounding box is also needed as some meshes are not defined around P(0,0,0) - - // Acquire entity's transformation data (local or world space) - const Vector3& center = entity_renderable ? entity_renderable->GetAabb().GetCenter() : entity_transform->GetPositionLocal(); - const Quaternion& entity_rotation = (space == TransformHandleSpace::World) ? entity_transform->GetRotation() : entity_transform->GetRotationLocal(); - const Vector3& right = (space == TransformHandleSpace::World) ? Vector3::Right : entity_rotation * Vector3::Right; - const Vector3& up = (space == TransformHandleSpace::World) ? Vector3::Up : entity_rotation * Vector3::Up; - const Vector3& forward = (space == TransformHandleSpace::World) ? Vector3::Forward : entity_rotation * Vector3::Forward; - - // Compute scale - const float distance_to_camera = camera ? (camera->GetTransform()->GetPosition() - (center)).Length() : 0.0f; - const float handle_scale = distance_to_camera / (1.0f / handle_size); - m_offset_handle_from_center = distance_to_camera / (1.0f / 0.1f); - - // Set position for each axis handle - m_handle_x.m_position = center; - m_handle_y.m_position = center; - m_handle_z.m_position = center; - m_handle_xyz.m_position = center; - if (m_offset_handle_axes_from_center) - { - m_handle_x.m_position += right * m_offset_handle_from_center; - m_handle_y.m_position += up * m_offset_handle_from_center; - m_handle_z.m_position += forward * m_offset_handle_from_center; - } - - // Set rotation for each axis handle - m_handle_x.m_rotation = Quaternion::FromEulerAngles(0.0f, 0.0f, -90.0f); - m_handle_y.m_rotation = Quaternion::FromEulerAngles(0.0f, 90.0f, 0.0f); - m_handle_z.m_rotation = Quaternion::FromEulerAngles(90.0f, 0.0f, 0.0f); - - // Set scale for each axis handle - m_handle_x.m_scale = handle_scale; - m_handle_y.m_scale = handle_scale; - m_handle_z.m_scale = handle_scale; - m_handle_xyz.m_scale = handle_scale; - - // Update transforms - m_handle_x.UpdateTransform(); - m_handle_y.UpdateTransform(); - m_handle_z.UpdateTransform(); - m_handle_xyz.UpdateTransform(); - } - - const Matrix& TransformOperator::GetTransform(const Vector3& axis) const - { - if (axis == Vector3::Right) - return m_handle_x.m_transform; - - if (axis == Vector3::Up) - return m_handle_y.m_transform; - - if (axis == Vector3::Forward) - return m_handle_z.m_transform; - - return m_handle_xyz.m_transform; - } - - const Vector3& TransformOperator::GetColor(const Vector3& axis) const - { - if (axis == Vector3::Right) - return m_handle_x.GetColor(); - - if (axis == Vector3::Up) - return m_handle_y.GetColor(); - - if (axis == Vector3::Forward) - return m_handle_z.GetColor(); - - return m_handle_xyz.GetColor(); - } - - const RHI_VertexBuffer* TransformOperator::GetVertexBuffer() - { - return m_axis_mesh? m_axis_mesh->GetVertexBuffer() : nullptr; - } - - const RHI_IndexBuffer* TransformOperator::GetIndexBuffer() - { - return m_axis_mesh ? m_axis_mesh->GetIndexBuffer() : nullptr; - } - - bool TransformOperator::IsEditing() const - { - SP_ASSERT(m_handle_x.m_type != TransformHandleType::Unknown); - - return m_handle_x.m_is_editing || m_handle_y.m_is_editing || m_handle_y.m_is_editing || m_handle_xyz.m_is_editing; - } - - bool TransformOperator::IsHovered() const - { - if (m_handle_x.m_type != TransformHandleType::Unknown && m_handle_x.m_is_hovered) - return true; - - if (m_handle_y.m_type != TransformHandleType::Unknown && m_handle_y.m_is_hovered) - return true; - - if (m_handle_z.m_type != TransformHandleType::Unknown && m_handle_z.m_is_hovered) - return true; - - if (m_handle_xyz.m_type != TransformHandleType::Unknown && m_handle_xyz.m_is_hovered) - return true; - - return false; - } - -} diff --git a/runtime/World/TransformHandle/TransformOperator.h b/runtime/World/TransformHandle/TransformOperator.h deleted file mode 100644 index fb60a291c..000000000 --- a/runtime/World/TransformHandle/TransformOperator.h +++ /dev/null @@ -1,88 +0,0 @@ -/* -Copyright(c) 2016-2022 Panos Karabelas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#pragma once - -//= INCLUDES ===================== -#include "TransformEnums.h" -#include "TransformOperatorAxis.h" -#include -#include "../../Rendering/Mesh.h" -//================================ - -namespace Spartan -{ - //= FWD DECLARATIONS == - class Renderer; - class Context; - class RHI_VertexBuffer; - class RHI_IndexBuffer; - class Entity; - class Input; - class Camera; - //===================== - - class SP_CLASS TransformOperator - { - public: - TransformOperator(Context* context, const TransformHandleType transform_handle_type); - ~TransformOperator() = default; - - void Tick(TransformHandleSpace space, Entity* entity, Camera* camera, float handle_size); - const Math::Matrix& GetTransform(const Math::Vector3& axis) const; - const Math::Vector3& GetColor(const Math::Vector3& axis) const; - const RHI_VertexBuffer* GetVertexBuffer(); - const RHI_IndexBuffer* GetIndexBuffer(); - bool MashMesh() const { return m_axis_mesh != nullptr; } - bool IsEditing() const; - bool IsHovered() const; - - private: - void SnapToTransform(TransformHandleSpace space, Entity* entity, Camera* camera, float handle_size); - - protected: - // Test if the mouse ray intersects any of the handles. - virtual void InteresectionTest(const Math::Ray& mouse_ray) = 0; - // Compute transformation (position, rotation or scale) delta. - virtual void ComputeDelta(const Math::Ray& mouse_ray, const Camera* camera) = 0; - // Map the transformation delta to the entity's transform. - virtual void MapToTransform(Transform* transform, const TransformHandleSpace space) = 0; - - TransformOperatorAxis m_handle_x; - TransformOperatorAxis m_handle_y; - TransformOperatorAxis m_handle_z; - TransformOperatorAxis m_handle_xyz; - bool m_handle_x_intersected = false; - bool m_handle_y_intersected = false; - bool m_handle_z_intersected = false; - bool m_handle_xyz_intersected = false; - TransformHandleType m_type = TransformHandleType::Unknown; - bool m_offset_handle_axes_from_center = true; - float m_offset_handle_from_center = 0.0f; - Math::Vector3 m_position = Math::Vector3::Zero; - Math::Vector3 m_rotation = Math::Vector3::Zero; - Math::Vector3 m_scale = Math::Vector3::Zero; - Context* m_context = nullptr; - Renderer* m_renderer = nullptr; - Input* m_input = nullptr; - std::unique_ptr m_axis_mesh; - }; -} diff --git a/runtime/World/TransformHandle/TransformOperatorAxis.cpp b/runtime/World/TransformHandle/TransformOperatorAxis.cpp deleted file mode 100644 index 6faa5eac5..000000000 --- a/runtime/World/TransformHandle/TransformOperatorAxis.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright(c) 2016-2022 Panos Karabelas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -//= INCLUDES ================================= -#include "pch.h" -#include "TransformOperatorAxis.h" -#include "../../Input/Input.h" -#include "../../World/Components/Transform.h" -#include "../../Rendering/Renderer.h" -//============================================ - -//= NAMESPACES =============== -using namespace std; -using namespace Spartan::Math; -//============================ - -namespace Spartan -{ - TransformOperatorAxis::TransformOperatorAxis(TransformHandleType type, const Math::Vector3& axis, Context* context) - { - m_axis = axis; - m_type = type; - m_context = context; - m_renderer = context->GetSystem(); - m_input = context->GetSystem(); - } - - void TransformOperatorAxis::UpdateTransform() - { - if (m_type == TransformHandleType::Unknown) - return; - - m_transform = Math::Matrix(m_position, m_rotation, m_scale); - m_box_transformed = m_box.Transform(m_transform); - } - - void TransformOperatorAxis::DrawPrimitives(const Vector3& transform_center) const - { - if (m_type == TransformHandleType::Unknown) - return; - - // Draw axis circle - if (m_type == TransformHandleType::Rotation) - { - const Vector3 center = m_box_transformed.GetCenter(); - const float radius = m_scale.Length() * 5.0f; - const uint32_t segment_count = 64; - const Vector4 color = Vector4(GetColor(), 1.0f); - m_renderer->DrawCircle(center, m_axis, radius, segment_count, color, 0.0f, false); - } - // Draw axis line (connect the handle with the origin of the transform) - else - { - const Vector4 color = Vector4(GetColor(), 1.0f); - const Vector3 from = m_box_transformed.GetCenter(); - const Vector3& to = transform_center; - m_renderer->DrawLine(from, to, color, color, 0.0f, false); - } - } - - const Spartan::Math::Vector3& TransformOperatorAxis::GetColor() const - { - if (m_is_disabled) - return m_color_disabled; - - if (m_is_hovered || m_is_editing) - return m_color_active; - - return m_axis; - } -} diff --git a/runtime/World/TransformHandle/TransformOperatorAxis.h b/runtime/World/TransformHandle/TransformOperatorAxis.h deleted file mode 100644 index e8f76e9b4..000000000 --- a/runtime/World/TransformHandle/TransformOperatorAxis.h +++ /dev/null @@ -1,66 +0,0 @@ -/* -Copyright(c) 2016-2022 Panos Karabelas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#pragma once - -//= INCLUDES ====================== -#include "../../Math/Matrix.h" -#include "../../Math/BoundingBox.h" -#include "TransformEnums.h" -//================================= - -namespace Spartan -{ - class Renderer; - class Context; - class Input; - class Transform; - - struct TransformOperatorAxis - { - TransformOperatorAxis() = default; - TransformOperatorAxis(TransformHandleType type, const Math::Vector3& axis, Context* context); - - void UpdateTransform(); - void DrawPrimitives(const Math::Vector3& transform_center) const; - const Math::Vector3& GetColor() const; - - Math::Vector3 m_axis = Math::Vector3::One; - Math::Matrix m_transform = Math::Matrix::Identity; - Math::Vector3 m_position = Math::Vector3::One; - Math::Quaternion m_rotation = Math::Quaternion::Identity; - Math::Vector3 m_scale = Math::Vector3::One; - Math::BoundingBox m_box = Math::BoundingBox::Zero; - Math::BoundingBox m_box_transformed = Math::BoundingBox::Zero; - bool m_is_editing = false; - bool m_is_hovered = false; - bool m_is_disabled = false; - bool m_is_editing_previous = false; - bool m_is_first_editing_run = false; - Math::Vector3 m_color_active = Math::Vector3(1.0f, 1.0f, 0.0f); - Math::Vector3 m_color_disabled = Math::Vector3(0.5f, 0.5f, 0.5f); - TransformHandleType m_type = TransformHandleType::Unknown; - - Context* m_context = nullptr; - Renderer* m_renderer = nullptr; - Input* m_input = nullptr; - }; -} diff --git a/runtime/World/TransformHandle/TransformPosition.cpp b/runtime/World/TransformHandle/TransformPosition.cpp deleted file mode 100644 index c848d0e2d..000000000 --- a/runtime/World/TransformHandle/TransformPosition.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* -Copyright(c) 2016-2022 Panos Karabelas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -//= INCLUDES ======================= -#include "pch.h" -#include "TransformPosition.h" -#include "../Components/Camera.h" -#include "../Components/Transform.h" -#include "../Rendering/Geometry.h" -#include "../Rendering/Mesh.h" -//================================== - -//= NAMESPACES =============== -using namespace std; -using namespace Spartan::Math; -//============================ - -namespace Spartan -{ - TransformPosition::TransformPosition(Context* context) : TransformOperator(context, TransformHandleType::Position) - { - // Create model - vector vertices; - vector indices; - - Geometry::CreateCone(&vertices, &indices); - - m_axis_mesh = make_unique(m_context); - m_axis_mesh->AddIndices(indices); - m_axis_mesh->AddVertices(vertices); - m_axis_mesh->CreateGpuBuffers(); - m_axis_mesh->ComputeAabb(); - - // Create an axis for each axis of control and fourth axis which control all of them - m_handle_x = TransformOperatorAxis(m_type, Vector3::Right, m_context); - m_handle_y = TransformOperatorAxis(m_type, Vector3::Up, m_context); - m_handle_z = TransformOperatorAxis(m_type, Vector3::Forward, m_context); - - // Create bounding boxes for the handles, based on the vertices used - m_handle_x.m_box = BoundingBox(vertices.data(), static_cast(vertices.size())); - m_handle_y.m_box = m_handle_x.m_box; - m_handle_z.m_box = m_handle_x.m_box; - - m_offset_handle_axes_from_center = true; - } - - void TransformPosition::InteresectionTest(const Math::Ray& mouse_ray) - { - // Test if the ray intersects any of the handles - m_handle_x_intersected = mouse_ray.HitDistance(m_handle_x.m_box_transformed) != Math::Helper::INFINITY_; - m_handle_y_intersected = mouse_ray.HitDistance(m_handle_y.m_box_transformed) != Math::Helper::INFINITY_; - m_handle_z_intersected = mouse_ray.HitDistance(m_handle_z.m_box_transformed) != Math::Helper::INFINITY_; - } - - static Vector3 GetIntersectionPointBetweenRayAndCameraPlane(const Camera* camera, const Ray& ray) - { - Vector3 normal = camera->GetTransform()->GetForward(); - float distance_from_origin = 0.0f; - Plane screen_plane = Plane(normal, distance_from_origin); - - // Find the intersection point between the plane and the ray. - Vector3 plane_interesection_point = Vector3::Zero; - ray.HitDistance(screen_plane, &plane_interesection_point); - - return plane_interesection_point; - } - - static Vector3 GetMousePointOnAxis(const Camera* camera, const Ray& mouse_ray, const TransformOperatorAxis& axis_handle) - { - // Find the intersection point between the camera plane and the mouse ray. - Vector3 normal = camera->GetTransform()->GetForward(); - float distance_from_origin = 0.0f; - Plane screen_plane = Plane(normal, distance_from_origin); - Vector3 plane_interesection_point = Vector3::Zero; - mouse_ray.HitDistance(screen_plane, &plane_interesection_point); - - // Find the point on the z-axis which is the closest to the ray-plane intersection point. - Vector3 closest_point = Vector3::Zero; - Ray(Vector3::Zero, axis_handle.m_axis).Distance(plane_interesection_point, closest_point); - - return closest_point; - } - - void TransformPosition::ComputeDelta(const Math::Ray& mouse_ray, const Camera* camera) - { - Vector3 mouse_point_on_axis = Vector3::Zero; - if (m_handle_x.m_is_editing) - { - // Find the closest point to the world space mouse cursor, on a given world axis. - mouse_point_on_axis = GetMousePointOnAxis(camera, mouse_ray, m_handle_x); - } - else if (m_handle_y.m_is_editing) - { - mouse_point_on_axis = GetMousePointOnAxis(camera, mouse_ray, m_handle_y); - } - else if (m_handle_z.m_is_editing) - { - mouse_point_on_axis = GetMousePointOnAxis(camera, mouse_ray, m_handle_z); - } - - bool is_first_editing_run = m_handle_x.m_is_first_editing_run || m_handle_y.m_is_first_editing_run || m_handle_z.m_is_first_editing_run; - m_delta = !is_first_editing_run ? (mouse_point_on_axis - m_previous_mouse_point_on_axis) : Vector3::Zero; - m_previous_mouse_point_on_axis = mouse_point_on_axis; - } - - void TransformPosition::MapToTransform(Transform* transform, const TransformHandleSpace space) - { - SP_ASSERT(transform != nullptr); - - if (space == TransformHandleSpace::World) - { - transform->SetPosition(transform->GetPosition() + m_delta); - } - else - { - transform->SetPositionLocal(transform->GetPositionLocal() + m_delta); - } - } -} diff --git a/runtime/World/TransformHandle/TransformPosition.h b/runtime/World/TransformHandle/TransformPosition.h deleted file mode 100644 index 546bb1d06..000000000 --- a/runtime/World/TransformHandle/TransformPosition.h +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright(c) 2016-2022 Panos Karabelas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#pragma once - -//= INCLUDES =============== -#include "TransformOperator.h" -//========================== - -namespace Spartan -{ - class SP_CLASS TransformPosition : public TransformOperator - { - public: - TransformPosition(Context* context = nullptr); - ~TransformPosition() = default; - - protected: - void InteresectionTest(const Math::Ray& mouse_ray) override; - void ComputeDelta(const Math::Ray& mouse_ray, const Camera* camera) override; - void MapToTransform(Transform* transform, const TransformHandleSpace space) override; - - private: - Math::Vector3 m_previous_mouse_point_on_axis = Math::Vector3::Zero; - Math::Vector3 m_delta = Math::Vector3::Zero; - }; -} diff --git a/runtime/World/TransformHandle/TransformRotation.cpp b/runtime/World/TransformHandle/TransformRotation.cpp deleted file mode 100644 index 530b54176..000000000 --- a/runtime/World/TransformHandle/TransformRotation.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright(c) 2016-2022 Panos Karabelas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -//= INCLUDES ======================== -#include "pch.h" -#include "TransformRotation.h" -#include "../../Input/Input.h" -#include "../Components/Transform.h" -//=================================== - -//= NAMESPACES =============== -using namespace std; -using namespace Spartan::Math; -//============================ - -namespace Spartan -{ - TransformRotation::TransformRotation(Context* context) : TransformOperator(context, TransformHandleType::Rotation) - { - // Create an axis for each axis of control and fourth axis which control all of them - m_handle_x = TransformOperatorAxis(m_type, Vector3::Right, m_context); - m_handle_y = TransformOperatorAxis(m_type, Vector3::Up, m_context); - m_handle_z = TransformOperatorAxis(m_type, Vector3::Forward, m_context); - - m_offset_handle_axes_from_center = false; - } - - void TransformRotation::InteresectionTest(const Math::Ray& mouse_ray) - { - const float circle_radius = m_handle_x.m_scale.Length() * 5.0f; - const float circle_thickness = 0.05f; - - // Construct 3 planes, 1 for each axis - const Plane plane_x = Plane(m_handle_x.m_axis, 0.0f); - const Plane plane_y = Plane(m_handle_y.m_axis, 0.0f); - const Plane plane_z = Plane(m_handle_z.m_axis, 0.0f); - - // Find the intersection point on those planes - Vector3 plane_x_intersection_point = Vector3::Infinity; mouse_ray.HitDistance(plane_x, &plane_x_intersection_point); - Vector3 plane_y_intersection_point = Vector3::Infinity; mouse_ray.HitDistance(plane_y, &plane_y_intersection_point); - Vector3 plane_z_intersection_point = Vector3::Infinity; mouse_ray.HitDistance(plane_z, &plane_z_intersection_point); - - // Compute the distance of the intersection point form the plane's center - const float handle_x_distance = plane_x_intersection_point.Distance(m_handle_x.m_position); - const float handle_y_distance = plane_y_intersection_point.Distance(m_handle_y.m_position); - const float handle_z_distance = plane_z_intersection_point.Distance(m_handle_z.m_position); - - // Test if the ray intersects any of the handles - m_handle_x_intersected = handle_x_distance >= (circle_radius - circle_thickness) && handle_x_distance < (circle_radius + circle_thickness); - m_handle_y_intersected = handle_y_distance >= (circle_radius - circle_thickness) && handle_y_distance < (circle_radius + circle_thickness); - m_handle_z_intersected = handle_z_distance >= (circle_radius - circle_thickness) && handle_z_distance < (circle_radius + circle_thickness); - - // Capture initial intersection point and axis (to be able to calculate future deltas) - if (m_input->GetKeyDown(KeyCode::Click_Left)) - { - if (m_handle_x_intersected) - { - m_initial_direction = (plane_x_intersection_point - m_position).Normalized(); - m_intersection_axis = m_handle_x.m_axis; - } - else if (m_handle_y_intersected) - { - m_initial_direction = (plane_y_intersection_point - m_position).Normalized(); - m_intersection_axis = m_handle_y.m_axis; - } - else if (m_handle_z_intersected) - { - m_initial_direction = (plane_z_intersection_point - m_position).Normalized(); - m_intersection_axis = m_handle_z.m_axis; - } - } - } - - void TransformRotation::ComputeDelta(const Math::Ray& mouse_ray, const Camera* camera) - { - const Plane plane = Plane(m_intersection_axis, 0.0f); - Vector3 plane_intersection_point = Vector3::Infinity; - mouse_ray.HitDistance(plane, &plane_intersection_point); - - Vector3 dir = (plane_intersection_point - m_position).Normalized(); - float angle = Vector3::Dot(dir, m_initial_direction); - - bool is_first_editing_run = m_handle_x.m_is_first_editing_run || m_handle_y.m_is_first_editing_run || m_handle_z.m_is_first_editing_run; - m_angle_delta = !is_first_editing_run ? (angle - m_previous_angle) : 0.0f; - m_previous_angle = angle; - } - - void TransformRotation::MapToTransform(Transform* transform, const TransformHandleSpace space) - { - SP_ASSERT(transform != nullptr); - - Quaternion rotation_delta = Quaternion::FromEulerAngles(Vector3(m_angle_delta * Math::Helper::RAD_TO_DEG) * m_intersection_axis); - - if (space == TransformHandleSpace::World) - { - Quaternion rotation_new = transform->GetRotation() * rotation_delta; - transform->SetRotation(rotation_new); - } - else - { - Quaternion rotation_new = transform->GetRotationLocal() * rotation_delta; - transform->SetRotationLocal(rotation_new); - } - } -} diff --git a/runtime/World/TransformHandle/TransformRotation.h b/runtime/World/TransformHandle/TransformRotation.h deleted file mode 100644 index a7adfe218..000000000 --- a/runtime/World/TransformHandle/TransformRotation.h +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright(c) 2016-2022 Panos Karabelas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#pragma once - -//= INCLUDES ================= -#include "TransformOperator.h" -//============================ - -namespace Spartan -{ - class SP_CLASS TransformRotation : public TransformOperator - { - public: - TransformRotation(Context* context = nullptr); - ~TransformRotation() = default; - - protected: - void InteresectionTest(const Math::Ray& mouse_ray) override; - void ComputeDelta(const Math::Ray& mouse_ray, const Camera* camera) override; - void MapToTransform(Transform* transform, const TransformHandleSpace space) override; - - private: - Math::Vector3 m_initial_direction; - Math::Vector3 m_intersection_axis; - float m_previous_angle = 0.0f; - float m_angle_delta = 0.0f; - }; -} diff --git a/runtime/World/TransformHandle/TransformScale.cpp b/runtime/World/TransformHandle/TransformScale.cpp deleted file mode 100644 index e52a65506..000000000 --- a/runtime/World/TransformHandle/TransformScale.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* -Copyright(c) 2016-2022 Panos Karabelas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -//= INCLUDES ======================= -#include "pch.h" -#include "TransformScale.h" -#include "../Components/Camera.h" -#include "../Components/Transform.h" -#include "../Rendering/Geometry.h" -#include "../Rendering/Mesh.h" -//================================== - -//= NAMESPACES =============== -using namespace std; -using namespace Spartan::Math; -//============================ - -namespace Spartan -{ - TransformScale::TransformScale(Context* context) : TransformOperator(context, TransformHandleType::Scale) - { - // Create model - vector vertices; - vector indices; - - Geometry::CreateCube(&vertices, &indices); - - m_axis_mesh = make_unique(m_context); - m_axis_mesh->AddIndices(indices); - m_axis_mesh->AddVertices(vertices); - m_axis_mesh->CreateGpuBuffers(); - m_axis_mesh->ComputeAabb(); - - // Create an axis for each axis of control and fourth axis which control all of them - m_handle_x = TransformOperatorAxis(m_type, Vector3::Right, m_context); - m_handle_y = TransformOperatorAxis(m_type, Vector3::Up, m_context); - m_handle_z = TransformOperatorAxis(m_type, Vector3::Forward, m_context); - m_handle_xyz = TransformOperatorAxis(m_type, Vector3::One, m_context); - - // Create bounding boxes for the handles, based on the vertices used - m_handle_x.m_box = BoundingBox(vertices.data(), static_cast(vertices.size())); - m_handle_y.m_box = m_handle_x.m_box; - m_handle_z.m_box = m_handle_x.m_box; - m_handle_xyz.m_box = m_handle_x.m_box; - - m_offset_handle_axes_from_center = true; - } - - void TransformScale::InteresectionTest(const Math::Ray& mouse_ray) - { - // Test if the ray intersects any of the handles - m_handle_x_intersected = mouse_ray.HitDistance(m_handle_x.m_box_transformed) != Math::Helper::INFINITY_; - m_handle_y_intersected = mouse_ray.HitDistance(m_handle_y.m_box_transformed) != Math::Helper::INFINITY_; - m_handle_z_intersected = mouse_ray.HitDistance(m_handle_z.m_box_transformed) != Math::Helper::INFINITY_; - m_handle_xyz_intersected = mouse_ray.HitDistance(m_handle_xyz.m_box_transformed) != Math::Helper::INFINITY_; - } - - static Vector3 GetMousePointOnAxis(const Camera* camera, const Ray& mouse_ray, const TransformOperatorAxis& axis_handle) - { - // Find the intersection point between the camera plane and the mouse ray. - Vector3 normal = camera->GetTransform()->GetForward(); - float distance_from_origin = 0.0f; - Plane screen_plane = Plane(normal, distance_from_origin); - Vector3 plane_interesection_point = Vector3::Zero; - mouse_ray.HitDistance(screen_plane, &plane_interesection_point); - - // Find the point on the z-axis which is the closest to the ray-plane intersection point. - Vector3 closest_point = Vector3::Zero; - Ray(Vector3::Zero, axis_handle.m_axis).Distance(plane_interesection_point, closest_point); - - return closest_point; - } - - void TransformScale::ComputeDelta(const Math::Ray& mouse_ray, const Camera* camera) - { - Vector3 mouse_point_on_axis = Vector3::Zero; - float sign = 1.0f; - - if (m_handle_x.m_is_editing) - { - mouse_point_on_axis = GetMousePointOnAxis(camera, mouse_ray, m_handle_x); - } - else if (m_handle_y.m_is_editing) - { - mouse_point_on_axis = GetMousePointOnAxis(camera, mouse_ray, m_handle_y); - } - else if (m_handle_z.m_is_editing) - { - mouse_point_on_axis = GetMousePointOnAxis(camera, mouse_ray, m_handle_z); - } - else if (m_handle_xyz.m_is_editing) - { - float x = GetMousePointOnAxis(camera, mouse_ray, m_handle_x).x; - float y = GetMousePointOnAxis(camera, mouse_ray, m_handle_y).y; - float z = GetMousePointOnAxis(camera, mouse_ray, m_handle_z).z; - - mouse_point_on_axis.x = Math::Helper::Max3(x, y, z); - mouse_point_on_axis.y = mouse_point_on_axis.x; - mouse_point_on_axis.z = mouse_point_on_axis.x; - - sign = static_cast(Math::Helper::Sign(Vector3::Dot(mouse_ray.GetDirection(), Vector3::Right))); - SP_LOG_INFO("%f", sign); - } - - bool is_first_editing_run = m_handle_x.m_is_first_editing_run || m_handle_y.m_is_first_editing_run || m_handle_z.m_is_first_editing_run || m_handle_xyz.m_is_first_editing_run; - m_delta = !is_first_editing_run ? (mouse_point_on_axis - m_previous_mouse_point_on_axis) : 0.0f; - m_previous_mouse_point_on_axis = mouse_point_on_axis; - } - - void TransformScale::MapToTransform(Transform* transform, const TransformHandleSpace space) - { - SP_ASSERT(transform != nullptr); - - if (space == TransformHandleSpace::World) - { - transform->SetScale(transform->GetScale() + m_delta); - } - else - { - transform->SetScaleLocal(transform->GetScaleLocal() + m_delta); - } - } -} diff --git a/runtime/World/TransformHandle/TransformScale.h b/runtime/World/TransformHandle/TransformScale.h deleted file mode 100644 index a3d5a9b2f..000000000 --- a/runtime/World/TransformHandle/TransformScale.h +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright(c) 2016-2022 Panos Karabelas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#pragma once - -//= INCLUDES =============== -#include "TransformOperator.h" -//========================== - -namespace Spartan -{ - class SP_CLASS TransformScale : public TransformOperator - { - public: - TransformScale(Context* context = nullptr); - ~TransformScale() = default; - - protected: - void InteresectionTest(const Math::Ray& mouse_ray) override; - void ComputeDelta(const Math::Ray& mouse_ray, const Camera* camera) override; - void MapToTransform(Transform* transform, const TransformHandleSpace space) override; - - private: - Math::Vector3 m_previous_mouse_point_on_axis = Math::Vector3::Zero; - Math::Vector3 m_delta = Math::Vector3::Zero; - }; -} diff --git a/runtime/World/World.cpp b/runtime/World/World.cpp index 78ce4039b..3841ffaca 100644 --- a/runtime/World/World.cpp +++ b/runtime/World/World.cpp @@ -33,7 +33,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "Components/RigidBody.h" #include "Components/Collider.h" #include "Components/Terrain.h" -#include "TransformHandle/TransformHandle.h" #include "../Resource/ResourceCache.h" #include "../IO/FileStream.h" #include "../Profiling/Profiler.h" @@ -72,11 +71,6 @@ namespace Spartan m_profiler = m_context->GetSystem(); } - void World::OnPostInitialise() - { - m_transform_handle = make_shared(m_context); - } - void World::OnPreTick() { for (shared_ptr& entity : m_entities) @@ -91,14 +85,6 @@ namespace Spartan SP_SCOPED_TIME_BLOCK(m_profiler); - if (Renderer* renderer = m_context->GetSystem()) - { - if (renderer->GetOption(RendererOption::Debug_TransformHandle)) - { - m_transform_handle->Tick(renderer->GetCamera().get(), m_gizmo_transform_size); - } - } - // Tick entities { // Detect game toggling diff --git a/runtime/World/World.h b/runtime/World/World.h index 257066a03..b28262607 100644 --- a/runtime/World/World.h +++ b/runtime/World/World.h @@ -49,7 +49,6 @@ namespace Spartan //= ISubsystem ========================= void OnInitialise() override; - void OnPostInitialise() override; void OnPreTick() override; void OnTick(double delta_time) override; //====================================== @@ -73,10 +72,6 @@ namespace Spartan void ActivateNewEntities(); //====================================================================== - // Transform handle - std::shared_ptr GetTransformHandle() { return m_transform_handle; } - float m_gizmo_transform_size = 0.015f; - private: void Clear(); void _EntityRemove(Entity* entity); @@ -90,7 +85,6 @@ namespace Spartan std::shared_ptr m_default_model_sponza = nullptr; std::shared_ptr m_default_model_sponza_curtains = nullptr; std::shared_ptr m_default_model_car = nullptr; - std::shared_ptr m_transform_handle = nullptr; Input* m_input = nullptr; Profiler* m_profiler = nullptr;