ADD: Selection
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
#version 450
|
#version 450
|
||||||
#extension GL_GOOGLE_include_directive : require
|
#extension GL_GOOGLE_include_directive : require
|
||||||
|
#extension GL_EXT_buffer_reference : require
|
||||||
#include "input_structures.glsl"
|
#include "input_structures.glsl"
|
||||||
|
|
||||||
layout(location = 0) in vec3 inNormal;
|
layout(location = 0) in vec3 inNormal;
|
||||||
@@ -11,6 +12,26 @@ layout(location = 4) in vec4 inTangent;
|
|||||||
layout(location = 0) out vec4 outPos;
|
layout(location = 0) out vec4 outPos;
|
||||||
layout(location = 1) out vec4 outNorm;
|
layout(location = 1) out vec4 outNorm;
|
||||||
layout(location = 2) out vec4 outAlbedo;
|
layout(location = 2) out vec4 outAlbedo;
|
||||||
|
layout(location = 3) out uint outObjectID;
|
||||||
|
|
||||||
|
// Keep push constants layout in sync with mesh.vert / GPUDrawPushConstants
|
||||||
|
struct Vertex {
|
||||||
|
vec3 position; float uv_x;
|
||||||
|
vec3 normal; float uv_y;
|
||||||
|
vec4 color;
|
||||||
|
vec4 tangent;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(buffer_reference, std430) readonly buffer VertexBuffer{
|
||||||
|
Vertex vertices[];
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(push_constant) uniform constants
|
||||||
|
{
|
||||||
|
mat4 render_matrix;
|
||||||
|
VertexBuffer vertexBuffer;
|
||||||
|
uint objectID;
|
||||||
|
} PushConstants;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// Apply baseColor texture and baseColorFactor once
|
// Apply baseColor texture and baseColorFactor once
|
||||||
@@ -37,4 +58,5 @@ void main() {
|
|||||||
outPos = vec4(inWorldPos, 1.0);
|
outPos = vec4(inWorldPos, 1.0);
|
||||||
outNorm = vec4(Nw, roughness);
|
outNorm = vec4(Nw, roughness);
|
||||||
outAlbedo = vec4(albedo, metallic);
|
outAlbedo = vec4(albedo, metallic);
|
||||||
|
outObjectID = PushConstants.objectID;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ layout(buffer_reference, std430) readonly buffer VertexBuffer{
|
|||||||
Vertex vertices[];
|
Vertex vertices[];
|
||||||
};
|
};
|
||||||
|
|
||||||
//push constants block
|
//push constants block (must match GPUDrawPushConstants layout in C++)
|
||||||
layout( push_constant ) uniform constants
|
layout(push_constant) uniform constants
|
||||||
{
|
{
|
||||||
mat4 render_matrix;
|
mat4 render_matrix;
|
||||||
VertexBuffer vertexBuffer;
|
VertexBuffer vertexBuffer;
|
||||||
|
uint objectID;
|
||||||
} PushConstants;
|
} PushConstants;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ layout(buffer_reference, std430) readonly buffer VertexBuffer{
|
|||||||
layout(push_constant) uniform PushConsts {
|
layout(push_constant) uniform PushConsts {
|
||||||
mat4 render_matrix;
|
mat4 render_matrix;
|
||||||
VertexBuffer vertexBuffer;
|
VertexBuffer vertexBuffer;
|
||||||
|
uint objectID;
|
||||||
uint cascadeIndex; // which cascade this pass renders
|
uint cascadeIndex; // which cascade this pass renders
|
||||||
// pad to 16-byte boundary implicitly
|
// pad to 16-byte boundary implicitly
|
||||||
} PC;
|
} PC;
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ target_include_directories(vulkan_engine PUBLIC
|
|||||||
|
|
||||||
option(ENABLE_MIKKTS "Use MikkTSpace for tangent generation" ON)
|
option(ENABLE_MIKKTS "Use MikkTSpace for tangent generation" ON)
|
||||||
|
|
||||||
target_link_libraries(vulkan_engine PUBLIC vma glm Vulkan::Vulkan fmt::fmt stb_image SDL2::SDL2 vkbootstrap imgui fastgltf::fastgltf)
|
target_link_libraries(vulkan_engine PUBLIC vma glm Vulkan::Vulkan fmt::fmt stb_image SDL2::SDL2 vkbootstrap imgui fastgltf::fastgltf ImGuizmo)
|
||||||
if (ENABLE_MIKKTS)
|
if (ENABLE_MIKKTS)
|
||||||
target_link_libraries(vulkan_engine PUBLIC mikktspace)
|
target_link_libraries(vulkan_engine PUBLIC mikktspace)
|
||||||
target_compile_definitions(vulkan_engine PUBLIC MIKKTS_ENABLE=1)
|
target_compile_definitions(vulkan_engine PUBLIC MIKKTS_ENABLE=1)
|
||||||
|
|||||||
@@ -14,10 +14,8 @@
|
|||||||
//
|
//
|
||||||
//> includes
|
//> includes
|
||||||
#include "vk_engine.h"
|
#include "vk_engine.h"
|
||||||
#include <core/vk_images.h>
|
|
||||||
|
|
||||||
#include "SDL2/SDL.h"
|
#include "SDL2/SDL.h"
|
||||||
#include "SDL2/SDL_vulkan.h"
|
|
||||||
|
|
||||||
#include <core/vk_initializers.h>
|
#include <core/vk_initializers.h>
|
||||||
#include <core/vk_types.h>
|
#include <core/vk_types.h>
|
||||||
@@ -29,7 +27,6 @@
|
|||||||
#include <span>
|
#include <span>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
#include "render/vk_pipelines.h"
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <glm/gtx/transform.hpp>
|
#include <glm/gtx/transform.hpp>
|
||||||
|
|
||||||
@@ -49,7 +46,6 @@
|
|||||||
#include "vk_resource.h"
|
#include "vk_resource.h"
|
||||||
#include "engine_context.h"
|
#include "engine_context.h"
|
||||||
#include "core/vk_pipeline_manager.h"
|
#include "core/vk_pipeline_manager.h"
|
||||||
#include "core/config.h"
|
|
||||||
#include "core/texture_cache.h"
|
#include "core/texture_cache.h"
|
||||||
#include "core/ibl_manager.h"
|
#include "core/ibl_manager.h"
|
||||||
|
|
||||||
@@ -540,6 +536,44 @@ namespace {
|
|||||||
const DrawContext &dc = eng->_context->getMainDrawContext();
|
const DrawContext &dc = eng->_context->getMainDrawContext();
|
||||||
ImGui::Text("Opaque draws: %zu", dc.OpaqueSurfaces.size());
|
ImGui::Text("Opaque draws: %zu", dc.OpaqueSurfaces.size());
|
||||||
ImGui::Text("Transp draws: %zu", dc.TransparentSurfaces.size());
|
ImGui::Text("Transp draws: %zu", dc.TransparentSurfaces.size());
|
||||||
|
ImGui::Checkbox("Use ID-buffer picking", &eng->_useIdBufferPicking);
|
||||||
|
ImGui::Separator();
|
||||||
|
if (eng->_lastPick.valid)
|
||||||
|
{
|
||||||
|
const char *meshName = eng->_lastPick.mesh ? eng->_lastPick.mesh->name.c_str() : "<unknown>";
|
||||||
|
const char *sceneName = "<none>";
|
||||||
|
if (eng->_lastPick.scene && !eng->_lastPick.scene->debugName.empty())
|
||||||
|
{
|
||||||
|
sceneName = eng->_lastPick.scene->debugName.c_str();
|
||||||
|
}
|
||||||
|
ImGui::Text("Last pick scene: %s", sceneName);
|
||||||
|
ImGui::Text("Last pick mesh: %s (surface %u)", meshName, eng->_lastPick.surfaceIndex);
|
||||||
|
ImGui::Text("World pos: (%.3f, %.3f, %.3f)",
|
||||||
|
eng->_lastPick.worldPos.x,
|
||||||
|
eng->_lastPick.worldPos.y,
|
||||||
|
eng->_lastPick.worldPos.z);
|
||||||
|
ImGui::Text("Indices: first=%u count=%u",
|
||||||
|
eng->_lastPick.firstIndex,
|
||||||
|
eng->_lastPick.indexCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted("Last pick: <none>");
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (eng->_hoverPick.valid)
|
||||||
|
{
|
||||||
|
const char *meshName = eng->_hoverPick.mesh ? eng->_hoverPick.mesh->name.c_str() : "<unknown>";
|
||||||
|
ImGui::Text("Hover mesh: %s (surface %u)", meshName, eng->_hoverPick.surfaceIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted("Hover: <none>");
|
||||||
|
}
|
||||||
|
if (!eng->_dragSelection.empty())
|
||||||
|
{
|
||||||
|
ImGui::Text("Drag selection: %zu objects", eng->_dragSelection.size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -708,7 +742,7 @@ void VulkanEngine::init()
|
|||||||
auto imguiPass = std::make_unique<ImGuiPass>();
|
auto imguiPass = std::make_unique<ImGuiPass>();
|
||||||
_renderPassManager->setImGuiPass(std::move(imguiPass));
|
_renderPassManager->setImGuiPass(std::move(imguiPass));
|
||||||
|
|
||||||
const std::string structurePath = _assetManager->modelPath("sponza_2/scene.gltf");
|
const std::string structurePath = _assetManager->modelPath("mirage2000/scene.gltf");
|
||||||
const auto structureFile = _assetManager->loadGLTF(structurePath);
|
const auto structureFile = _assetManager->loadGLTF(structurePath);
|
||||||
|
|
||||||
assert(structureFile.has_value());
|
assert(structureFile.has_value());
|
||||||
@@ -857,6 +891,13 @@ void VulkanEngine::cleanup()
|
|||||||
print_vma_stats(_deviceManager.get(), "after RTManager");
|
print_vma_stats(_deviceManager.get(), "after RTManager");
|
||||||
dump_vma_json(_deviceManager.get(), "after_RTManager");
|
dump_vma_json(_deviceManager.get(), "after_RTManager");
|
||||||
|
|
||||||
|
// Destroy pick readback buffer before resource manager cleanup
|
||||||
|
if (_pickReadbackBuffer.buffer)
|
||||||
|
{
|
||||||
|
_resourceManager->destroy_buffer(_pickReadbackBuffer);
|
||||||
|
_pickReadbackBuffer = {};
|
||||||
|
}
|
||||||
|
|
||||||
_resourceManager->cleanup();
|
_resourceManager->cleanup();
|
||||||
print_vma_stats(_deviceManager.get(), "after ResourceManager");
|
print_vma_stats(_deviceManager.get(), "after ResourceManager");
|
||||||
dump_vma_json(_deviceManager.get(), "after_ResourceManager");
|
dump_vma_json(_deviceManager.get(), "after_ResourceManager");
|
||||||
@@ -884,11 +925,43 @@ void VulkanEngine::cleanup()
|
|||||||
|
|
||||||
void VulkanEngine::draw()
|
void VulkanEngine::draw()
|
||||||
{
|
{
|
||||||
_sceneManager->update_scene();
|
|
||||||
//> frame_clear
|
//> frame_clear
|
||||||
//wait until the gpu has finished rendering the last frame. Timeout of 1 second
|
//wait until the gpu has finished rendering the last frame. Timeout of 1 second
|
||||||
VK_CHECK(vkWaitForFences(_deviceManager->device(), 1, &get_current_frame()._renderFence, true, 1000000000));
|
VK_CHECK(vkWaitForFences(_deviceManager->device(), 1, &get_current_frame()._renderFence, true, 1000000000));
|
||||||
|
|
||||||
|
// If we scheduled an ID-buffer readback in the previous frame, resolve it now.
|
||||||
|
if (_pickResultPending && _pickReadbackBuffer.buffer && _sceneManager)
|
||||||
|
{
|
||||||
|
vmaInvalidateAllocation(_deviceManager->allocator(), _pickReadbackBuffer.allocation, 0, sizeof(uint32_t));
|
||||||
|
uint32_t pickedID = 0;
|
||||||
|
if (_pickReadbackBuffer.info.pMappedData)
|
||||||
|
{
|
||||||
|
pickedID = *reinterpret_cast<const uint32_t *>(_pickReadbackBuffer.info.pMappedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pickedID == 0)
|
||||||
|
{
|
||||||
|
// Do not override existing raycast pick when ID buffer reports "no object".
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RenderObject picked{};
|
||||||
|
if (_sceneManager->resolveObjectID(pickedID, picked))
|
||||||
|
{
|
||||||
|
// Fallback hit position: object origin in world space (can refine later)
|
||||||
|
glm::vec3 fallbackPos = glm::vec3(picked.transform[3]);
|
||||||
|
_lastPick.mesh = picked.sourceMesh;
|
||||||
|
_lastPick.scene = picked.sourceScene;
|
||||||
|
_lastPick.worldPos = fallbackPos;
|
||||||
|
_lastPick.firstIndex = picked.firstIndex;
|
||||||
|
_lastPick.indexCount = picked.indexCount;
|
||||||
|
_lastPick.surfaceIndex = picked.surfaceIndex;
|
||||||
|
_lastPick.valid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_pickResultPending = false;
|
||||||
|
}
|
||||||
|
|
||||||
get_current_frame()._deletionQueue.flush();
|
get_current_frame()._deletionQueue.flush();
|
||||||
// Resolve last frame's pass timings before we clear and rebuild the graph
|
// Resolve last frame's pass timings before we clear and rebuild the graph
|
||||||
if (_renderGraph)
|
if (_renderGraph)
|
||||||
@@ -898,6 +971,29 @@ void VulkanEngine::draw()
|
|||||||
get_current_frame()._frameDescriptors.clear_pools(_deviceManager->device());
|
get_current_frame()._frameDescriptors.clear_pools(_deviceManager->device());
|
||||||
//< frame_clear
|
//< frame_clear
|
||||||
|
|
||||||
|
_sceneManager->update_scene();
|
||||||
|
|
||||||
|
// Per-frame hover raycast based on last mouse position.
|
||||||
|
if (_sceneManager && _mousePosPixels.x >= 0.0f && _mousePosPixels.y >= 0.0f)
|
||||||
|
{
|
||||||
|
RenderObject hoverObj{};
|
||||||
|
glm::vec3 hoverPos{};
|
||||||
|
if (_sceneManager->pick(_mousePosPixels, hoverObj, hoverPos))
|
||||||
|
{
|
||||||
|
_hoverPick.mesh = hoverObj.sourceMesh;
|
||||||
|
_hoverPick.scene = hoverObj.sourceScene;
|
||||||
|
_hoverPick.worldPos = hoverPos;
|
||||||
|
_hoverPick.firstIndex = hoverObj.firstIndex;
|
||||||
|
_hoverPick.indexCount = hoverObj.indexCount;
|
||||||
|
_hoverPick.surfaceIndex = hoverObj.surfaceIndex;
|
||||||
|
_hoverPick.valid = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_hoverPick.valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t swapchainImageIndex;
|
uint32_t swapchainImageIndex;
|
||||||
|
|
||||||
VkResult e = vkAcquireNextImageKHR(_deviceManager->device(), _swapchainManager->swapchain(), 1000000000,
|
VkResult e = vkAcquireNextImageKHR(_deviceManager->device(), _swapchainManager->swapchain(), 1000000000,
|
||||||
@@ -999,7 +1095,67 @@ void VulkanEngine::draw()
|
|||||||
}
|
}
|
||||||
if (auto *geometry = _renderPassManager->getPass<GeometryPass>())
|
if (auto *geometry = _renderPassManager->getPass<GeometryPass>())
|
||||||
{
|
{
|
||||||
geometry->register_graph(_renderGraph.get(), hGBufferPosition, hGBufferNormal, hGBufferAlbedo, hDepth);
|
RGImageHandle hID = _renderGraph->import_id_buffer();
|
||||||
|
geometry->register_graph(_renderGraph.get(), hGBufferPosition, hGBufferNormal, hGBufferAlbedo, hID, hDepth);
|
||||||
|
|
||||||
|
// If ID-buffer picking is enabled and a pick was requested this frame,
|
||||||
|
// add a small transfer pass to read back 1 pixel from the ID buffer.
|
||||||
|
if (_useIdBufferPicking && _pendingPick.active && hID.valid() && _pickReadbackBuffer.buffer)
|
||||||
|
{
|
||||||
|
VkExtent2D swapExt = _swapchainManager->swapchainExtent();
|
||||||
|
VkExtent2D drawExt = _drawExtent;
|
||||||
|
|
||||||
|
float sx = _pendingPick.windowPos.x / float(std::max(1u, swapExt.width));
|
||||||
|
float sy = _pendingPick.windowPos.y / float(std::max(1u, swapExt.height));
|
||||||
|
|
||||||
|
uint32_t idX = uint32_t(glm::clamp(sx * float(drawExt.width), 0.0f, float(drawExt.width - 1)));
|
||||||
|
uint32_t idY = uint32_t(glm::clamp(sy * float(drawExt.height), 0.0f, float(drawExt.height - 1)));
|
||||||
|
_pendingPick.idCoords = {idX, idY};
|
||||||
|
|
||||||
|
RGImportedBufferDesc bd{};
|
||||||
|
bd.name = "pick.readback";
|
||||||
|
bd.buffer = _pickReadbackBuffer.buffer;
|
||||||
|
bd.size = sizeof(uint32_t);
|
||||||
|
bd.currentStage = VK_PIPELINE_STAGE_2_NONE;
|
||||||
|
bd.currentAccess = 0;
|
||||||
|
RGBufferHandle hPickBuf = _renderGraph->import_buffer(bd);
|
||||||
|
|
||||||
|
_renderGraph->add_pass(
|
||||||
|
"PickReadback",
|
||||||
|
RGPassType::Transfer,
|
||||||
|
[hID, hPickBuf](RGPassBuilder &builder, EngineContext *)
|
||||||
|
{
|
||||||
|
builder.read(hID, RGImageUsage::TransferSrc);
|
||||||
|
builder.write_buffer(hPickBuf, RGBufferUsage::TransferDst);
|
||||||
|
},
|
||||||
|
[this, hID, hPickBuf](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *)
|
||||||
|
{
|
||||||
|
VkImage idImage = res.image(hID);
|
||||||
|
VkBuffer dst = res.buffer(hPickBuf);
|
||||||
|
if (idImage == VK_NULL_HANDLE || dst == VK_NULL_HANDLE) return;
|
||||||
|
|
||||||
|
VkBufferImageCopy region{};
|
||||||
|
region.bufferOffset = 0;
|
||||||
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
region.imageSubresource.mipLevel = 0;
|
||||||
|
region.imageSubresource.baseArrayLayer = 0;
|
||||||
|
region.imageSubresource.layerCount = 1;
|
||||||
|
region.imageOffset = { static_cast<int32_t>(_pendingPick.idCoords.x),
|
||||||
|
static_cast<int32_t>(_pendingPick.idCoords.y),
|
||||||
|
0 };
|
||||||
|
region.imageExtent = {1, 1, 1};
|
||||||
|
|
||||||
|
vkCmdCopyImageToBuffer(cmd,
|
||||||
|
idImage,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||||
|
dst,
|
||||||
|
1,
|
||||||
|
®ion);
|
||||||
|
});
|
||||||
|
|
||||||
|
_pickResultPending = true;
|
||||||
|
_pendingPick.active = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (auto *lighting = _renderPassManager->getPass<LightingPass>())
|
if (auto *lighting = _renderPassManager->getPass<LightingPass>())
|
||||||
{
|
{
|
||||||
@@ -1104,6 +1260,95 @@ void VulkanEngine::run()
|
|||||||
freeze_rendering = false;
|
freeze_rendering = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (e.type == SDL_MOUSEMOTION)
|
||||||
|
{
|
||||||
|
_mousePosPixels = glm::vec2{static_cast<float>(e.motion.x),
|
||||||
|
static_cast<float>(e.motion.y)};
|
||||||
|
if (_dragState.buttonDown)
|
||||||
|
{
|
||||||
|
_dragState.current = _mousePosPixels;
|
||||||
|
// Consider any motion as dragging for now; can add threshold if desired.
|
||||||
|
_dragState.dragging = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_LEFT)
|
||||||
|
{
|
||||||
|
_dragState.buttonDown = true;
|
||||||
|
_dragState.dragging = false;
|
||||||
|
_dragState.start = glm::vec2{static_cast<float>(e.button.x),
|
||||||
|
static_cast<float>(e.button.y)};
|
||||||
|
_dragState.current = _dragState.start;
|
||||||
|
}
|
||||||
|
if (e.type == SDL_MOUSEBUTTONUP && e.button.button == SDL_BUTTON_LEFT)
|
||||||
|
{
|
||||||
|
glm::vec2 releasePos{static_cast<float>(e.button.x),
|
||||||
|
static_cast<float>(e.button.y)};
|
||||||
|
_dragState.buttonDown = false;
|
||||||
|
|
||||||
|
constexpr float clickThreshold = 3.0f;
|
||||||
|
glm::vec2 delta = releasePos - _dragState.start;
|
||||||
|
bool treatAsClick = !_dragState.dragging &&
|
||||||
|
std::abs(delta.x) < clickThreshold &&
|
||||||
|
std::abs(delta.y) < clickThreshold;
|
||||||
|
|
||||||
|
if (treatAsClick)
|
||||||
|
{
|
||||||
|
// Raycast click selection
|
||||||
|
if (_sceneManager)
|
||||||
|
{
|
||||||
|
RenderObject hitObject{};
|
||||||
|
glm::vec3 hitPos{};
|
||||||
|
if (_sceneManager->pick(releasePos, hitObject, hitPos))
|
||||||
|
{
|
||||||
|
_lastPick.mesh = hitObject.sourceMesh;
|
||||||
|
_lastPick.scene = hitObject.sourceScene;
|
||||||
|
_lastPick.worldPos = hitPos;
|
||||||
|
_lastPick.firstIndex = hitObject.firstIndex;
|
||||||
|
_lastPick.indexCount = hitObject.indexCount;
|
||||||
|
_lastPick.surfaceIndex = hitObject.surfaceIndex;
|
||||||
|
_lastPick.valid = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_lastPick.valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally queue an ID-buffer pick at this position
|
||||||
|
if (_useIdBufferPicking)
|
||||||
|
{
|
||||||
|
_pendingPick.active = true;
|
||||||
|
_pendingPick.windowPos = releasePos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Drag selection completed; compute selection based on screen-space rectangle.
|
||||||
|
_dragSelection.clear();
|
||||||
|
if (_sceneManager)
|
||||||
|
{
|
||||||
|
std::vector<RenderObject> selected;
|
||||||
|
_sceneManager->selectRect(_dragState.start, releasePos, selected);
|
||||||
|
_dragSelection.reserve(selected.size());
|
||||||
|
for (const RenderObject &obj : selected)
|
||||||
|
{
|
||||||
|
PickInfo info{};
|
||||||
|
info.mesh = obj.sourceMesh;
|
||||||
|
info.scene = obj.sourceScene;
|
||||||
|
// Use bounds origin transformed to world as a representative point.
|
||||||
|
glm::vec3 centerWorld = glm::vec3(obj.transform * glm::vec4(obj.bounds.origin, 1.0f));
|
||||||
|
info.worldPos = centerWorld;
|
||||||
|
info.firstIndex = obj.firstIndex;
|
||||||
|
info.indexCount = obj.indexCount;
|
||||||
|
info.surfaceIndex = obj.surfaceIndex;
|
||||||
|
info.valid = true;
|
||||||
|
_dragSelection.push_back(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_dragState.dragging = false;
|
||||||
|
}
|
||||||
_sceneManager->getMainCamera().processSDLEvent(e);
|
_sceneManager->getMainCamera().processSDLEvent(e);
|
||||||
ImGui_ImplSDL2_ProcessEvent(&e);
|
ImGui_ImplSDL2_ProcessEvent(&e);
|
||||||
}
|
}
|
||||||
@@ -1207,6 +1452,12 @@ void VulkanEngine::init_frame_resources()
|
|||||||
{
|
{
|
||||||
_frames[i].init(_deviceManager.get(), frame_sizes);
|
_frames[i].init(_deviceManager.get(), frame_sizes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allocate a small readback buffer for ID-buffer picking (single uint32 pixel)
|
||||||
|
_pickReadbackBuffer = _resourceManager->create_buffer(
|
||||||
|
sizeof(uint32_t),
|
||||||
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||||
|
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanEngine::init_pipelines()
|
void VulkanEngine::init_pipelines()
|
||||||
@@ -1218,8 +1469,16 @@ void MeshNode::Draw(const glm::mat4 &topMatrix, DrawContext &ctx)
|
|||||||
{
|
{
|
||||||
glm::mat4 nodeMatrix = topMatrix * worldTransform;
|
glm::mat4 nodeMatrix = topMatrix * worldTransform;
|
||||||
|
|
||||||
for (auto &s: mesh->surfaces)
|
if (!mesh)
|
||||||
{
|
{
|
||||||
|
Node::Draw(topMatrix, ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < mesh->surfaces.size(); ++i)
|
||||||
|
{
|
||||||
|
const auto &s = mesh->surfaces[i];
|
||||||
|
|
||||||
RenderObject def{};
|
RenderObject def{};
|
||||||
def.indexCount = s.count;
|
def.indexCount = s.count;
|
||||||
def.firstIndex = s.startIndex;
|
def.firstIndex = s.startIndex;
|
||||||
@@ -1230,6 +1489,10 @@ void MeshNode::Draw(const glm::mat4 &topMatrix, DrawContext &ctx)
|
|||||||
|
|
||||||
def.transform = nodeMatrix;
|
def.transform = nodeMatrix;
|
||||||
def.vertexBufferAddress = mesh->meshBuffers.vertexBufferAddress;
|
def.vertexBufferAddress = mesh->meshBuffers.vertexBufferAddress;
|
||||||
|
def.sourceMesh = mesh.get();
|
||||||
|
def.surfaceIndex = i;
|
||||||
|
def.objectID = ctx.nextID++;
|
||||||
|
def.sourceScene = scene;
|
||||||
|
|
||||||
if (s.material->data.passType == MaterialPass::Transparent)
|
if (s.material->data.passType == MaterialPass::Transparent)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,9 +49,11 @@ struct RenderPass
|
|||||||
|
|
||||||
struct MeshNode : public Node
|
struct MeshNode : public Node
|
||||||
{
|
{
|
||||||
std::shared_ptr<MeshAsset> mesh;
|
std::shared_ptr<MeshAsset> mesh;
|
||||||
|
// Owning glTF scene (for picking/debug); may be null for non-gltf meshes.
|
||||||
|
LoadedGLTF *scene = nullptr;
|
||||||
|
|
||||||
virtual void Draw(const glm::mat4 &topMatrix, DrawContext &ctx) override;
|
virtual void Draw(const glm::mat4 &topMatrix, DrawContext &ctx) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class VulkanEngine
|
class VulkanEngine
|
||||||
@@ -114,6 +116,42 @@ public:
|
|||||||
// Debug helpers: track spawned IBL test meshes to remove them easily
|
// Debug helpers: track spawned IBL test meshes to remove them easily
|
||||||
std::vector<std::string> _iblTestNames;
|
std::vector<std::string> _iblTestNames;
|
||||||
|
|
||||||
|
struct PickInfo
|
||||||
|
{
|
||||||
|
MeshAsset *mesh = nullptr;
|
||||||
|
LoadedGLTF *scene = nullptr;
|
||||||
|
glm::vec3 worldPos{0.0f};
|
||||||
|
uint32_t indexCount = 0;
|
||||||
|
uint32_t firstIndex = 0;
|
||||||
|
uint32_t surfaceIndex = 0;
|
||||||
|
bool valid = false;
|
||||||
|
} _lastPick;
|
||||||
|
|
||||||
|
struct PickRequest
|
||||||
|
{
|
||||||
|
bool active = false;
|
||||||
|
glm::vec2 windowPos{0.0f};
|
||||||
|
glm::uvec2 idCoords{0, 0};
|
||||||
|
} _pendingPick;
|
||||||
|
bool _pickResultPending = false;
|
||||||
|
AllocatedBuffer _pickReadbackBuffer{};
|
||||||
|
|
||||||
|
// Hover and drag-selection state (raycast-based)
|
||||||
|
PickInfo _hoverPick{};
|
||||||
|
glm::vec2 _mousePosPixels{-1.0f, -1.0f};
|
||||||
|
struct DragState
|
||||||
|
{
|
||||||
|
bool dragging = false;
|
||||||
|
bool buttonDown = false;
|
||||||
|
glm::vec2 start{0.0f};
|
||||||
|
glm::vec2 current{0.0f};
|
||||||
|
} _dragState;
|
||||||
|
// Optional list of last drag-selected objects (for future editing UI)
|
||||||
|
std::vector<PickInfo> _dragSelection;
|
||||||
|
|
||||||
|
// Toggle to enable/disable ID-buffer picking in addition to raycast
|
||||||
|
bool _useIdBufferPicking = false;
|
||||||
|
|
||||||
// Debug: persistent pass enable overrides (by pass name)
|
// Debug: persistent pass enable overrides (by pass name)
|
||||||
std::unordered_map<std::string, bool> _rgPassToggles;
|
std::unordered_map<std::string, bool> _rgPassToggles;
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ void SwapchainManager::init_swapchain()
|
|||||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||||
_gBufferAlbedo = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R8G8B8A8_UNORM,
|
_gBufferAlbedo = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R8G8B8A8_UNORM,
|
||||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||||
|
_idBuffer = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R32_UINT,
|
||||||
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
|
||||||
|
VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
|
||||||
|
VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||||
|
|
||||||
_deletionQueue.push_function([=]() {
|
_deletionQueue.push_function([=]() {
|
||||||
vkDestroyImageView(_deviceManager->device(), _drawImage.imageView, nullptr);
|
vkDestroyImageView(_deviceManager->device(), _drawImage.imageView, nullptr);
|
||||||
@@ -81,6 +85,7 @@ void SwapchainManager::init_swapchain()
|
|||||||
_resourceManager->destroy_image(_gBufferPosition);
|
_resourceManager->destroy_image(_gBufferPosition);
|
||||||
_resourceManager->destroy_image(_gBufferNormal);
|
_resourceManager->destroy_image(_gBufferNormal);
|
||||||
_resourceManager->destroy_image(_gBufferAlbedo);
|
_resourceManager->destroy_image(_gBufferAlbedo);
|
||||||
|
_resourceManager->destroy_image(_idBuffer);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -191,6 +196,10 @@ void SwapchainManager::resize_swapchain(struct SDL_Window *window)
|
|||||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||||
_gBufferAlbedo = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R8G8B8A8_UNORM,
|
_gBufferAlbedo = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R8G8B8A8_UNORM,
|
||||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||||
|
_idBuffer = _resourceManager->create_image(drawImageExtent, VK_FORMAT_R32_UINT,
|
||||||
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
|
||||||
|
VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
|
||||||
|
VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||||
|
|
||||||
_deletionQueue.push_function([=]() {
|
_deletionQueue.push_function([=]() {
|
||||||
vkDestroyImageView(_deviceManager->device(), _drawImage.imageView, nullptr);
|
vkDestroyImageView(_deviceManager->device(), _drawImage.imageView, nullptr);
|
||||||
@@ -202,6 +211,7 @@ void SwapchainManager::resize_swapchain(struct SDL_Window *window)
|
|||||||
_resourceManager->destroy_image(_gBufferPosition);
|
_resourceManager->destroy_image(_gBufferPosition);
|
||||||
_resourceManager->destroy_image(_gBufferNormal);
|
_resourceManager->destroy_image(_gBufferNormal);
|
||||||
_resourceManager->destroy_image(_gBufferAlbedo);
|
_resourceManager->destroy_image(_gBufferAlbedo);
|
||||||
|
_resourceManager->destroy_image(_idBuffer);
|
||||||
});
|
});
|
||||||
|
|
||||||
resize_requested = false;
|
resize_requested = false;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public:
|
|||||||
AllocatedImage gBufferPosition() const { return _gBufferPosition; }
|
AllocatedImage gBufferPosition() const { return _gBufferPosition; }
|
||||||
AllocatedImage gBufferNormal() const { return _gBufferNormal; }
|
AllocatedImage gBufferNormal() const { return _gBufferNormal; }
|
||||||
AllocatedImage gBufferAlbedo() const { return _gBufferAlbedo; }
|
AllocatedImage gBufferAlbedo() const { return _gBufferAlbedo; }
|
||||||
|
AllocatedImage idBuffer() const { return _idBuffer; }
|
||||||
VkExtent2D windowExtent() const { return _windowExtent; }
|
VkExtent2D windowExtent() const { return _windowExtent; }
|
||||||
|
|
||||||
bool resize_requested{false};
|
bool resize_requested{false};
|
||||||
@@ -50,6 +51,7 @@ private:
|
|||||||
AllocatedImage _gBufferPosition = {};
|
AllocatedImage _gBufferPosition = {};
|
||||||
AllocatedImage _gBufferNormal = {};
|
AllocatedImage _gBufferNormal = {};
|
||||||
AllocatedImage _gBufferAlbedo = {};
|
AllocatedImage _gBufferAlbedo = {};
|
||||||
|
AllocatedImage _idBuffer = {};
|
||||||
|
|
||||||
DeletionQueue _deletionQueue;
|
DeletionQueue _deletionQueue;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ struct GPUMeshBuffers {
|
|||||||
struct GPUDrawPushConstants {
|
struct GPUDrawPushConstants {
|
||||||
glm::mat4 worldMatrix;
|
glm::mat4 worldMatrix;
|
||||||
VkDeviceAddress vertexBuffer;
|
VkDeviceAddress vertexBuffer;
|
||||||
|
uint32_t objectID;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawContext;
|
struct DrawContext;
|
||||||
|
|||||||
@@ -947,6 +947,18 @@ RGImageHandle RenderGraph::import_gbuffer_albedo()
|
|||||||
return import_image(d);
|
return import_image(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RGImageHandle RenderGraph::import_id_buffer()
|
||||||
|
{
|
||||||
|
RGImportedImageDesc d{};
|
||||||
|
d.name = "idBuffer.objectID";
|
||||||
|
d.image = _context->getSwapchain()->idBuffer().image;
|
||||||
|
d.imageView = _context->getSwapchain()->idBuffer().imageView;
|
||||||
|
d.format = _context->getSwapchain()->idBuffer().imageFormat;
|
||||||
|
d.extent = _context->getDrawExtent();
|
||||||
|
d.currentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
return import_image(d);
|
||||||
|
}
|
||||||
|
|
||||||
RGImageHandle RenderGraph::import_swapchain_image(uint32_t index)
|
RGImageHandle RenderGraph::import_swapchain_image(uint32_t index)
|
||||||
{
|
{
|
||||||
RGImportedImageDesc d{};
|
RGImportedImageDesc d{};
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ struct Pass; // fwd
|
|||||||
RGImageHandle import_gbuffer_position();
|
RGImageHandle import_gbuffer_position();
|
||||||
RGImageHandle import_gbuffer_normal();
|
RGImageHandle import_gbuffer_normal();
|
||||||
RGImageHandle import_gbuffer_albedo();
|
RGImageHandle import_gbuffer_albedo();
|
||||||
|
RGImageHandle import_id_buffer();
|
||||||
RGImageHandle import_swapchain_image(uint32_t index);
|
RGImageHandle import_swapchain_image(uint32_t index);
|
||||||
void add_present_chain(RGImageHandle sourceDraw,
|
void add_present_chain(RGImageHandle sourceDraw,
|
||||||
RGImageHandle targetSwapchain,
|
RGImageHandle targetSwapchain,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ void GLTFMetallic_Roughness::build_pipelines(VulkanEngine *engine)
|
|||||||
VkPushConstantRange matrixRange{};
|
VkPushConstantRange matrixRange{};
|
||||||
matrixRange.offset = 0;
|
matrixRange.offset = 0;
|
||||||
matrixRange.size = sizeof(GPUDrawPushConstants);
|
matrixRange.size = sizeof(GPUDrawPushConstants);
|
||||||
matrixRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
matrixRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||||
|
|
||||||
DescriptorLayoutBuilder layoutBuilder;
|
DescriptorLayoutBuilder layoutBuilder;
|
||||||
layoutBuilder.add_binding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
layoutBuilder.add_binding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||||
@@ -92,9 +92,10 @@ void GLTFMetallic_Roughness::build_pipelines(VulkanEngine *engine)
|
|||||||
VkFormat gFormats[] = {
|
VkFormat gFormats[] = {
|
||||||
engine->_swapchainManager->gBufferPosition().imageFormat,
|
engine->_swapchainManager->gBufferPosition().imageFormat,
|
||||||
engine->_swapchainManager->gBufferNormal().imageFormat,
|
engine->_swapchainManager->gBufferNormal().imageFormat,
|
||||||
engine->_swapchainManager->gBufferAlbedo().imageFormat
|
engine->_swapchainManager->gBufferAlbedo().imageFormat,
|
||||||
|
engine->_swapchainManager->idBuffer().imageFormat
|
||||||
};
|
};
|
||||||
b.set_color_attachment_formats(std::span<VkFormat>(gFormats, 3));
|
b.set_color_attachment_formats(std::span<VkFormat>(gFormats, 4));
|
||||||
b.set_depth_format(engine->_swapchainManager->depthImage().imageFormat);
|
b.set_depth_format(engine->_swapchainManager->depthImage().imageFormat);
|
||||||
};
|
};
|
||||||
engine->_pipelineManager->registerGraphics("mesh.gbuffer", gbufferInfo);
|
engine->_pipelineManager->registerGraphics("mesh.gbuffer", gbufferInfo);
|
||||||
|
|||||||
@@ -69,9 +69,11 @@ void GeometryPass::register_graph(RenderGraph *graph,
|
|||||||
RGImageHandle gbufferPosition,
|
RGImageHandle gbufferPosition,
|
||||||
RGImageHandle gbufferNormal,
|
RGImageHandle gbufferNormal,
|
||||||
RGImageHandle gbufferAlbedo,
|
RGImageHandle gbufferAlbedo,
|
||||||
|
RGImageHandle idHandle,
|
||||||
RGImageHandle depthHandle)
|
RGImageHandle depthHandle)
|
||||||
{
|
{
|
||||||
if (!graph || !gbufferPosition.valid() || !gbufferNormal.valid() || !gbufferAlbedo.valid() || !depthHandle.valid())
|
if (!graph || !gbufferPosition.valid() || !gbufferNormal.valid() || !gbufferAlbedo.valid() ||
|
||||||
|
!idHandle.valid() || !depthHandle.valid())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -79,7 +81,7 @@ void GeometryPass::register_graph(RenderGraph *graph,
|
|||||||
graph->add_pass(
|
graph->add_pass(
|
||||||
"Geometry",
|
"Geometry",
|
||||||
RGPassType::Graphics,
|
RGPassType::Graphics,
|
||||||
[gbufferPosition, gbufferNormal, gbufferAlbedo, depthHandle](RGPassBuilder &builder, EngineContext *ctx)
|
[gbufferPosition, gbufferNormal, gbufferAlbedo, idHandle, depthHandle](RGPassBuilder &builder, EngineContext *ctx)
|
||||||
{
|
{
|
||||||
VkClearValue clear{};
|
VkClearValue clear{};
|
||||||
clear.color = {{0.f, 0.f, 0.f, 0.f}};
|
clear.color = {{0.f, 0.f, 0.f, 0.f}};
|
||||||
@@ -87,6 +89,9 @@ void GeometryPass::register_graph(RenderGraph *graph,
|
|||||||
builder.write_color(gbufferPosition, true, clear);
|
builder.write_color(gbufferPosition, true, clear);
|
||||||
builder.write_color(gbufferNormal, true, clear);
|
builder.write_color(gbufferNormal, true, clear);
|
||||||
builder.write_color(gbufferAlbedo, true, clear);
|
builder.write_color(gbufferAlbedo, true, clear);
|
||||||
|
VkClearValue clearID{};
|
||||||
|
clearID.color.uint32[0] = 0u;
|
||||||
|
builder.write_color(idHandle, true, clearID);
|
||||||
|
|
||||||
// Reverse-Z: clear depth to 0.0
|
// Reverse-Z: clear depth to 0.0
|
||||||
VkClearValue depthClear{};
|
VkClearValue depthClear{};
|
||||||
@@ -118,11 +123,11 @@ void GeometryPass::register_graph(RenderGraph *graph,
|
|||||||
builder.read_buffer(b, RGBufferUsage::StorageRead, 0, "geom.vertex");
|
builder.read_buffer(b, RGBufferUsage::StorageRead, 0, "geom.vertex");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[this, gbufferPosition, gbufferNormal, gbufferAlbedo, depthHandle](VkCommandBuffer cmd,
|
[this, gbufferPosition, gbufferNormal, gbufferAlbedo, idHandle, depthHandle](VkCommandBuffer cmd,
|
||||||
const RGPassResources &res,
|
const RGPassResources &res,
|
||||||
EngineContext *ctx)
|
EngineContext *ctx)
|
||||||
{
|
{
|
||||||
draw_geometry(cmd, ctx, res, gbufferPosition, gbufferNormal, gbufferAlbedo, depthHandle);
|
draw_geometry(cmd, ctx, res, gbufferPosition, gbufferNormal, gbufferAlbedo, idHandle, depthHandle);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +137,7 @@ void GeometryPass::draw_geometry(VkCommandBuffer cmd,
|
|||||||
RGImageHandle gbufferPosition,
|
RGImageHandle gbufferPosition,
|
||||||
RGImageHandle gbufferNormal,
|
RGImageHandle gbufferNormal,
|
||||||
RGImageHandle gbufferAlbedo,
|
RGImageHandle gbufferAlbedo,
|
||||||
|
RGImageHandle /*idHandle*/,
|
||||||
RGImageHandle depthHandle) const
|
RGImageHandle depthHandle) const
|
||||||
{
|
{
|
||||||
EngineContext *ctxLocal = context ? context : _context;
|
EngineContext *ctxLocal = context ? context : _context;
|
||||||
@@ -270,6 +276,7 @@ void GeometryPass::draw_geometry(VkCommandBuffer cmd,
|
|||||||
GPUDrawPushConstants push_constants{};
|
GPUDrawPushConstants push_constants{};
|
||||||
push_constants.worldMatrix = r.transform;
|
push_constants.worldMatrix = r.transform;
|
||||||
push_constants.vertexBuffer = r.vertexBufferAddress;
|
push_constants.vertexBuffer = r.vertexBufferAddress;
|
||||||
|
push_constants.objectID = r.objectID;
|
||||||
|
|
||||||
vkCmdPushConstants(cmd, r.material->pipeline->layout, VK_SHADER_STAGE_VERTEX_BIT, 0,
|
vkCmdPushConstants(cmd, r.material->pipeline->layout, VK_SHADER_STAGE_VERTEX_BIT, 0,
|
||||||
sizeof(GPUDrawPushConstants), &push_constants);
|
sizeof(GPUDrawPushConstants), &push_constants);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public:
|
|||||||
RGImageHandle gbufferPosition,
|
RGImageHandle gbufferPosition,
|
||||||
RGImageHandle gbufferNormal,
|
RGImageHandle gbufferNormal,
|
||||||
RGImageHandle gbufferAlbedo,
|
RGImageHandle gbufferAlbedo,
|
||||||
|
RGImageHandle idHandle,
|
||||||
RGImageHandle depthHandle);
|
RGImageHandle depthHandle);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -29,5 +30,6 @@ private:
|
|||||||
RGImageHandle gbufferPosition,
|
RGImageHandle gbufferPosition,
|
||||||
RGImageHandle gbufferNormal,
|
RGImageHandle gbufferNormal,
|
||||||
RGImageHandle gbufferAlbedo,
|
RGImageHandle gbufferAlbedo,
|
||||||
|
RGImageHandle idHandle,
|
||||||
RGImageHandle depthHandle) const;
|
RGImageHandle depthHandle) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ void ShadowPass::init(EngineContext *context)
|
|||||||
// Keep push constants matching current shader layout for now
|
// Keep push constants matching current shader layout for now
|
||||||
VkPushConstantRange pc{};
|
VkPushConstantRange pc{};
|
||||||
pc.offset = 0;
|
pc.offset = 0;
|
||||||
// Push constants layout in shadow.vert is mat4 + device address + uint, rounded to 16 bytes
|
// Push constants layout in shadow.vert is GPUDrawPushConstants + cascade index, rounded to 16 bytes
|
||||||
const uint32_t pcRaw = static_cast<uint32_t>(sizeof(GPUDrawPushConstants) + sizeof(uint32_t));
|
const uint32_t pcRaw = static_cast<uint32_t>(sizeof(GPUDrawPushConstants) + sizeof(uint32_t));
|
||||||
const uint32_t pcAligned = (pcRaw + 15u) & ~15u; // 16-byte alignment to match std430 expectations
|
const uint32_t pcAligned = (pcRaw + 15u) & ~15u; // 16-byte alignment to match std430 expectations
|
||||||
pc.size = pcAligned;
|
pc.size = pcAligned;
|
||||||
@@ -197,6 +197,7 @@ void ShadowPass::draw_shadow(VkCommandBuffer cmd,
|
|||||||
ShadowPC spc{};
|
ShadowPC spc{};
|
||||||
spc.draw.worldMatrix = r.transform;
|
spc.draw.worldMatrix = r.transform;
|
||||||
spc.draw.vertexBuffer = r.vertexBufferAddress;
|
spc.draw.vertexBuffer = r.vertexBufferAddress;
|
||||||
|
spc.draw.objectID = r.objectID;
|
||||||
spc.cascadeIndex = cascadeIndex;
|
spc.cascadeIndex = cascadeIndex;
|
||||||
vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(ShadowPC), &spc);
|
vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(ShadowPC), &spc);
|
||||||
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ void TransparentPass::draw_transparent(VkCommandBuffer cmd,
|
|||||||
GPUDrawPushConstants push{};
|
GPUDrawPushConstants push{};
|
||||||
push.worldMatrix = r.transform;
|
push.worldMatrix = r.transform;
|
||||||
push.vertexBuffer = r.vertexBufferAddress;
|
push.vertexBuffer = r.vertexBufferAddress;
|
||||||
|
push.objectID = r.objectID;
|
||||||
vkCmdPushConstants(cmd, r.material->pipeline->layout, VK_SHADER_STAGE_VERTEX_BIT, 0,
|
vkCmdPushConstants(cmd, r.material->pipeline->layout, VK_SHADER_STAGE_VERTEX_BIT, 0,
|
||||||
sizeof(GPUDrawPushConstants), &push);
|
sizeof(GPUDrawPushConstants), &push);
|
||||||
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
||||||
|
|||||||
@@ -574,17 +574,31 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
newSurface.material = materials[0];
|
newSurface.material = materials[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 minpos = vertices[initial_vtx].position;
|
// Compute per-surface bounds using only the indices referenced by this primitive.
|
||||||
glm::vec3 maxpos = vertices[initial_vtx].position;
|
if (newSurface.count > 0)
|
||||||
for (int i = initial_vtx; i < vertices.size(); i++)
|
|
||||||
{
|
{
|
||||||
minpos = glm::min(minpos, vertices[i].position);
|
uint32_t firstIndex = newSurface.startIndex;
|
||||||
maxpos = glm::max(maxpos, vertices[i].position);
|
uint32_t lastIndex = newSurface.startIndex + newSurface.count;
|
||||||
|
uint32_t baseVertex = indices[firstIndex];
|
||||||
|
glm::vec3 minpos = vertices[baseVertex].position;
|
||||||
|
glm::vec3 maxpos = vertices[baseVertex].position;
|
||||||
|
for (uint32_t i = firstIndex + 1; i < lastIndex; i++)
|
||||||
|
{
|
||||||
|
uint32_t vi = indices[i];
|
||||||
|
const glm::vec3 &p = vertices[vi].position;
|
||||||
|
minpos = glm::min(minpos, p);
|
||||||
|
maxpos = glm::max(maxpos, p);
|
||||||
|
}
|
||||||
|
newSurface.bounds.origin = (maxpos + minpos) / 2.f;
|
||||||
|
newSurface.bounds.extents = (maxpos - minpos) / 2.f;
|
||||||
|
newSurface.bounds.sphereRadius = glm::length(newSurface.bounds.extents);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newSurface.bounds.origin = glm::vec3(0.0f);
|
||||||
|
newSurface.bounds.extents = glm::vec3(0.5f);
|
||||||
|
newSurface.bounds.sphereRadius = glm::length(newSurface.bounds.extents);
|
||||||
}
|
}
|
||||||
|
|
||||||
newSurface.bounds.origin = (maxpos + minpos) / 2.f;
|
|
||||||
newSurface.bounds.extents = (maxpos - minpos) / 2.f;
|
|
||||||
newSurface.bounds.sphereRadius = glm::length(newSurface.bounds.extents);
|
|
||||||
newmesh->surfaces.push_back(newSurface);
|
newmesh->surfaces.push_back(newSurface);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,8 +630,10 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
// find if the node has a mesh, and if it does hook it to the mesh pointer and allocate it with the meshnode class
|
// find if the node has a mesh, and if it does hook it to the mesh pointer and allocate it with the meshnode class
|
||||||
if (node.meshIndex.has_value())
|
if (node.meshIndex.has_value())
|
||||||
{
|
{
|
||||||
newNode = std::make_shared<MeshNode>();
|
auto meshNode = std::make_shared<MeshNode>();
|
||||||
static_cast<MeshNode *>(newNode.get())->mesh = meshes[*node.meshIndex];
|
meshNode->mesh = meshes[*node.meshIndex];
|
||||||
|
meshNode->scene = &file;
|
||||||
|
newNode = meshNode;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ struct LoadedGLTF : public IRenderable
|
|||||||
float animationTime = 0.f;
|
float animationTime = 0.f;
|
||||||
bool animationLoop = true;
|
bool animationLoop = true;
|
||||||
|
|
||||||
|
// Optional debug name (e.g., key used when loaded into SceneManager)
|
||||||
|
std::string debugName;
|
||||||
|
|
||||||
// Animation helpers
|
// Animation helpers
|
||||||
void updateAnimation(float dt);
|
void updateAnimation(float dt);
|
||||||
void refreshAllTransforms();
|
void refreshAllTransforms();
|
||||||
|
|||||||
@@ -9,12 +9,187 @@
|
|||||||
#include "core/config.h"
|
#include "core/config.h"
|
||||||
#include "glm/gtx/transform.hpp"
|
#include "glm/gtx/transform.hpp"
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
#include "glm/gtx/norm.inl"
|
#include "glm/gtx/norm.inl"
|
||||||
#include "glm/gtx/compatibility.hpp"
|
#include "glm/gtx/compatibility.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
#include <cmath>
|
||||||
#include "core/config.h"
|
#include "core/config.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Quick conservative ray / bounding-sphere test in world space.
|
||||||
|
// Returns false when the ray misses the sphere; on hit, outT is the
|
||||||
|
// closest positive intersection distance along the ray direction.
|
||||||
|
bool intersect_ray_sphere(const glm::vec3 &rayOrigin,
|
||||||
|
const glm::vec3 &rayDir,
|
||||||
|
const Bounds &bounds,
|
||||||
|
const glm::mat4 &worldTransform,
|
||||||
|
float &outT)
|
||||||
|
{
|
||||||
|
// Sphere center is bounds.origin transformed to world.
|
||||||
|
glm::vec3 centerWorld = glm::vec3(worldTransform * glm::vec4(bounds.origin, 1.0f));
|
||||||
|
|
||||||
|
// Approximate world-space radius by scaling with the maximum axis scale.
|
||||||
|
glm::vec3 sx = glm::vec3(worldTransform[0]);
|
||||||
|
glm::vec3 sy = glm::vec3(worldTransform[1]);
|
||||||
|
glm::vec3 sz = glm::vec3(worldTransform[2]);
|
||||||
|
float maxScale = std::max({glm::length(sx), glm::length(sy), glm::length(sz)});
|
||||||
|
float radiusWorld = bounds.sphereRadius * maxScale;
|
||||||
|
if (radiusWorld <= 0.0f)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 oc = rayOrigin - centerWorld;
|
||||||
|
float b = glm::dot(oc, rayDir);
|
||||||
|
float c = glm::dot(oc, oc) - radiusWorld * radiusWorld;
|
||||||
|
float disc = b * b - c;
|
||||||
|
if (disc < 0.0f)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
float s = std::sqrt(disc);
|
||||||
|
float t0 = -b - s;
|
||||||
|
float t1 = -b + s;
|
||||||
|
float t = t0 >= 0.0f ? t0 : t1;
|
||||||
|
if (t < 0.0f)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
outT = t;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ray / oriented-bounds intersection in world space using object-local AABB.
|
||||||
|
// Uses a quick sphere test first; on success refines with OBB slabs.
|
||||||
|
// Returns true when hit; outWorldHit is the closest hit point in world space.
|
||||||
|
bool intersect_ray_bounds(const glm::vec3 &rayOrigin,
|
||||||
|
const glm::vec3 &rayDir,
|
||||||
|
const Bounds &bounds,
|
||||||
|
const glm::mat4 &worldTransform,
|
||||||
|
glm::vec3 &outWorldHit)
|
||||||
|
{
|
||||||
|
if (glm::length2(rayDir) < 1e-8f)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Early reject using bounding sphere in world space.
|
||||||
|
float sphereT = 0.0f;
|
||||||
|
if (!intersect_ray_sphere(rayOrigin, rayDir, bounds, worldTransform, sphereT))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform ray into local space of the bounds for precise box test.
|
||||||
|
glm::mat4 invM = glm::inverse(worldTransform);
|
||||||
|
glm::vec3 localOrigin = glm::vec3(invM * glm::vec4(rayOrigin, 1.0f));
|
||||||
|
glm::vec3 localDir = glm::vec3(invM * glm::vec4(rayDir, 0.0f));
|
||||||
|
|
||||||
|
if (glm::length2(localDir) < 1e-8f)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
localDir = glm::normalize(localDir);
|
||||||
|
|
||||||
|
glm::vec3 minB = bounds.origin - bounds.extents;
|
||||||
|
glm::vec3 maxB = bounds.origin + bounds.extents;
|
||||||
|
|
||||||
|
float tMin = 0.0f;
|
||||||
|
float tMax = std::numeric_limits<float>::max();
|
||||||
|
|
||||||
|
for (int axis = 0; axis < 3; ++axis)
|
||||||
|
{
|
||||||
|
float o = localOrigin[axis];
|
||||||
|
float d = localDir[axis];
|
||||||
|
if (std::abs(d) < 1e-8f)
|
||||||
|
{
|
||||||
|
// Ray parallel to slab: must be inside to intersect.
|
||||||
|
if (o < minB[axis] || o > maxB[axis])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float invD = 1.0f / d;
|
||||||
|
float t1 = (minB[axis] - o) * invD;
|
||||||
|
float t2 = (maxB[axis] - o) * invD;
|
||||||
|
if (t1 > t2)
|
||||||
|
{
|
||||||
|
std::swap(t1, t2);
|
||||||
|
}
|
||||||
|
|
||||||
|
tMin = std::max(tMin, t1);
|
||||||
|
tMax = std::min(tMax, t2);
|
||||||
|
|
||||||
|
if (tMax < tMin)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tMax < 0.0f)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float tHit = (tMin >= 0.0f) ? tMin : tMax;
|
||||||
|
glm::vec3 localHit = localOrigin + tHit * localDir;
|
||||||
|
glm::vec3 worldHit = glm::vec3(worldTransform * glm::vec4(localHit, 1.0f));
|
||||||
|
|
||||||
|
if (glm::dot(worldHit - rayOrigin, rayDir) <= 0.0f)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outWorldHit = worldHit;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test whether the clip-space box corners of an object intersect a 2D NDC rectangle.
|
||||||
|
// ndcMin/ndcMax are in [-1,1]x[-1,1]. Returns true if any visible corner projects inside.
|
||||||
|
bool box_overlaps_ndc_rect(const RenderObject &obj,
|
||||||
|
const glm::mat4 &viewproj,
|
||||||
|
const glm::vec2 &ndcMin,
|
||||||
|
const glm::vec2 &ndcMax)
|
||||||
|
{
|
||||||
|
const glm::vec3 o = obj.bounds.origin;
|
||||||
|
const glm::vec3 e = obj.bounds.extents;
|
||||||
|
const glm::mat4 m = viewproj * obj.transform; // world -> clip
|
||||||
|
|
||||||
|
const std::array<glm::vec3, 8> corners{
|
||||||
|
glm::vec3{+1, +1, +1}, glm::vec3{+1, +1, -1}, glm::vec3{+1, -1, +1}, glm::vec3{+1, -1, -1},
|
||||||
|
glm::vec3{-1, +1, +1}, glm::vec3{-1, +1, -1}, glm::vec3{-1, -1, +1}, glm::vec3{-1, -1, -1},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const glm::vec3 &c : corners)
|
||||||
|
{
|
||||||
|
glm::vec3 pLocal = o + c * e;
|
||||||
|
glm::vec4 clip = m * glm::vec4(pLocal, 1.f);
|
||||||
|
if (clip.w <= 0.0f)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float x = clip.x / clip.w;
|
||||||
|
float y = clip.y / clip.w;
|
||||||
|
float z = clip.z / clip.w; // Vulkan Z0: 0..1
|
||||||
|
if (z < 0.0f || z > 1.0f)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (x >= ndcMin.x && x <= ndcMax.x &&
|
||||||
|
y >= ndcMin.y && y <= ndcMax.y)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void SceneManager::init(EngineContext *context)
|
void SceneManager::init(EngineContext *context)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
@@ -35,6 +210,7 @@ void SceneManager::update_scene()
|
|||||||
|
|
||||||
mainDrawContext.OpaqueSurfaces.clear();
|
mainDrawContext.OpaqueSurfaces.clear();
|
||||||
mainDrawContext.TransparentSurfaces.clear();
|
mainDrawContext.TransparentSurfaces.clear();
|
||||||
|
mainDrawContext.nextID = 1;
|
||||||
|
|
||||||
mainCamera.update();
|
mainCamera.update();
|
||||||
|
|
||||||
@@ -102,6 +278,7 @@ void SceneManager::update_scene()
|
|||||||
{
|
{
|
||||||
const MeshInstance &inst = kv.second;
|
const MeshInstance &inst = kv.second;
|
||||||
if (!inst.mesh || inst.mesh->surfaces.empty()) continue;
|
if (!inst.mesh || inst.mesh->surfaces.empty()) continue;
|
||||||
|
uint32_t surfaceIndex = 0;
|
||||||
for (const auto &surf: inst.mesh->surfaces)
|
for (const auto &surf: inst.mesh->surfaces)
|
||||||
{
|
{
|
||||||
RenderObject obj{};
|
RenderObject obj{};
|
||||||
@@ -113,6 +290,9 @@ void SceneManager::update_scene()
|
|||||||
obj.material = &surf.material->data;
|
obj.material = &surf.material->data;
|
||||||
obj.bounds = surf.bounds;
|
obj.bounds = surf.bounds;
|
||||||
obj.transform = inst.transform;
|
obj.transform = inst.transform;
|
||||||
|
obj.sourceMesh = inst.mesh.get();
|
||||||
|
obj.surfaceIndex = surfaceIndex++;
|
||||||
|
obj.objectID = mainDrawContext.nextID++;
|
||||||
if (obj.material->passType == MaterialPass::Transparent)
|
if (obj.material->passType == MaterialPass::Transparent)
|
||||||
{
|
{
|
||||||
mainDrawContext.TransparentSurfaces.push_back(obj);
|
mainDrawContext.TransparentSurfaces.push_back(obj);
|
||||||
@@ -229,8 +409,165 @@ void SceneManager::update_scene()
|
|||||||
stats.scene_update_time = elapsed.count() / 1000.f;
|
stats.scene_update_time = elapsed.count() / 1000.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SceneManager::pick(const glm::vec2 &mousePosPixels, RenderObject &outObject, glm::vec3 &outWorldPos)
|
||||||
|
{
|
||||||
|
if (_context == nullptr)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SwapchainManager *swapchain = _context->getSwapchain();
|
||||||
|
if (swapchain == nullptr)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkExtent2D extent = swapchain->windowExtent();
|
||||||
|
if (extent.width == 0 || extent.height == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float width = static_cast<float>(extent.width);
|
||||||
|
float height = static_cast<float>(extent.height);
|
||||||
|
|
||||||
|
// Convert from window coordinates (top-left origin) to NDC in [-1, 1].
|
||||||
|
float ndcX = (2.0f * mousePosPixels.x / width) - 1.0f;
|
||||||
|
float ndcY = 1.0f - (2.0f * mousePosPixels.y / height);
|
||||||
|
|
||||||
|
float fovRad = glm::radians(mainCamera.fovDegrees);
|
||||||
|
float tanHalfFov = std::tan(fovRad * 0.5f);
|
||||||
|
float aspect = width / height;
|
||||||
|
|
||||||
|
// Build ray in camera space using -Z forward convention.
|
||||||
|
glm::vec3 dirCamera(ndcX * aspect * tanHalfFov,
|
||||||
|
ndcY * tanHalfFov,
|
||||||
|
-1.0f);
|
||||||
|
dirCamera = glm::normalize(dirCamera);
|
||||||
|
|
||||||
|
glm::vec3 rayOrigin = mainCamera.position;
|
||||||
|
glm::mat4 camRotation = mainCamera.getRotationMatrix();
|
||||||
|
glm::vec3 rayDir = glm::normalize(glm::vec3(camRotation * glm::vec4(dirCamera, 0.0f)));
|
||||||
|
|
||||||
|
bool anyHit = false;
|
||||||
|
float bestDist2 = std::numeric_limits<float>::max();
|
||||||
|
glm::vec3 bestHitPos{};
|
||||||
|
|
||||||
|
auto testList = [&](const std::vector<RenderObject> &list)
|
||||||
|
{
|
||||||
|
for (const RenderObject &obj: list)
|
||||||
|
{
|
||||||
|
glm::vec3 hitPos{};
|
||||||
|
if (!intersect_ray_bounds(rayOrigin, rayDir, obj.bounds, obj.transform, hitPos))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float d2 = glm::length2(hitPos - rayOrigin);
|
||||||
|
if (d2 < bestDist2)
|
||||||
|
{
|
||||||
|
bestDist2 = d2;
|
||||||
|
bestHitPos = hitPos;
|
||||||
|
outObject = obj;
|
||||||
|
anyHit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
testList(mainDrawContext.OpaqueSurfaces);
|
||||||
|
testList(mainDrawContext.TransparentSurfaces);
|
||||||
|
|
||||||
|
if (anyHit)
|
||||||
|
{
|
||||||
|
outWorldPos = bestHitPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneManager::resolveObjectID(uint32_t id, RenderObject &outObject) const
|
||||||
|
{
|
||||||
|
if (id == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto findIn = [&](const std::vector<RenderObject> &list) -> bool
|
||||||
|
{
|
||||||
|
for (const RenderObject &obj : list)
|
||||||
|
{
|
||||||
|
if (obj.objectID == id)
|
||||||
|
{
|
||||||
|
outObject = obj;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (findIn(mainDrawContext.OpaqueSurfaces))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (findIn(mainDrawContext.TransparentSurfaces))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneManager::selectRect(const glm::vec2 &p0, const glm::vec2 &p1, std::vector<RenderObject> &outObjects) const
|
||||||
|
{
|
||||||
|
if (!_context || !_context->getSwapchain())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkExtent2D extent = _context->getSwapchain()->windowExtent();
|
||||||
|
if (extent.width == 0 || extent.height == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float width = static_cast<float>(extent.width);
|
||||||
|
float height = static_cast<float>(extent.height);
|
||||||
|
|
||||||
|
// Convert from window coordinates (top-left origin) to NDC in [-1, 1].
|
||||||
|
auto toNdc = [&](const glm::vec2 &p) -> glm::vec2
|
||||||
|
{
|
||||||
|
float ndcX = (2.0f * p.x / width) - 1.0f;
|
||||||
|
float ndcY = 1.0f - (2.0f * p.y / height);
|
||||||
|
return glm::vec2{ndcX, ndcY};
|
||||||
|
};
|
||||||
|
|
||||||
|
glm::vec2 ndc0 = toNdc(p0);
|
||||||
|
glm::vec2 ndc1 = toNdc(p1);
|
||||||
|
glm::vec2 ndcMin = glm::min(ndc0, ndc1);
|
||||||
|
glm::vec2 ndcMax = glm::max(ndc0, ndc1);
|
||||||
|
|
||||||
|
const glm::mat4 vp = sceneData.viewproj;
|
||||||
|
|
||||||
|
auto testList = [&](const std::vector<RenderObject> &list)
|
||||||
|
{
|
||||||
|
for (const RenderObject &obj : list)
|
||||||
|
{
|
||||||
|
if (box_overlaps_ndc_rect(obj, vp, ndcMin, ndcMax))
|
||||||
|
{
|
||||||
|
outObjects.push_back(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
testList(mainDrawContext.OpaqueSurfaces);
|
||||||
|
testList(mainDrawContext.TransparentSurfaces);
|
||||||
|
}
|
||||||
|
|
||||||
void SceneManager::loadScene(const std::string &name, std::shared_ptr<LoadedGLTF> scene)
|
void SceneManager::loadScene(const std::string &name, std::shared_ptr<LoadedGLTF> scene)
|
||||||
{
|
{
|
||||||
|
if (scene)
|
||||||
|
{
|
||||||
|
scene->debugName = name;
|
||||||
|
}
|
||||||
loadedScenes[name] = std::move(scene);
|
loadedScenes[name] = std::move(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
#include <scene/camera.h>
|
#include <scene/camera.h>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <glm/vec2.hpp>
|
||||||
|
|
||||||
#include "scene/vk_loader.h"
|
#include "scene/vk_loader.h"
|
||||||
class EngineContext;
|
class EngineContext;
|
||||||
|
|
||||||
struct RenderObject
|
struct RenderObject
|
||||||
{
|
{
|
||||||
|
// Geometry and material binding
|
||||||
uint32_t indexCount;
|
uint32_t indexCount;
|
||||||
uint32_t firstIndex;
|
uint32_t firstIndex;
|
||||||
VkBuffer indexBuffer;
|
VkBuffer indexBuffer;
|
||||||
@@ -19,12 +21,22 @@ struct RenderObject
|
|||||||
|
|
||||||
glm::mat4 transform;
|
glm::mat4 transform;
|
||||||
VkDeviceAddress vertexBufferAddress;
|
VkDeviceAddress vertexBufferAddress;
|
||||||
|
|
||||||
|
// Optional debug/source information (may be null/unused for some objects).
|
||||||
|
MeshAsset *sourceMesh = nullptr;
|
||||||
|
uint32_t surfaceIndex = 0;
|
||||||
|
// Unique per-draw identifier for ID-buffer picking (0 = none).
|
||||||
|
uint32_t objectID = 0;
|
||||||
|
// Optional owning glTF scene for this draw (null for procedural/dynamic meshes).
|
||||||
|
LoadedGLTF *sourceScene = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawContext
|
struct DrawContext
|
||||||
{
|
{
|
||||||
std::vector<RenderObject> OpaqueSurfaces;
|
std::vector<RenderObject> OpaqueSurfaces;
|
||||||
std::vector<RenderObject> TransparentSurfaces;
|
std::vector<RenderObject> TransparentSurfaces;
|
||||||
|
// Monotonic counter used to assign stable per-frame object IDs.
|
||||||
|
uint32_t nextID = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SceneManager
|
class SceneManager
|
||||||
@@ -37,6 +49,20 @@ public:
|
|||||||
void update_scene();
|
void update_scene();
|
||||||
|
|
||||||
Camera &getMainCamera() { return mainCamera; }
|
Camera &getMainCamera() { return mainCamera; }
|
||||||
|
|
||||||
|
// Ray-pick against current DrawContext using per-surface Bounds.
|
||||||
|
// mousePosPixels is in window coordinates (SDL), origin at top-left.
|
||||||
|
// Returns true if any object was hit, filling outObject and outWorldPos.
|
||||||
|
bool pick(const glm::vec2 &mousePosPixels, RenderObject &outObject, glm::vec3 &outWorldPos);
|
||||||
|
|
||||||
|
// Resolve an object ID (from ID buffer) back to the RenderObject for
|
||||||
|
// the most recently built DrawContext. Returns false if not found or id==0.
|
||||||
|
bool resolveObjectID(uint32_t id, RenderObject &outObject) const;
|
||||||
|
|
||||||
|
// Select all objects whose projected bounds intersect the given screen-space
|
||||||
|
// rectangle (window coordinates, origin top-left). Results are appended to outObjects.
|
||||||
|
void selectRect(const glm::vec2 &p0, const glm::vec2 &p1, std::vector<RenderObject> &outObjects) const;
|
||||||
|
|
||||||
const GPUSceneData &getSceneData() const { return sceneData; }
|
const GPUSceneData &getSceneData() const { return sceneData; }
|
||||||
DrawContext &getMainDrawContext() { return mainDrawContext; }
|
DrawContext &getMainDrawContext() { return mainDrawContext; }
|
||||||
|
|
||||||
|
|||||||
20
third_party/CMakeLists.txt
vendored
20
third_party/CMakeLists.txt
vendored
@@ -49,6 +49,26 @@ target_sources(imgui PRIVATE
|
|||||||
|
|
||||||
target_link_libraries(imgui PUBLIC Vulkan::Vulkan SDL2::SDL2)
|
target_link_libraries(imgui PUBLIC Vulkan::Vulkan SDL2::SDL2)
|
||||||
|
|
||||||
|
add_library(ImGuizmo STATIC
|
||||||
|
ImGuizmo/ImGuizmo.cpp
|
||||||
|
ImGuizmo/ImSequencer.cpp
|
||||||
|
ImGuizmo/ImCurveEdit.cpp
|
||||||
|
ImGuizmo/ImGradient.cpp
|
||||||
|
ImGuizmo/GraphEditor.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(ImGuizmo PUBLIC
|
||||||
|
ImGuizmo
|
||||||
|
imgui
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(ImGuizmo
|
||||||
|
PRIVATE
|
||||||
|
IMGUI_DEFINE_MATH_OPERATORS
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(ImGuizmo PUBLIC imgui)
|
||||||
|
|
||||||
target_include_directories(stb_image INTERFACE stb_image)
|
target_include_directories(stb_image INTERFACE stb_image)
|
||||||
|
|
||||||
# MikkTSpace (optional)
|
# MikkTSpace (optional)
|
||||||
|
|||||||
11
third_party/ImGuizmo/.editorconfig
vendored
Normal file
11
third_party/ImGuizmo/.editorconfig
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 3
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
1111
third_party/ImGuizmo/GraphEditor.cpp
vendored
Normal file
1111
third_party/ImGuizmo/GraphEditor.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
151
third_party/ImGuizmo/GraphEditor.h
vendored
Normal file
151
third_party/ImGuizmo/GraphEditor.h
vendored
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
// https://github.com/CedricGuillemet/ImGuizmo
|
||||||
|
// v1.92.5 WIP
|
||||||
|
//
|
||||||
|
// The MIT License(MIT)
|
||||||
|
//
|
||||||
|
// Copyright(c) 2016-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 <vector>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "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;
|
||||||
|
|
||||||
|
virtual ~Delegate() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
void Show(Delegate& delegate, const Options& options, ViewState& viewState, bool enabled, FitOnScreen* fit = nullptr);
|
||||||
|
void GraphEditorClear();
|
||||||
|
|
||||||
|
bool EditOptions(Options& options);
|
||||||
|
|
||||||
|
} // namespace
|
||||||
458
third_party/ImGuizmo/ImCurveEdit.cpp
vendored
Normal file
458
third_party/ImGuizmo/ImCurveEdit.cpp
vendored
Normal file
@@ -0,0 +1,458 @@
|
|||||||
|
// https://github.com/CedricGuillemet/ImGuizmo
|
||||||
|
// v1.92.5 WIP
|
||||||
|
//
|
||||||
|
// The MIT License(MIT)
|
||||||
|
//
|
||||||
|
// Copyright(c) 2016-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 "imgui.h"
|
||||||
|
#include "imgui_internal.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||||
|
#include <malloc.h>
|
||||||
|
#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<EditPoint>* selectedPoints)
|
||||||
|
{
|
||||||
|
static bool selectingQuad = false;
|
||||||
|
static ImVec2 quadSelection;
|
||||||
|
static int overCurve = -1;
|
||||||
|
static int movingCurve = -1;
|
||||||
|
static bool scrollingV = false;
|
||||||
|
static std::set<EditPoint> 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::BeginChild(id, size, ImGuiChildFlags_FrameStyle);
|
||||||
|
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<ImVec2> 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::EndChild();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
ImGui::PopStyleColor(1);
|
||||||
|
|
||||||
|
if (selectedPoints)
|
||||||
|
{
|
||||||
|
selectedPoints->resize(int(selection.size()));
|
||||||
|
int index = 0;
|
||||||
|
for (auto& point : selection)
|
||||||
|
(*selectedPoints)[index++] = point;
|
||||||
|
}
|
||||||
|
_freea(curvesIndex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
82
third_party/ImGuizmo/ImCurveEdit.h
vendored
Normal file
82
third_party/ImGuizmo/ImCurveEdit.h
vendored
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// https://github.com/CedricGuillemet/ImGuizmo
|
||||||
|
// v1.92.5 WIP
|
||||||
|
//
|
||||||
|
// The MIT License(MIT)
|
||||||
|
//
|
||||||
|
// Copyright(c) 2016-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 <stdint.h>
|
||||||
|
#include "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() {}
|
||||||
|
|
||||||
|
virtual ~Delegate() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
int Edit(Delegate& delegate, const ImVec2& size, unsigned int id, const ImRect* clippingRect = NULL, ImVector<EditPoint>* selectedPoints = NULL);
|
||||||
|
}
|
||||||
116
third_party/ImGuizmo/ImGradient.cpp
vendored
Normal file
116
third_party/ImGuizmo/ImGradient.cpp
vendored
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
// https://github.com/CedricGuillemet/ImGuizmo
|
||||||
|
// v1.92.5 WIP
|
||||||
|
//
|
||||||
|
// The MIT License(MIT)
|
||||||
|
//
|
||||||
|
// Copyright(c) 2016-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 "imgui.h"
|
||||||
|
#include "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::BeginChild(137, size, ImGuiChildFlags_FrameStyle);
|
||||||
|
|
||||||
|
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::EndChild();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
|
selection = currentSelection;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
third_party/ImGuizmo/ImGradient.h
vendored
Normal file
45
third_party/ImGuizmo/ImGradient.h
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// https://github.com/CedricGuillemet/ImGuizmo
|
||||||
|
// v1.92.5 WIP
|
||||||
|
//
|
||||||
|
// The MIT License(MIT)
|
||||||
|
//
|
||||||
|
// Copyright(c) 2016-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 <cstddef>
|
||||||
|
|
||||||
|
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;
|
||||||
|
virtual ~Delegate() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool Edit(Delegate& delegate, const ImVec2& size, int& selection);
|
||||||
|
}
|
||||||
3164
third_party/ImGuizmo/ImGuizmo.cpp
vendored
Normal file
3164
third_party/ImGuizmo/ImGuizmo.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
306
third_party/ImGuizmo/ImGuizmo.h
vendored
Normal file
306
third_party/ImGuizmo/ImGuizmo.h
vendored
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
// https://github.com/CedricGuillemet/ImGuizmo
|
||||||
|
// v1.92.5 WIP
|
||||||
|
//
|
||||||
|
// The MIT License(MIT)
|
||||||
|
//
|
||||||
|
// Copyright(c) 2016-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
|
||||||
|
|
||||||
|
struct ImGuiWindow;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// return true if the view gizmo is in moving state
|
||||||
|
IMGUI_API bool IsUsingViewManipulate();
|
||||||
|
// only check if your mouse is over the view manipulator - no matter whether it's active or not
|
||||||
|
IMGUI_API bool IsViewManipulateHovered();
|
||||||
|
|
||||||
|
// return true if any gizmo is in moving state
|
||||||
|
IMGUI_API bool IsUsingAny();
|
||||||
|
|
||||||
|
// 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<OPERATION>(static_cast<int>(lhs) | static_cast<int>(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 SetAlternativeWindow(ImGuiWindow* window);
|
||||||
|
|
||||||
|
[[deprecated("Use PushID/PopID instead.")]]
|
||||||
|
IMGUI_API void SetID(int id);
|
||||||
|
|
||||||
|
// ID stack/scopes
|
||||||
|
// Read the FAQ (docs/FAQ.md or http://dearimgui.org/faq) for more details about how ID are handled in dear imgui.
|
||||||
|
// - Those questions are answered and impacted by understanding of the ID stack system:
|
||||||
|
// - "Q: Why is my widget not reacting when I click on it?"
|
||||||
|
// - "Q: How can I have widgets with an empty label?"
|
||||||
|
// - "Q: How can I have multiple widgets with the same label?"
|
||||||
|
// - Short version: ID are hashes of the entire ID stack. If you are creating widgets in a loop you most likely
|
||||||
|
// want to push a unique identifier (e.g. object pointer, loop index) to uniquely differentiate them.
|
||||||
|
// - You can also use the "Label##foobar" syntax within widget label to distinguish them from each others.
|
||||||
|
// - In this header file we use the "label"/"name" terminology to denote a string that will be displayed + used as an ID,
|
||||||
|
// whereas "str_id" denote a string that is only used as an ID and not normally displayed.
|
||||||
|
IMGUI_API void PushID(const char* str_id); // push string into the ID stack (will hash string).
|
||||||
|
IMGUI_API void PushID(const char* str_id_begin, const char* str_id_end); // push string into the ID stack (will hash string).
|
||||||
|
IMGUI_API void PushID(const void* ptr_id); // push pointer into the ID stack (will hash pointer).
|
||||||
|
IMGUI_API void PushID(int int_id); // push integer into the ID stack (will hash integer).
|
||||||
|
IMGUI_API void PopID(); // pop from the ID stack.
|
||||||
|
IMGUI_API ImGuiID GetID(const char* str_id); // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself
|
||||||
|
IMGUI_API ImGuiID GetID(const char* str_id_begin, const char* str_id_end);
|
||||||
|
IMGUI_API ImGuiID GetID(const void* ptr_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);
|
||||||
|
|
||||||
|
// Configure the limit where axis are hidden
|
||||||
|
IMGUI_API void SetAxisLimit(float value);
|
||||||
|
// Set an axis mask to permanently hide a given axis (true -> hidden, false -> shown)
|
||||||
|
IMGUI_API void SetAxisMask(bool x, bool y, bool z);
|
||||||
|
// Configure the limit where planes are hiden
|
||||||
|
IMGUI_API void SetPlaneLimit(float value);
|
||||||
|
// from a x,y,z point in space and using Manipulation view/projection matrix, check if mouse is in pixel radius distance of that projected point
|
||||||
|
IMGUI_API bool IsOver(float* position, float pixelRadius);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
695
third_party/ImGuizmo/ImSequencer.cpp
vendored
Normal file
695
third_party/ImGuizmo/ImSequencer.cpp
vendored
Normal file
@@ -0,0 +1,695 @@
|
|||||||
|
// https://github.com/CedricGuillemet/ImGuizmo
|
||||||
|
// v1.92.5 WIP
|
||||||
|
//
|
||||||
|
// The MIT License(MIT)
|
||||||
|
//
|
||||||
|
// Copyright(c) 2016-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 "imgui.h"
|
||||||
|
#include "imgui_internal.h"
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
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<CustomDraw> customDraws;
|
||||||
|
ImVector<CustomDraw> 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::BeginChild(889, childFrameSize, ImGuiChildFlags_FrameStyle);
|
||||||
|
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::EndChild();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
79
third_party/ImGuizmo/ImSequencer.h
vendored
Normal file
79
third_party/ImGuizmo/ImSequencer.h
vendored
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
// https://github.com/CedricGuillemet/ImGuizmo
|
||||||
|
// v1.92.5 WIP
|
||||||
|
//
|
||||||
|
// The MIT License(MIT)
|
||||||
|
//
|
||||||
|
// Copyright(c) 2016-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 <cstddef>
|
||||||
|
|
||||||
|
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*/) {}
|
||||||
|
|
||||||
|
virtual ~SequenceInterface() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// return true if selection is made
|
||||||
|
bool Sequencer(SequenceInterface* sequence, int* currentFrame, bool* expanded, int* selectedEntry, int* firstFrame, int sequenceOptions);
|
||||||
|
|
||||||
|
}
|
||||||
245
third_party/ImGuizmo/ImZoomSlider.h
vendored
Normal file
245
third_party/ImGuizmo/ImZoomSlider.h
vendored
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
// https://github.com/CedricGuillemet/ImGuizmo
|
||||||
|
// v1.92.5 WIP
|
||||||
|
//
|
||||||
|
// The MIT License(MIT)
|
||||||
|
//
|
||||||
|
// Copyright(c) 2016-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<typename T> 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
|
||||||
BIN
third_party/ImGuizmo/Images/nodeeditor.jpg
vendored
Normal file
BIN
third_party/ImGuizmo/Images/nodeeditor.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
21
third_party/ImGuizmo/LICENSE
vendored
Normal file
21
third_party/ImGuizmo/LICENSE
vendored
Normal file
@@ -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.
|
||||||
19
third_party/ImGuizmo/Makefile
vendored
Normal file
19
third_party/ImGuizmo/Makefile
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
CXXFLAGS=-std=c++11
|
||||||
|
CPPFLAGS=-I. -Iexample
|
||||||
|
|
||||||
|
LIB_OBJS = ImGuizmo.o GraphEditor.o ImCurveEdit.o ImGradient.o ImSequencer.o
|
||||||
|
EXAMPLE_OBJS = example/imgui.o example/imgui_draw.o example/imgui_tables.o example/imgui_widgets.o example/main.o
|
||||||
|
|
||||||
|
EXAMPLE_NAME = example.exe
|
||||||
|
LDFLAGS=-mwindows -static-libgcc -static-libstdc++
|
||||||
|
LIBS=-limm32 -lopengl32 -lgdi32
|
||||||
|
|
||||||
|
$(EXAMPLE_NAME): $(LIB_OBJS) $(EXAMPLE_OBJS)
|
||||||
|
$(CXX) $(LDFLAGS) -o $@ $^ $(LIBS)
|
||||||
|
|
||||||
|
example/main.o: CXXFLAGS := -std=c++17
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) $(LIB_OBJS)
|
||||||
|
$(RM) $(EXAMPLE_OBJS)
|
||||||
|
$(RM) $(EXAMPLE_NAME)
|
||||||
192
third_party/ImGuizmo/README.md
vendored
Normal file
192
third_party/ImGuizmo/README.md
vendored
Normal file
@@ -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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### ImGuizmo
|
||||||
|
|
||||||
|
ImGuizmo 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
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
There is now a sample for Win32/OpenGL ! With a binary in bin directory.
|
||||||
|

|
||||||
|
|
||||||
|
### ImSequencer
|
||||||
|
|
||||||
|
A WIP little sequencer used to edit frame start/end for different events in a timeline.
|
||||||
|

|
||||||
|
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.
|
||||||
|

|
||||||
|
|
||||||
|
### 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 :
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```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.
|
||||||
Reference in New Issue
Block a user