Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9826b7bbb | |||
| 7d214821f5 | |||
| 2ddeeed061 | |||
| 038450a1ca | |||
| 0f30e080e3 | |||
| 6b8cb2406e | |||
| c5ca6a7501 | |||
| de94decf64 | |||
| 42fd355c53 | |||
| b95b567b75 | |||
| bb9a5d9428 | |||
| b08bb6b233 |
@@ -79,3 +79,4 @@ imgui.ini
|
|||||||
# OCIO config LUT files — downloaded automatically at CMake configure time
|
# OCIO config LUT files — downloaded automatically at CMake configure time
|
||||||
resources/OpenColorIO-Configs/
|
resources/OpenColorIO-Configs/
|
||||||
resources/hdri/
|
resources/hdri/
|
||||||
|
resources/vdb/
|
||||||
|
|||||||
+39
-1
@@ -173,6 +173,11 @@ endif()
|
|||||||
# Collect source files
|
# Collect source files
|
||||||
file(GLOB_RECURSE CORE_SOURCES "src/core/*.cpp")
|
file(GLOB_RECURSE CORE_SOURCES "src/core/*.cpp")
|
||||||
file(GLOB_RECURSE UI_SOURCES "src/ui/*.cpp")
|
file(GLOB_RECURSE UI_SOURCES "src/ui/*.cpp")
|
||||||
|
# src/ui/NodeEditor is the in-tree vendored imgui-node-editor subset — already
|
||||||
|
# compiled into the imgui_node_editor_impl target by FindImguiNodeEditor.cmake
|
||||||
|
# (with its own required include dirs/defines); excluded here so it isn't
|
||||||
|
# compiled a second time, incorrectly, as part of this target's own sources.
|
||||||
|
list(FILTER UI_SOURCES EXCLUDE REGEX "/src/ui/NodeEditor/")
|
||||||
file(GLOB_RECURSE UTILS_SOURCES "src/utils/*.cpp")
|
file(GLOB_RECURSE UTILS_SOURCES "src/utils/*.cpp")
|
||||||
set(MAIN_SOURCE "src/main.cpp")
|
set(MAIN_SOURCE "src/main.cpp")
|
||||||
|
|
||||||
@@ -606,6 +611,33 @@ foreach(_hdri ${_hdri_files})
|
|||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# VDB: cloud volume for the shader-ball preview (OpenVDB sample model) at
|
||||||
|
# configure time if not present
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
set(_vdb_dir "${CMAKE_SOURCE_DIR}/resources/vdb")
|
||||||
|
file(MAKE_DIRECTORY "${_vdb_dir}")
|
||||||
|
if(NOT EXISTS "${_vdb_dir}/bunny_cloud.vdb")
|
||||||
|
message(STATUS "Downloading OpenVDB sample bunny_cloud.vdb ...")
|
||||||
|
set(_vdb_zip "${CMAKE_BINARY_DIR}/bunny_cloud.zip")
|
||||||
|
file(DOWNLOAD
|
||||||
|
"https://artifacts.aswf.io/io/aswf/openvdb/models/bunny_cloud.vdb/1.0.0/bunny_cloud.vdb-1.0.0.zip"
|
||||||
|
"${_vdb_zip}"
|
||||||
|
STATUS _vdb_dl_status
|
||||||
|
)
|
||||||
|
list(GET _vdb_dl_status 0 _vdb_dl_code)
|
||||||
|
if(_vdb_dl_code EQUAL 0)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E tar xf "${_vdb_zip}"
|
||||||
|
WORKING_DIRECTORY "${_vdb_dir}"
|
||||||
|
)
|
||||||
|
file(REMOVE "${_vdb_zip}")
|
||||||
|
message(STATUS "bunny_cloud.vdb installed to ${_vdb_dir}")
|
||||||
|
else()
|
||||||
|
message(WARNING "OpenVDB sample download failed (${_vdb_dl_status}) — the Cloud Volume preview shape will be unavailable.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Copy resources to build directory
|
# Copy resources to build directory
|
||||||
file(COPY ${CMAKE_SOURCE_DIR}/resources
|
file(COPY ${CMAKE_SOURCE_DIR}/resources
|
||||||
DESTINATION ${CMAKE_BINARY_DIR}
|
DESTINATION ${CMAKE_BINARY_DIR}
|
||||||
@@ -624,7 +656,13 @@ add_custom_command(TARGET UsdLayerManager POST_BUILD
|
|||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
"${CMAKE_SOURCE_DIR}/resources/hdri"
|
"${CMAKE_SOURCE_DIR}/resources/hdri"
|
||||||
"$<TARGET_FILE_DIR:UsdLayerManager>/resources/hdri"
|
"$<TARGET_FILE_DIR:UsdLayerManager>/resources/hdri"
|
||||||
COMMENT "Copying fonts, SVG icons and HDRI presets to build output"
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
"${CMAKE_SOURCE_DIR}/resources/preview"
|
||||||
|
"$<TARGET_FILE_DIR:UsdLayerManager>/resources/preview"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
"${CMAKE_SOURCE_DIR}/resources/vdb"
|
||||||
|
"$<TARGET_FILE_DIR:UsdLayerManager>/resources/vdb"
|
||||||
|
COMMENT "Copying fonts, SVG icons, HDRI presets and preview shapes to build output"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Copy ACES OCIO config to build output — only on first build (skipped if already present).
|
# Copy ACES OCIO config to build output — only on first build (skipped if already present).
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
# FindImguiNodeEditor.cmake - Find or configure thedmd/imgui-node-editor
|
# FindImguiNodeEditor.cmake - Find or configure thedmd/imgui-node-editor
|
||||||
#
|
#
|
||||||
# This module looks for imgui-node-editor in the third_party/imgui-node-editor
|
# This module looks for imgui-node-editor at src/ui/NodeEditor (an in-tree,
|
||||||
# directory (vendored, docking branch) and compiles its sources directly
|
# version-controlled subset of the upstream library — MIT licensed, see
|
||||||
# against this project's own Imgui::Imgui target so it shares the exact same
|
# src/ui/NodeEditor/LICENSE — containing only the files this project's build
|
||||||
# ImGui headers/defines (no second copy of ImGui is pulled in).
|
# actually compiles; the full upstream drop, including the standalone example
|
||||||
|
# app used to reproduce library-level bugs against a pure reference build,
|
||||||
|
# stays vendored separately at third_party/imgui-node-editor) and compiles its
|
||||||
|
# sources directly against this project's own Imgui::Imgui target so it shares
|
||||||
|
# the exact same ImGui headers/defines (no second copy of ImGui is pulled in).
|
||||||
#
|
#
|
||||||
# Inputs:
|
# Inputs:
|
||||||
# IMGUI_NODE_EDITOR_DIR - Path to imgui-node-editor/NodeEditor root
|
# IMGUI_NODE_EDITOR_DIR - Path to imgui-node-editor/NodeEditor root
|
||||||
@@ -13,12 +17,12 @@
|
|||||||
# ImguiNodeEditor::ImguiNodeEditor (imported STATIC target)
|
# ImguiNodeEditor::ImguiNodeEditor (imported STATIC target)
|
||||||
|
|
||||||
if(NOT IMGUI_NODE_EDITOR_DIR)
|
if(NOT IMGUI_NODE_EDITOR_DIR)
|
||||||
set(IMGUI_NODE_EDITOR_DIR "${CMAKE_SOURCE_DIR}/third_party/imgui-node-editor/NodeEditor")
|
set(IMGUI_NODE_EDITOR_DIR "${CMAKE_SOURCE_DIR}/src/ui/NodeEditor")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT IMGUI_NODE_EDITOR_BLUEPRINT_UTILITIES_DIR)
|
if(NOT IMGUI_NODE_EDITOR_BLUEPRINT_UTILITIES_DIR)
|
||||||
set(IMGUI_NODE_EDITOR_BLUEPRINT_UTILITIES_DIR
|
set(IMGUI_NODE_EDITOR_BLUEPRINT_UTILITIES_DIR
|
||||||
"${CMAKE_SOURCE_DIR}/third_party/imgui-node-editor/Examples/Common/BlueprintUtilities")
|
"${CMAKE_SOURCE_DIR}/src/ui/NodeEditor/BlueprintWidgets")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_path(ImguiNodeEditor_INCLUDE_DIR
|
find_path(ImguiNodeEditor_INCLUDE_DIR
|
||||||
@@ -48,7 +52,7 @@ endforeach()
|
|||||||
include(FindPackageHandleStandardArgs)
|
include(FindPackageHandleStandardArgs)
|
||||||
find_package_handle_standard_args(ImguiNodeEditor
|
find_package_handle_standard_args(ImguiNodeEditor
|
||||||
REQUIRED_VARS ImguiNodeEditor_INCLUDE_DIR
|
REQUIRED_VARS ImguiNodeEditor_INCLUDE_DIR
|
||||||
FAIL_MESSAGE "imgui-node-editor not found — expected it vendored at third_party/imgui-node-editor/NodeEditor"
|
FAIL_MESSAGE "imgui-node-editor not found — expected it at src/ui/NodeEditor"
|
||||||
)
|
)
|
||||||
|
|
||||||
if(ImguiNodeEditor_FOUND AND ImguiNodeEditor_SOURCES_MISSING)
|
if(ImguiNodeEditor_FOUND AND ImguiNodeEditor_SOURCES_MISSING)
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ set(OpenUSD_REQUIRED_LIBS
|
|||||||
usdGeom
|
usdGeom
|
||||||
usdLux
|
usdLux
|
||||||
usdShade
|
usdShade
|
||||||
|
usdVol
|
||||||
usdImaging
|
usdImaging
|
||||||
usdImagingGL
|
usdImagingGL
|
||||||
hdx
|
hdx
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,7 @@
|
|||||||
#include "MaterialManager.h"
|
#include "MaterialManager.h"
|
||||||
#include <pxr/usd/sdr/registry.h>
|
#include <pxr/usd/sdr/registry.h>
|
||||||
|
#include <pxr/usd/sdr/shaderProperty.h>
|
||||||
|
#include <pxr/base/tf/stringUtils.h>
|
||||||
#include <pxr/usd/usd/primRange.h>
|
#include <pxr/usd/usd/primRange.h>
|
||||||
#include <pxr/usd/usdShade/material.h>
|
#include <pxr/usd/usdShade/material.h>
|
||||||
#include <pxr/usd/usdShade/shader.h>
|
#include <pxr/usd/usdShade/shader.h>
|
||||||
@@ -9,10 +11,84 @@
|
|||||||
#include <pxr/base/tf/token.h>
|
#include <pxr/base/tf/token.h>
|
||||||
#include <pxr/base/vt/value.h>
|
#include <pxr/base/vt/value.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
namespace UsdLayerManager {
|
namespace UsdLayerManager {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Coarse Hypershade-style bucket for the create-node list. Sdr metadata is
|
||||||
|
// parser-dependent (glslfx vs MaterialX vs Arnold vs Cycles fill different
|
||||||
|
// fields), so classify from the strongest signal available: context first,
|
||||||
|
// then terminal output types, then role, then name keywords.
|
||||||
|
std::string DeriveCategory(pxr::SdrShaderNodeConstPtr node) {
|
||||||
|
const std::string context = pxr::TfStringify(node->GetContext());
|
||||||
|
if (context == "surface" || context == "volume" || context == "displacement")
|
||||||
|
return "Material";
|
||||||
|
if (context == "light" || context == "lightFilter")
|
||||||
|
return "Light";
|
||||||
|
|
||||||
|
// MaterialX/Arnold terminals often lack a context but type their outputs
|
||||||
|
// as surfaceshader/volumeshader/displacementshader/closure.
|
||||||
|
for (const pxr::TfToken& outName : node->GetShaderOutputNames()) {
|
||||||
|
if (const auto* prop = node->GetShaderOutput(outName)) {
|
||||||
|
const std::string outType = pxr::TfStringify(prop->GetType());
|
||||||
|
if (outType.find("shader") != std::string::npos ||
|
||||||
|
outType == "terminal" || outType == "closure")
|
||||||
|
return "Material";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string role = pxr::TfStringify(node->GetRole());
|
||||||
|
if (role == "texture") return "Texture";
|
||||||
|
if (role == "primvar") return "Geometry";
|
||||||
|
if (role == "math") return "Utility";
|
||||||
|
|
||||||
|
const std::string name = pxr::TfStringToLower(
|
||||||
|
node->GetIdentifier().GetString() + " " + node->GetName());
|
||||||
|
for (const char* kw : {"texture", "image", "checker", "noise", "ramp",
|
||||||
|
"fractal", "worley", "voronoi", "triplanar"})
|
||||||
|
if (name.find(kw) != std::string::npos) return "Texture";
|
||||||
|
for (const char* kw : {"primvar", "texcoord", "geomprop", "position",
|
||||||
|
"normal", "tangent", "bitangent", "uv_"})
|
||||||
|
if (name.find(kw) != std::string::npos) return "Geometry";
|
||||||
|
|
||||||
|
return "Utility";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed display order for the known buckets; anything unexpected sorts after.
|
||||||
|
int CategoryRank(const std::string& category) {
|
||||||
|
if (category == "Material") return 0;
|
||||||
|
if (category == "Texture") return 1;
|
||||||
|
if (category == "Geometry") return 2;
|
||||||
|
if (category == "Utility") return 3;
|
||||||
|
if (category == "Light") return 4;
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top-level group from the Sdr source type. glslfx and OSL collapse into one
|
||||||
|
// "USD" group — the core usd* nodes register under both parsers and would
|
||||||
|
// otherwise show up twice under different headers.
|
||||||
|
std::string DeriveSource(pxr::SdrShaderNodeConstPtr node) {
|
||||||
|
std::string st = pxr::TfStringToLower(pxr::TfStringify(node->GetSourceType()));
|
||||||
|
if (st == "glslfx" || st == "osl") return "USD";
|
||||||
|
if (st == "mtlx") return "MaterialX";
|
||||||
|
if (st == "arnold") return "Arnold";
|
||||||
|
if (st == "cycles") return "Cycles";
|
||||||
|
if (st.empty()) return "Other";
|
||||||
|
st[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(st[0])));
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SourceRank(const std::string& source) {
|
||||||
|
if (source == "USD") return 0;
|
||||||
|
if (source == "MaterialX") return 1;
|
||||||
|
return 2; // renderers and anything else, alphabetical among themselves
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
const std::vector<ShaderNodeTypeInfo>& MaterialManager::GetAvailableShaderNodes() {
|
const std::vector<ShaderNodeTypeInfo>& MaterialManager::GetAvailableShaderNodes() {
|
||||||
if (m_shaderNodeCacheBuilt)
|
if (m_shaderNodeCacheBuilt)
|
||||||
return m_shaderNodeCache;
|
return m_shaderNodeCache;
|
||||||
@@ -24,12 +100,19 @@ const std::vector<ShaderNodeTypeInfo>& MaterialManager::GetAvailableShaderNodes(
|
|||||||
info.identifier = node->GetIdentifier().GetString();
|
info.identifier = node->GetIdentifier().GetString();
|
||||||
info.label = !node->GetLabel().IsEmpty() ? node->GetLabel().GetString() : node->GetName();
|
info.label = !node->GetLabel().IsEmpty() ? node->GetLabel().GetString() : node->GetName();
|
||||||
info.family = node->GetFamily().GetString();
|
info.family = node->GetFamily().GetString();
|
||||||
|
info.category = DeriveCategory(node);
|
||||||
|
info.source = DeriveSource(node);
|
||||||
m_shaderNodeCache.push_back(std::move(info));
|
m_shaderNodeCache.push_back(std::move(info));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::sort(m_shaderNodeCache.begin(), m_shaderNodeCache.end(),
|
std::sort(m_shaderNodeCache.begin(), m_shaderNodeCache.end(),
|
||||||
[](const ShaderNodeTypeInfo& a, const ShaderNodeTypeInfo& b) {
|
[](const ShaderNodeTypeInfo& a, const ShaderNodeTypeInfo& b) {
|
||||||
if (a.family != b.family) return a.family < b.family;
|
const int sa = SourceRank(a.source), sb = SourceRank(b.source);
|
||||||
|
if (sa != sb) return sa < sb;
|
||||||
|
if (a.source != b.source) return a.source < b.source;
|
||||||
|
const int ra = CategoryRank(a.category), rb = CategoryRank(b.category);
|
||||||
|
if (ra != rb) return ra < rb;
|
||||||
|
if (a.category != b.category) return a.category < b.category;
|
||||||
return a.label < b.label;
|
return a.label < b.label;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -102,6 +185,7 @@ ShaderGraphSnapshot MaterialManager::GetShaderGraph(const pxr::SdfPath& material
|
|||||||
pxr::SdrShaderNodeConstPtr sdrNode =
|
pxr::SdrShaderNodeConstPtr sdrNode =
|
||||||
pxr::SdrRegistry::GetInstance().GetShaderNodeByIdentifier(shaderId);
|
pxr::SdrRegistry::GetInstance().GetShaderNodeByIdentifier(shaderId);
|
||||||
if (sdrNode) {
|
if (sdrNode) {
|
||||||
|
node.category = DeriveCategory(sdrNode);
|
||||||
for (const pxr::TfToken& inputName : sdrNode->GetShaderInputNames()) {
|
for (const pxr::TfToken& inputName : sdrNode->GetShaderInputNames()) {
|
||||||
if (auto* prop = sdrNode->GetShaderInput(inputName))
|
if (auto* prop = sdrNode->GetShaderInput(inputName))
|
||||||
node.inputs.push_back({inputName.GetString(), prop->GetTypeAsSdfType().GetSdfType()});
|
node.inputs.push_back({inputName.GetString(), prop->GetTypeAsSdfType().GetSdfType()});
|
||||||
|
|||||||
@@ -15,7 +15,15 @@ namespace UsdLayerManager {
|
|||||||
struct ShaderNodeTypeInfo {
|
struct ShaderNodeTypeInfo {
|
||||||
std::string identifier; ///< Sdr identifier, authored as info:id
|
std::string identifier; ///< Sdr identifier, authored as info:id
|
||||||
std::string label; ///< display label for the create-node menu
|
std::string label; ///< display label for the create-node menu
|
||||||
std::string family; ///< grouping for the create-node menu (may be empty)
|
std::string family; ///< Sdr family (parser-provided, may be empty)
|
||||||
|
/// Coarse Hypershade-style bucket ("Material", "Texture", "Geometry",
|
||||||
|
/// "Utility", "Light") derived from Sdr context/role/output types; the
|
||||||
|
/// create-node list groups by it. Never empty.
|
||||||
|
std::string category;
|
||||||
|
/// Top-level create-list group derived from the Sdr source type:
|
||||||
|
/// "USD" (glslfx/OSL core nodes), "MaterialX", "Arnold", "Cycles", or a
|
||||||
|
/// capitalized raw source type. Never empty.
|
||||||
|
std::string source;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// One input or output pin on a shader node, with its USD value type so
|
/// One input or output pin on a shader node, with its USD value type so
|
||||||
@@ -31,6 +39,11 @@ struct ShaderPinInfo {
|
|||||||
struct ShaderGraphNode {
|
struct ShaderGraphNode {
|
||||||
pxr::SdfPath path;
|
pxr::SdfPath path;
|
||||||
std::string shaderId;
|
std::string shaderId;
|
||||||
|
/// Coarse Hypershade-style bucket ("Material", "Texture", "Geometry",
|
||||||
|
/// "Utility", "Light") from the same classifier the create-node list uses;
|
||||||
|
/// empty when the shader id isn't in the Sdr registry. Drives which nodes
|
||||||
|
/// get an in-canvas thumbnail.
|
||||||
|
std::string category;
|
||||||
pxr::GfVec2f uiPosition{0.0f, 0.0f};
|
pxr::GfVec2f uiPosition{0.0f, 0.0f};
|
||||||
/// False when no uiPosition custom data is authored (typical for networks
|
/// False when no uiPosition custom data is authored (typical for networks
|
||||||
/// referenced from .mtlx) — the editor auto-lays such nodes out instead.
|
/// referenced from .mtlx) — the editor auto-lays such nodes out instead.
|
||||||
|
|||||||
@@ -59,6 +59,14 @@ void DeletePrimCommand::Undo() {
|
|||||||
if (!rootLayer) return;
|
if (!rootLayer) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// The destination ancestors may be gone by now (e.g. the parent
|
||||||
|
// material was itself deleted after this command ran) — SdfCopySpec
|
||||||
|
// fails with "No spec ... when trying to set field 'primChildren'"
|
||||||
|
// when the parent spec is missing, so recreate the chain first.
|
||||||
|
pxr::SdfPath parentPath = m_primPath.GetParentPath();
|
||||||
|
if (!parentPath.IsAbsoluteRootPath() && !rootLayer->GetPrimAtPath(parentPath))
|
||||||
|
pxr::SdfCreatePrimInLayer(rootLayer, parentPath);
|
||||||
|
|
||||||
if (!pxr::SdfCopySpec(m_savedLayer, m_primPath, rootLayer, m_primPath))
|
if (!pxr::SdfCopySpec(m_savedLayer, m_primPath, rootLayer, m_primPath))
|
||||||
LOG_ERROR("DeletePrimCommand::Undo: SdfCopySpec failed for " + m_primPath.GetString());
|
LOG_ERROR("DeletePrimCommand::Undo: SdfCopySpec failed for " + m_primPath.GetString());
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
|
|||||||
+11
-1
@@ -5,6 +5,7 @@
|
|||||||
#include <exception>
|
#include <exception>
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
static void SetUsdPluginPath() {
|
static void SetUsdPluginPath() {
|
||||||
char exePath[MAX_PATH];
|
char exePath[MAX_PATH];
|
||||||
@@ -98,7 +99,16 @@ static void SetUsdPluginPath() {
|
|||||||
int main(int /*argc*/, char* /*argv*/[]) {
|
int main(int /*argc*/, char* /*argv*/[]) {
|
||||||
try {
|
try {
|
||||||
UsdLayerManager::Logger::Instance().SetLogLevel(UsdLayerManager::LogLevel::Info);
|
UsdLayerManager::Logger::Instance().SetLogLevel(UsdLayerManager::LogLevel::Info);
|
||||||
|
// Temporary: capture LOG_INFO to a file so the [NodeGraph]/[ed] debug
|
||||||
|
// traces are readable after the fact (this is a WIN32-subsystem app,
|
||||||
|
// so stdout isn't visible when launched normally). Remove once the
|
||||||
|
// node-editor drag regression is diagnosed.
|
||||||
|
if (const char* appData = getenv("APPDATA")) {
|
||||||
|
std::filesystem::path logDir = std::filesystem::path(appData) / "UsdLayerManager";
|
||||||
|
std::filesystem::create_directories(logDir);
|
||||||
|
UsdLayerManager::Logger::Instance().SetLogFile((logDir / "debug.log").string());
|
||||||
|
}
|
||||||
|
|
||||||
LOG_INFO("=== USD Layer Manager Starting ===");
|
LOG_INFO("=== USD Layer Manager Starting ===");
|
||||||
|
|
||||||
SetUsdPluginPath();
|
SetUsdPluginPath();
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
|
|||||||
m_iconManager = std::make_unique<IconManager>();
|
m_iconManager = std::make_unique<IconManager>();
|
||||||
m_iconManager->Initialize(ResourcePath("resources/icons"), 24);
|
m_iconManager->Initialize(ResourcePath("resources/icons"), 24);
|
||||||
m_sceneHierarchyPanel->SetIconManager(m_iconManager.get());
|
m_sceneHierarchyPanel->SetIconManager(m_iconManager.get());
|
||||||
|
m_propertyPanel->SetIconManager(m_iconManager.get());
|
||||||
m_viewportPanel->SetIconManager(m_iconManager.get());
|
m_viewportPanel->SetIconManager(m_iconManager.get());
|
||||||
m_timelinePanel->SetIconManager(m_iconManager.get());
|
m_timelinePanel->SetIconManager(m_iconManager.get());
|
||||||
m_stageEditorPanel->SetIconManager(m_iconManager.get());
|
m_stageEditorPanel->SetIconManager(m_iconManager.get());
|
||||||
@@ -424,11 +425,25 @@ void Application::LoadPreferences()
|
|||||||
else if (key == "OcioView") m_prefs.ocioView = val;
|
else if (key == "OcioView") m_prefs.ocioView = val;
|
||||||
else if (key == "OcioColorSpace") m_prefs.ocioColorSpace = val;
|
else if (key == "OcioColorSpace") m_prefs.ocioColorSpace = val;
|
||||||
else if (key == "OcioLook") m_prefs.ocioLook = val;
|
else if (key == "OcioLook") m_prefs.ocioLook = val;
|
||||||
|
else if (key == "MaterialBrowserWidth")
|
||||||
|
m_prefs.materialBrowserWidth = [&]{ try { return std::stof(val); } catch(...){ return 220.0f; } }();
|
||||||
|
else if (key == "MaterialPreviewWidth")
|
||||||
|
m_prefs.materialPreviewWidth = [&]{ try { return std::stof(val); } catch(...){ return 320.0f; } }();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_materialEditorPanel)
|
||||||
|
m_materialEditorPanel->SetColumnWidths(m_prefs.materialBrowserWidth,
|
||||||
|
m_prefs.materialPreviewWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::SavePreferences()
|
void Application::SavePreferences()
|
||||||
{
|
{
|
||||||
|
// Pull the latest splitter positions; called from Shutdown before the
|
||||||
|
// panel is reset, and from the preferences dialog while it's alive.
|
||||||
|
if (m_materialEditorPanel)
|
||||||
|
m_materialEditorPanel->GetColumnWidths(m_prefs.materialBrowserWidth,
|
||||||
|
m_prefs.materialPreviewWidth);
|
||||||
|
|
||||||
std::ofstream f(m_prefsPath);
|
std::ofstream f(m_prefsPath);
|
||||||
if (!f) return;
|
if (!f) return;
|
||||||
f << "[Preferences]\n";
|
f << "[Preferences]\n";
|
||||||
@@ -438,6 +453,8 @@ void Application::SavePreferences()
|
|||||||
f << "OcioView=" << m_prefs.ocioView << "\n";
|
f << "OcioView=" << m_prefs.ocioView << "\n";
|
||||||
f << "OcioColorSpace=" << m_prefs.ocioColorSpace << "\n";
|
f << "OcioColorSpace=" << m_prefs.ocioColorSpace << "\n";
|
||||||
f << "OcioLook=" << m_prefs.ocioLook << "\n";
|
f << "OcioLook=" << m_prefs.ocioLook << "\n";
|
||||||
|
f << "MaterialBrowserWidth=" << m_prefs.materialBrowserWidth << "\n";
|
||||||
|
f << "MaterialPreviewWidth=" << m_prefs.materialPreviewWidth << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::ApplyPrefsToAllViewports()
|
void Application::ApplyPrefsToAllViewports()
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ struct AppPreferences {
|
|||||||
std::string ocioView;
|
std::string ocioView;
|
||||||
std::string ocioColorSpace;
|
std::string ocioColorSpace;
|
||||||
std::string ocioLook;
|
std::string ocioLook;
|
||||||
|
float materialBrowserWidth = 220.0f; ///< Material Editor left-column width
|
||||||
|
float materialPreviewWidth = 320.0f; ///< Material Editor right-column width
|
||||||
};
|
};
|
||||||
|
|
||||||
class Application {
|
class Application {
|
||||||
|
|||||||
@@ -94,6 +94,20 @@ bool ImGuiContext::Initialize(const std::string& windowTitle, int width, int hei
|
|||||||
// With NavEnableKeyboard set, io.WantCaptureKeyboard becomes true whenever any
|
// With NavEnableKeyboard set, io.WantCaptureKeyboard becomes true whenever any
|
||||||
// ImGui window is focused, which would block all viewport hotkeys (F, A, etc.).
|
// ImGui window is focused, which would block all viewport hotkeys (F, A, etc.).
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||||
|
// Dock only while Shift is held during a title-bar/tab drag (the dock
|
||||||
|
// position overlay appears then); a plain drag just moves the window,
|
||||||
|
// so casual rearranging can't accidentally re-dock panels.
|
||||||
|
io.ConfigDockingWithShift = true;
|
||||||
|
// Windows move only via their title bar/tab, never by dragging empty
|
||||||
|
// content. Without this, a press-drag that a content widget doesn't
|
||||||
|
// capture falls through to ImGui's "drag body to move window" behaviour —
|
||||||
|
// which, in the node-editor canvas (whose background button doesn't
|
||||||
|
// reliably claim the drag in this nested-child embedding), meant dragging
|
||||||
|
// in the graph moved the whole Material Editor window. Title-bar dragging
|
||||||
|
// and Shift-to-dock are unaffected; this is also the desired behaviour for
|
||||||
|
// every other content area (viewports, timelines) where a content drag
|
||||||
|
// must never move the host window.
|
||||||
|
io.ConfigWindowsMoveFromTitleBarOnly = true;
|
||||||
|
|
||||||
// Store imgui.ini in %APPDATA%\UsdLayerManager\ so it doesn't litter the CWD.
|
// Store imgui.ini in %APPDATA%\UsdLayerManager\ so it doesn't litter the CWD.
|
||||||
if (const char* appData = getenv("APPDATA")) {
|
if (const char* appData = getenv("APPDATA")) {
|
||||||
|
|||||||
+1054
-81
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@
|
|||||||
#include "../core/MaterialManager.h"
|
#include "../core/MaterialManager.h"
|
||||||
#include "IconManager.h"
|
#include "IconManager.h"
|
||||||
#include "MaterialPreviewRenderer.h"
|
#include "MaterialPreviewRenderer.h"
|
||||||
|
#include "NodeThumbnailCache.h"
|
||||||
#include <pxr/usd/usd/stage.h>
|
#include <pxr/usd/usd/stage.h>
|
||||||
#include <pxr/usd/sdf/path.h>
|
#include <pxr/usd/sdf/path.h>
|
||||||
#include <pxr/usd/sdf/valueTypeName.h>
|
#include <pxr/usd/sdf/valueTypeName.h>
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace UsdLayerManager {
|
namespace UsdLayerManager {
|
||||||
|
|
||||||
@@ -44,6 +46,14 @@ public:
|
|||||||
/// auto-loads it into the canvas.
|
/// auto-loads it into the canvas.
|
||||||
void SetTargetPrimPath(const std::string& path);
|
void SetTargetPrimPath(const std::string& path);
|
||||||
|
|
||||||
|
/// Splitter-adjustable column widths, persisted in preferences.ini by
|
||||||
|
/// Application. Setter clamps to the same range as the splitters.
|
||||||
|
void GetColumnWidths(float& browserWidth, float& previewWidth) const {
|
||||||
|
browserWidth = m_browserWidth;
|
||||||
|
previewWidth = m_previewWidth;
|
||||||
|
}
|
||||||
|
void SetColumnWidths(float browserWidth, float previewWidth);
|
||||||
|
|
||||||
void Render();
|
void Render();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -51,7 +61,7 @@ private:
|
|||||||
struct PinInfo {
|
struct PinInfo {
|
||||||
pxr::SdfPath nodePath;
|
pxr::SdfPath nodePath;
|
||||||
std::string name;
|
std::string name;
|
||||||
bool isOutput;
|
bool isOutput = false;
|
||||||
pxr::SdfValueTypeName typeName;
|
pxr::SdfValueTypeName typeName;
|
||||||
};
|
};
|
||||||
/// Resolved endpoint of a rendered link, keyed by its ax::NodeEditor LinkId.
|
/// Resolved endpoint of a rendered link, keyed by its ax::NodeEditor LinkId.
|
||||||
@@ -74,13 +84,28 @@ private:
|
|||||||
void RenderMaterialBrowser();
|
void RenderMaterialBrowser();
|
||||||
/// Creates a uniquely-named empty material under /Materials and opens it.
|
/// Creates a uniquely-named empty material under /Materials and opens it.
|
||||||
void CreateNewMaterial();
|
void CreateNewMaterial();
|
||||||
|
/// Preset graphs for the open material (one undoable command each):
|
||||||
|
/// UsdPreviewSurface + UsdUVTextures fed by an st primvar reader, or a
|
||||||
|
/// MaterialX standard_surface + image nodes fed by a texcoord node.
|
||||||
|
void CreateUsdPresetGraph();
|
||||||
|
void CreateMaterialXPresetGraph();
|
||||||
|
void CreateArnoldPresetGraph();
|
||||||
void RenderNodeGraphCanvas();
|
void RenderNodeGraphCanvas();
|
||||||
void RenderPreviewPanel();
|
void RenderPreviewPanel();
|
||||||
void HandleCreateAndDelete();
|
void HandleCreateAndDelete();
|
||||||
/// Nuke-style TAB popup: type-to-filter shader node list; Enter or click
|
/// Nuke-style TAB popup: type-to-filter shader node list; Enter or click
|
||||||
/// creates the highlighted node at canvasPos.
|
/// creates the highlighted node at canvasPos. When linkSource is non-null
|
||||||
void RenderNodeSearchMenu(const ImVec2& canvasPos);
|
/// (a connection was dragged onto empty canvas), the list is filtered to
|
||||||
void CreateShaderNode(const std::string& shaderId, const ImVec2& canvasPos);
|
/// nodes with a type-connectable pin and the chosen node is auto-wired to
|
||||||
|
/// linkSource.
|
||||||
|
void RenderNodeSearchMenu(const ImVec2& canvasPos, const PinInfo* linkSource = nullptr);
|
||||||
|
/// Creates the node and returns its prim path (empty on failure).
|
||||||
|
pxr::SdfPath CreateShaderNode(const std::string& shaderId, const ImVec2& canvasPos);
|
||||||
|
/// Nudges a creation point right until the new node's estimated rectangle
|
||||||
|
/// clears every existing node's rendered rectangle. Overlapping nodes
|
||||||
|
/// fight over the editor's hit test: the buried one becomes unmovable and
|
||||||
|
/// its pins unhoverable. Must be called with the editor context current.
|
||||||
|
ImVec2 FindFreeCanvasSpot(ImVec2 desired, const std::string& shaderId) const;
|
||||||
/// Binds an existing material to targetPath, undoably, restoring whatever
|
/// Binds an existing material to targetPath, undoably, restoring whatever
|
||||||
/// direct binding (if any) targetPath had before.
|
/// direct binding (if any) targetPath had before.
|
||||||
void BindMaterialToTarget(const pxr::SdfPath& materialPath, const pxr::SdfPath& targetPath);
|
void BindMaterialToTarget(const pxr::SdfPath& materialPath, const pxr::SdfPath& targetPath);
|
||||||
@@ -90,6 +115,9 @@ private:
|
|||||||
void CreateConnection(const PinInfo& destInput, const PinInfo& sourceOutput);
|
void CreateConnection(const PinInfo& destInput, const PinInfo& sourceOutput);
|
||||||
void DeleteNode(const pxr::SdfPath& nodePath);
|
void DeleteNode(const pxr::SdfPath& nodePath);
|
||||||
void DisconnectAttr(const pxr::SdfPath& destNode, const std::string& destInput);
|
void DisconnectAttr(const pxr::SdfPath& destNode, const std::string& destInput);
|
||||||
|
/// Resolved on-disk path of a texture node's image (its Asset-typed `file`
|
||||||
|
/// input), or empty if unauthored/unresolvable. Feeds the thumbnail cache.
|
||||||
|
std::string ResolveTextureFilePath(const ShaderGraphNode& node) const;
|
||||||
void SyncFromUsd();
|
void SyncFromUsd();
|
||||||
void PersistNodePosition(ax::NodeEditor::NodeId nodeId);
|
void PersistNodePosition(ax::NodeEditor::NodeId nodeId);
|
||||||
bool IsPinLinked(const pxr::SdfPath& nodePath, const std::string& pinName, bool isOutput) const;
|
bool IsPinLinked(const pxr::SdfPath& nodePath, const std::string& pinName, bool isOutput) const;
|
||||||
@@ -123,6 +151,8 @@ private:
|
|||||||
std::unordered_map<uintptr_t, LinkInfo> m_linkIdToInfo;
|
std::unordered_map<uintptr_t, LinkInfo> m_linkIdToInfo;
|
||||||
|
|
||||||
MaterialPreviewRenderer m_preview;
|
MaterialPreviewRenderer m_preview;
|
||||||
|
/// Per-node in-canvas thumbnails (texture images + material shader balls).
|
||||||
|
NodeThumbnailCache m_thumbnails;
|
||||||
|
|
||||||
ImVec2 m_pendingCreateNodePos{0.0f, 0.0f};
|
ImVec2 m_pendingCreateNodePos{0.0f, 0.0f};
|
||||||
|
|
||||||
@@ -133,6 +163,30 @@ private:
|
|||||||
char m_nodeSearchBuf[128] = "";
|
char m_nodeSearchBuf[128] = "";
|
||||||
int m_nodeSearchSelected = 0;
|
int m_nodeSearchSelected = 0;
|
||||||
|
|
||||||
|
/// Nuke-style drag-to-create: set when a connection was dragged from a pin
|
||||||
|
/// onto empty canvas. m_openLinkDragMenu requests the create-node popup on
|
||||||
|
/// the next frame (deferred so OpenPopup runs while the editor is
|
||||||
|
/// suspended); m_linkDragSourcePin is the pin to auto-wire the new node to.
|
||||||
|
bool m_openLinkDragMenu = false;
|
||||||
|
PinInfo m_linkDragSourcePin;
|
||||||
|
|
||||||
|
/// Column widths of the browser (left) and preview (right) sections,
|
||||||
|
/// user-adjustable via the splitters between them and the canvas.
|
||||||
|
float m_browserWidth = 220.0f;
|
||||||
|
float m_previewWidth = 320.0f;
|
||||||
|
|
||||||
|
/// Fraction of the browser column's height given to the materials list;
|
||||||
|
/// the create-node list takes the rest. User-adjustable via the horizontal
|
||||||
|
/// splitter between them.
|
||||||
|
float m_browserListRatio = 0.45f;
|
||||||
|
|
||||||
|
/// Inline-rename state for the materials list (double-click a row to start;
|
||||||
|
/// mirrors SceneHierarchyPanel): the material being renamed (empty = none),
|
||||||
|
/// the edit buffer, and a one-frame flag to focus the InputText on start.
|
||||||
|
pxr::SdfPath m_renamingMaterial;
|
||||||
|
char m_materialRenameBuf[128] = "";
|
||||||
|
bool m_materialRenameJustStarted = false;
|
||||||
|
|
||||||
/// Search text of the browser's persistent create-node list.
|
/// Search text of the browser's persistent create-node list.
|
||||||
char m_browserNodeSearchBuf[128] = "";
|
char m_browserNodeSearchBuf[128] = "";
|
||||||
/// Shader id clicked in the browser's create-node list; created at the
|
/// Shader id clicked in the browser's create-node list; created at the
|
||||||
@@ -140,6 +194,15 @@ private:
|
|||||||
/// is only valid inside the editor's Begin/End).
|
/// is only valid inside the editor's Begin/End).
|
||||||
std::string m_pendingCreateShaderId;
|
std::string m_pendingCreateShaderId;
|
||||||
|
|
||||||
|
/// Debug event-trace state (see the logging block at the end of
|
||||||
|
/// RenderNodeGraphCanvas): whether the last LMB press landed on a node
|
||||||
|
/// rect, whether that press's drag-start has been logged yet, and the
|
||||||
|
/// editor NodeIds hit at press time (their live positions are logged on
|
||||||
|
/// every drag-move so a node that stops following the mouse is visible).
|
||||||
|
bool m_debugPressOnNode = false;
|
||||||
|
bool m_debugDragLogged = false;
|
||||||
|
std::vector<uintptr_t> m_debugDragNodeIds;
|
||||||
|
|
||||||
/// First node currently selected in the graph editor (empty = none);
|
/// First node currently selected in the graph editor (empty = none);
|
||||||
/// drives the properties section under the preview.
|
/// drives the properties section under the preview.
|
||||||
pxr::SdfPath m_selectedNodePath;
|
pxr::SdfPath m_selectedNodePath;
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
#include "MaterialPreviewRenderer.h"
|
#include "MaterialPreviewRenderer.h"
|
||||||
#include "../utils/Logger.h"
|
#include "../utils/Logger.h"
|
||||||
#include "../utils/PathUtils.h"
|
#include "../utils/PathUtils.h"
|
||||||
|
#include <pxr/base/tf/stringUtils.h>
|
||||||
#include <pxr/usd/usdGeom/sphere.h>
|
#include <pxr/usd/usdGeom/sphere.h>
|
||||||
|
#include <pxr/usd/usdGeom/cube.h>
|
||||||
|
#include <pxr/usd/usdGeom/cylinder.h>
|
||||||
|
#include <pxr/usd/usdGeom/mesh.h>
|
||||||
|
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||||
|
#include <pxr/usd/usdGeom/scope.h>
|
||||||
#include <pxr/usd/usdGeom/metrics.h>
|
#include <pxr/usd/usdGeom/metrics.h>
|
||||||
#include <pxr/usd/usdGeom/tokens.h>
|
#include <pxr/usd/usdGeom/tokens.h>
|
||||||
|
#include <pxr/usd/usdVol/volume.h>
|
||||||
|
#include <pxr/usd/usdVol/openVDBAsset.h>
|
||||||
|
#include <pxr/base/vt/array.h>
|
||||||
|
#include <pxr/base/gf/vec2f.h>
|
||||||
|
#include <pxr/base/gf/vec3f.h>
|
||||||
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
||||||
#include <pxr/usd/usdShade/material.h>
|
#include <pxr/usd/usdShade/material.h>
|
||||||
#include <pxr/usd/usdShade/shader.h>
|
#include <pxr/usd/usdShade/shader.h>
|
||||||
@@ -14,6 +25,7 @@
|
|||||||
#include <pxr/usd/usdLux/tokens.h>
|
#include <pxr/usd/usdLux/tokens.h>
|
||||||
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||||
#include <pxr/usd/usd/references.h>
|
#include <pxr/usd/usd/references.h>
|
||||||
|
#include <pxr/usd/usd/editContext.h>
|
||||||
#include <pxr/usd/sdf/assetPath.h>
|
#include <pxr/usd/sdf/assetPath.h>
|
||||||
#include <pxr/usd/sdf/primSpec.h>
|
#include <pxr/usd/sdf/primSpec.h>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@@ -22,10 +34,30 @@
|
|||||||
namespace UsdLayerManager {
|
namespace UsdLayerManager {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const pxr::SdfPath kSpherePath("/Preview/Sphere");
|
const pxr::SdfPath kShapesRootPath("/Preview/Shapes");
|
||||||
const pxr::SdfPath kDistantLightPath("/Preview/Light");
|
const pxr::SdfPath kDistantLightPath("/Preview/Light");
|
||||||
const pxr::SdfPath kDomeLightPath("/Preview/DomeLight");
|
const pxr::SdfPath kDomeLightPath("/Preview/DomeLight");
|
||||||
|
|
||||||
|
/// Selectable preview geometry; prim name is a child of kShapesRootPath.
|
||||||
|
/// Shapes backed by an asset file are only created when the file exists.
|
||||||
|
/// frameFit: camera framing margin — the camera frames the shape's bounding
|
||||||
|
/// box, and a cube fills its box completely while a sphere only inscribes it,
|
||||||
|
/// so boxy shapes need a larger margin to read at a similar visual size.
|
||||||
|
struct ShapePreset {
|
||||||
|
const char* label;
|
||||||
|
const char* primName;
|
||||||
|
double frameFit;
|
||||||
|
};
|
||||||
|
const ShapePreset kShapePresets[] = {
|
||||||
|
{"Sphere", "Sphere", 1.6},
|
||||||
|
{"Cube", "Cube", 2.1},
|
||||||
|
{"Cylinder", "Cylinder", 1.8},
|
||||||
|
{"Teapot", "Teapot", 1.6},
|
||||||
|
{"Hair", "Hair", 1.6},
|
||||||
|
{"Cloud Volume", "Cloud", 1.6},
|
||||||
|
};
|
||||||
|
constexpr int kShapePresetCount = static_cast<int>(sizeof(kShapePresets) / sizeof(kShapePresets[0]));
|
||||||
|
|
||||||
/// Scratch UsdPreviewSurface authored inside the (referenced) material scope
|
/// Scratch UsdPreviewSurface authored inside the (referenced) material scope
|
||||||
/// when previewing a non-terminal node output — child of the material so the
|
/// when previewing a non-terminal node output — child of the material so the
|
||||||
/// connection stays encapsulated.
|
/// connection stays encapsulated.
|
||||||
@@ -42,17 +74,227 @@ const LightPreset kLightPresets[] = {
|
|||||||
{"Sunset", "venice_sunset_1k.exr"},
|
{"Sunset", "venice_sunset_1k.exr"},
|
||||||
};
|
};
|
||||||
constexpr int kLightPresetCount = static_cast<int>(sizeof(kLightPresets) / sizeof(kLightPresets[0]));
|
constexpr int kLightPresetCount = static_cast<int>(sizeof(kLightPresets) / sizeof(kLightPresets[0]));
|
||||||
|
|
||||||
|
/// Authors a polygonal UsdGeomMesh from prebuilt arrays: vertex-interpolated
|
||||||
|
/// normals + a `st` texCoord2f primvar, no subdivision, single-sided. Shared by
|
||||||
|
/// every preview shape so they all light and texture identically.
|
||||||
|
void DefineUvMesh(const pxr::UsdStageRefPtr& stage, const pxr::SdfPath& path,
|
||||||
|
const pxr::VtVec3fArray& points, const pxr::VtVec3fArray& normals,
|
||||||
|
const pxr::VtVec2fArray& st, const pxr::VtIntArray& fvCounts,
|
||||||
|
const pxr::VtIntArray& fvIndices,
|
||||||
|
const pxr::GfVec3f& extentMin, const pxr::GfVec3f& extentMax) {
|
||||||
|
pxr::UsdGeomMesh mesh = pxr::UsdGeomMesh::Define(stage, path);
|
||||||
|
mesh.CreatePointsAttr(pxr::VtValue(points));
|
||||||
|
mesh.CreateNormalsAttr(pxr::VtValue(normals));
|
||||||
|
mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->vertex);
|
||||||
|
mesh.CreateFaceVertexCountsAttr(pxr::VtValue(fvCounts));
|
||||||
|
mesh.CreateFaceVertexIndicesAttr(pxr::VtValue(fvIndices));
|
||||||
|
// Polygonal (not subdivided) so the authored smooth normals and UVs are used
|
||||||
|
// verbatim. Single-sided (the default): a closed shape never shows a
|
||||||
|
// backface, and double-sided shading runs a view-dependent normal flip that
|
||||||
|
// corrupts the mirror-reflection vector near the silhouette.
|
||||||
|
mesh.CreateSubdivisionSchemeAttr(pxr::VtValue(pxr::UsdGeomTokens->none));
|
||||||
|
mesh.CreateExtentAttr(pxr::VtValue(pxr::VtVec3fArray{extentMin, extentMax}));
|
||||||
|
pxr::UsdGeomPrimvarsAPI(mesh).CreatePrimvar(
|
||||||
|
pxr::TfToken("st"), pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->vertex)
|
||||||
|
.Set(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Authors a UV-mapped mesh sphere (Y-up) at `path` with vertex normals and a
|
||||||
|
/// `st` texCoord2f primvar. The implicit UsdGeomSphere carries no texture
|
||||||
|
/// coordinates (GeomUtilSphereMeshGenerator emits only points/normals/topology),
|
||||||
|
/// so a UsdUVTexture fed by a UsdPrimvarReader_float2("st") would only ever read
|
||||||
|
/// the reader's fallback and paint a flat colour. This mesh gives the shader
|
||||||
|
/// ball real UVs so textures map across every render delegate.
|
||||||
|
void CreateUvSphere(const pxr::UsdStageRefPtr& stage, const pxr::SdfPath& path, double radius) {
|
||||||
|
constexpr int rings = 48; // latitude bands (theta 0..pi)
|
||||||
|
constexpr int sectors = 96; // longitude bands (phi 0..2pi)
|
||||||
|
constexpr double kPi = 3.14159265358979323846;
|
||||||
|
const float r = static_cast<float>(radius);
|
||||||
|
|
||||||
|
pxr::VtVec3fArray points, normals;
|
||||||
|
pxr::VtVec2fArray st;
|
||||||
|
points.reserve((rings + 1) * (sectors + 1));
|
||||||
|
normals.reserve((rings + 1) * (sectors + 1));
|
||||||
|
st.reserve((rings + 1) * (sectors + 1));
|
||||||
|
// A duplicated seam column (sectors+1 wide) gives distinct u=0 and u=1
|
||||||
|
// vertices so the texture wraps without a smear across the seam.
|
||||||
|
for (int i = 0; i <= rings; ++i) {
|
||||||
|
const float v = static_cast<float>(i) / rings;
|
||||||
|
const float th = v * static_cast<float>(kPi);
|
||||||
|
const float sinT = std::sin(th), cosT = std::cos(th);
|
||||||
|
for (int j = 0; j <= sectors; ++j) {
|
||||||
|
const float u = static_cast<float>(j) / sectors;
|
||||||
|
const float phi = u * 2.0f * static_cast<float>(kPi);
|
||||||
|
const pxr::GfVec3f n(sinT * std::cos(phi), cosT, sinT * std::sin(phi));
|
||||||
|
points.push_back(n * r);
|
||||||
|
normals.push_back(n);
|
||||||
|
st.push_back(pxr::GfVec2f(u, 1.0f - v)); // flip V so image top → sphere top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::VtIntArray fvCounts, fvIndices;
|
||||||
|
const int stride = sectors + 1;
|
||||||
|
for (int i = 0; i < rings; ++i)
|
||||||
|
for (int j = 0; j < sectors; ++j) {
|
||||||
|
const int a = i * stride + j;
|
||||||
|
fvCounts.push_back(4);
|
||||||
|
// Wind CCW as seen from outside so the (single-sided) front face and
|
||||||
|
// the outward vertex normals agree; a reversed winding would either
|
||||||
|
// cull the visible surface or flip its shading normal.
|
||||||
|
fvIndices.push_back(a);
|
||||||
|
fvIndices.push_back(a + 1);
|
||||||
|
fvIndices.push_back(a + stride + 1);
|
||||||
|
fvIndices.push_back(a + stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
DefineUvMesh(stage, path, points, normals, st, fvCounts, fvIndices,
|
||||||
|
pxr::GfVec3f(-r, -r, -r), pxr::GfVec3f(r, r, r));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// UV-mapped box: 24 vertices (4 per face) so every face gets its own outward
|
||||||
|
/// normal and a full 0..1 UV square. halfSize is the half-edge length.
|
||||||
|
void CreateUvCube(const pxr::UsdStageRefPtr& stage, const pxr::SdfPath& path, double halfSize) {
|
||||||
|
const float h = static_cast<float>(halfSize);
|
||||||
|
pxr::VtVec3fArray points, normals;
|
||||||
|
pxr::VtVec2fArray st;
|
||||||
|
pxr::VtIntArray fvCounts, fvIndices;
|
||||||
|
|
||||||
|
// p0..p3 are wound CCW as seen from outside, so the front face agrees with n.
|
||||||
|
auto addFace = [&](const pxr::GfVec3f& p0, const pxr::GfVec3f& p1,
|
||||||
|
const pxr::GfVec3f& p2, const pxr::GfVec3f& p3,
|
||||||
|
const pxr::GfVec3f& n) {
|
||||||
|
const int base = static_cast<int>(points.size());
|
||||||
|
points.push_back(p0); points.push_back(p1); points.push_back(p2); points.push_back(p3);
|
||||||
|
for (int k = 0; k < 4; ++k) normals.push_back(n);
|
||||||
|
st.push_back(pxr::GfVec2f(0, 0)); st.push_back(pxr::GfVec2f(1, 0));
|
||||||
|
st.push_back(pxr::GfVec2f(1, 1)); st.push_back(pxr::GfVec2f(0, 1));
|
||||||
|
fvCounts.push_back(4);
|
||||||
|
fvIndices.push_back(base + 0); fvIndices.push_back(base + 1);
|
||||||
|
fvIndices.push_back(base + 2); fvIndices.push_back(base + 3);
|
||||||
|
};
|
||||||
|
addFace({ h,-h, h}, { h,-h,-h}, { h, h,-h}, { h, h, h}, { 1, 0, 0}); // +X
|
||||||
|
addFace({-h,-h,-h}, {-h,-h, h}, {-h, h, h}, {-h, h,-h}, {-1, 0, 0}); // -X
|
||||||
|
addFace({-h, h,-h}, {-h, h, h}, { h, h, h}, { h, h,-h}, { 0, 1, 0}); // +Y
|
||||||
|
addFace({-h,-h,-h}, { h,-h,-h}, { h,-h, h}, {-h,-h, h}, { 0,-1, 0}); // -Y
|
||||||
|
addFace({-h,-h, h}, { h,-h, h}, { h, h, h}, {-h, h, h}, { 0, 0, 1}); // +Z
|
||||||
|
addFace({ h,-h,-h}, {-h,-h,-h}, {-h, h,-h}, { h, h,-h}, { 0, 0,-1}); // -Z
|
||||||
|
|
||||||
|
DefineUvMesh(stage, path, points, normals, st, fvCounts, fvIndices,
|
||||||
|
pxr::GfVec3f(-h, -h, -h), pxr::GfVec3f(h, h, h));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// UV-mapped cylinder (Y axis): radial side wall + two end caps. Side normals
|
||||||
|
/// are radial, caps ±Y; UVs wrap the wall (u around, v along height) and map
|
||||||
|
/// each cap as a disk.
|
||||||
|
void CreateUvCylinder(const pxr::UsdStageRefPtr& stage, const pxr::SdfPath& path,
|
||||||
|
double radius, double halfHeight) {
|
||||||
|
constexpr int sectors = 96;
|
||||||
|
constexpr double kPi = 3.14159265358979323846;
|
||||||
|
const float R = static_cast<float>(radius);
|
||||||
|
const float hy = static_cast<float>(halfHeight);
|
||||||
|
|
||||||
|
pxr::VtVec3fArray points, normals;
|
||||||
|
pxr::VtVec2fArray st;
|
||||||
|
pxr::VtIntArray fvCounts, fvIndices;
|
||||||
|
|
||||||
|
// Side wall: sectors+1 columns (duplicated seam for a clean u wrap), each a
|
||||||
|
// bottom+top pair interleaved so column j owns indices 2j (bottom), 2j+1 (top).
|
||||||
|
for (int j = 0; j <= sectors; ++j) {
|
||||||
|
const float u = static_cast<float>(j) / sectors;
|
||||||
|
const float phi = u * 2.0f * static_cast<float>(kPi);
|
||||||
|
const float c = std::cos(phi), s = std::sin(phi);
|
||||||
|
points.push_back(pxr::GfVec3f(R * c, -hy, R * s));
|
||||||
|
normals.push_back(pxr::GfVec3f(c, 0, s));
|
||||||
|
st.push_back(pxr::GfVec2f(u, 0));
|
||||||
|
points.push_back(pxr::GfVec3f(R * c, hy, R * s));
|
||||||
|
normals.push_back(pxr::GfVec3f(c, 0, s));
|
||||||
|
st.push_back(pxr::GfVec2f(u, 1));
|
||||||
|
}
|
||||||
|
for (int j = 0; j < sectors; ++j) {
|
||||||
|
const int b0 = 2 * j, t0 = 2 * j + 1, b1 = 2 * (j + 1), t1 = 2 * (j + 1) + 1;
|
||||||
|
fvCounts.push_back(4); // b_j, t_j, t_{j+1}, b_{j+1} → outward radial
|
||||||
|
fvIndices.push_back(b0); fvIndices.push_back(t0);
|
||||||
|
fvIndices.push_back(t1); fvIndices.push_back(b1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// End caps as triangle fans around a center vertex; ring vertices carry the
|
||||||
|
// cap normal (not the radial one) and disk UVs.
|
||||||
|
auto addCap = [&](float y, float ny) {
|
||||||
|
const int center = static_cast<int>(points.size());
|
||||||
|
points.push_back(pxr::GfVec3f(0, y, 0));
|
||||||
|
normals.push_back(pxr::GfVec3f(0, ny, 0));
|
||||||
|
st.push_back(pxr::GfVec2f(0.5f, 0.5f));
|
||||||
|
const int ringBase = static_cast<int>(points.size());
|
||||||
|
for (int j = 0; j < sectors; ++j) {
|
||||||
|
const float phi = static_cast<float>(j) / sectors * 2.0f * static_cast<float>(kPi);
|
||||||
|
const float c = std::cos(phi), s = std::sin(phi);
|
||||||
|
points.push_back(pxr::GfVec3f(R * c, y, R * s));
|
||||||
|
normals.push_back(pxr::GfVec3f(0, ny, 0));
|
||||||
|
st.push_back(pxr::GfVec2f(0.5f + 0.5f * c, 0.5f + 0.5f * s));
|
||||||
|
}
|
||||||
|
for (int j = 0; j < sectors; ++j) {
|
||||||
|
const int a = ringBase + j, b = ringBase + (j + 1) % sectors;
|
||||||
|
fvCounts.push_back(3);
|
||||||
|
fvIndices.push_back(center);
|
||||||
|
// Wind so the tri normal matches the cap direction (+Y vs -Y).
|
||||||
|
if (ny > 0) { fvIndices.push_back(b); fvIndices.push_back(a); }
|
||||||
|
else { fvIndices.push_back(a); fvIndices.push_back(b); }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addCap( hy, 1.0f);
|
||||||
|
addCap(-hy, -1.0f);
|
||||||
|
|
||||||
|
DefineUvMesh(stage, path, points, normals, st, fvCounts, fvIndices,
|
||||||
|
pxr::GfVec3f(-R, -hy, -R), pxr::GfVec3f(R, hy, R));
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void MaterialPreviewRenderer::EnsureInitialized() {
|
void MaterialPreviewRenderer::EnsureInitialized() {
|
||||||
if (m_initialized) return;
|
if (m_initialized) return;
|
||||||
|
|
||||||
m_previewStage = pxr::UsdStage::CreateInMemory();
|
m_previewStage = pxr::UsdStage::CreateInMemory();
|
||||||
|
// Everything authored here (sphere, lights, reference) must go to the
|
||||||
|
// root layer: the session layer is reserved for node-preview overrides
|
||||||
|
// and is wiped wholesale on every SetMaterial.
|
||||||
|
m_previewStage->SetEditTarget(pxr::UsdEditTarget(m_previewStage->GetRootLayer()));
|
||||||
// Pin the up axis rather than inheriting the site fallback: the light
|
// Pin the up axis rather than inheriting the site fallback: the light
|
||||||
// rig, dome-pole compensation, and camera framing all assume Y-up.
|
// rig, dome-pole compensation, and camera framing all assume Y-up.
|
||||||
pxr::UsdGeomSetStageUpAxis(m_previewStage, pxr::UsdGeomTokens->y);
|
pxr::UsdGeomSetStageUpAxis(m_previewStage, pxr::UsdGeomTokens->y);
|
||||||
pxr::UsdGeomSphere sphere = pxr::UsdGeomSphere::Define(m_previewStage, kSpherePath);
|
|
||||||
sphere.GetRadiusAttr().Set(1.0);
|
// All selectable preview shapes live under one scope; the material is
|
||||||
|
// bound on the scope so it inherits to whichever shape is active, and
|
||||||
|
// ApplyPreviewShape keeps exactly one child active.
|
||||||
|
pxr::UsdGeomScope::Define(m_previewStage, kShapesRootPath);
|
||||||
|
|
||||||
|
// UV-mapped meshes (not implicit UsdGeom prims) so textured materials —
|
||||||
|
// e.g. a UsdUVTexture wired into diffuseColor via a UsdPrimvarReader "st" —
|
||||||
|
// map onto the preview geometry instead of sampling a single texel. The
|
||||||
|
// implicit primitives generate no `st` primvar, so they can't show textures.
|
||||||
|
CreateUvSphere(m_previewStage, kShapesRootPath.AppendChild(pxr::TfToken("Sphere")), 1.0);
|
||||||
|
CreateUvCube(m_previewStage, kShapesRootPath.AppendChild(pxr::TfToken("Cube")), 0.6);
|
||||||
|
CreateUvCylinder(m_previewStage, kShapesRootPath.AppendChild(pxr::TfToken("Cylinder")), 0.7, 0.8);
|
||||||
|
|
||||||
|
// Asset-backed shapes, created only when their file is bundled.
|
||||||
|
for (const char* name : {"Teapot", "Hair"}) {
|
||||||
|
const std::string file = ResourcePath(
|
||||||
|
std::string("resources/preview/") + pxr::TfStringToLower(name) + ".usdc");
|
||||||
|
if (!std::filesystem::exists(file)) continue;
|
||||||
|
pxr::UsdPrim prim = m_previewStage->DefinePrim(
|
||||||
|
kShapesRootPath.AppendChild(pxr::TfToken(name)));
|
||||||
|
prim.GetReferences().AddReference(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string vdbFile = ResourcePath("resources/vdb/bunny_cloud.vdb");
|
||||||
|
if (std::filesystem::exists(vdbFile)) {
|
||||||
|
pxr::UsdVolVolume cloud = pxr::UsdVolVolume::Define(
|
||||||
|
m_previewStage, kShapesRootPath.AppendChild(pxr::TfToken("Cloud")));
|
||||||
|
pxr::UsdVolOpenVDBAsset density = pxr::UsdVolOpenVDBAsset::Define(
|
||||||
|
m_previewStage, cloud.GetPath().AppendChild(pxr::TfToken("density")));
|
||||||
|
density.CreateFilePathAttr().Set(pxr::SdfAssetPath(vdbFile));
|
||||||
|
density.CreateFieldNameAttr().Set(pxr::TfToken("density"));
|
||||||
|
cloud.CreateFieldRelationship(pxr::TfToken("density"), density.GetPath());
|
||||||
|
}
|
||||||
|
|
||||||
// Storm renders fine with zero authored lights (it injects a GL headlight
|
// Storm renders fine with zero authored lights (it injects a GL headlight
|
||||||
// as a fallback), but proper Hydra delegates like Embree/Arnold shade
|
// as a fallback), but proper Hydra delegates like Embree/Arnold shade
|
||||||
@@ -78,10 +320,14 @@ void MaterialPreviewRenderer::EnsureInitialized() {
|
|||||||
m_renderer.SetComplexity(1.3f);
|
m_renderer.SetComplexity(1.3f);
|
||||||
|
|
||||||
ApplyLightPreset(m_lightPreset);
|
ApplyLightPreset(m_lightPreset);
|
||||||
|
ApplyPreviewShape(m_previewShape); // activates one shape + frames the camera
|
||||||
|
|
||||||
m_previewBBox = pxr::GfBBox3d(m_renderer.ComputeStageBounds());
|
// Maya-style default 3/4 view: orbit the framed camera ~45° around and
|
||||||
m_camera.FrameSelection(m_previewBBox, 1.6); // extra margin so the sphere doesn't touch the frame edge
|
// ~25° down instead of the dead-on front view (subsequent shape switches
|
||||||
m_renderer.SetCameraStateFromGfCamera(m_camera.ComputeGfCamera(m_previewBBox));
|
// keep whatever orientation the user has orbited to).
|
||||||
|
m_camera.Tumble(-45.0, 25.0);
|
||||||
|
m_renderer.SetCameraStateFromGfCamera(
|
||||||
|
m_camera.ComputeGfCamera(m_previewBBox, /*autoClip=*/true));
|
||||||
|
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
@@ -112,39 +358,37 @@ void MaterialPreviewRenderer::SetMaterial(const pxr::UsdStageRefPtr& sourceStage
|
|||||||
// materials can be spread across sublayers. Referencing at the identical
|
// materials can be spread across sublayers. Referencing at the identical
|
||||||
// path keeps internal connection paths valid without remapping, and
|
// path keeps internal connection paths valid without remapping, and
|
||||||
// relative asset paths keep resolving against their original layers.
|
// relative asset paths keep resolving against their original layers.
|
||||||
pxr::UsdPrim mirrorPrim = m_previewStage->OverridePrim(materialPath);
|
// Authored only when the material changes: the shared in-memory layers
|
||||||
if (!mirrorPrim) {
|
// recompose live, so revision bumps need no re-referencing.
|
||||||
LOG_ERROR("MaterialPreviewRenderer: failed to create override at " + materialPath.GetString());
|
if (materialPath != m_materialPath) {
|
||||||
return;
|
pxr::UsdPrim mirrorPrim = m_previewStage->OverridePrim(materialPath);
|
||||||
|
if (!mirrorPrim) {
|
||||||
|
LOG_ERROR("MaterialPreviewRenderer: failed to create override at " + materialPath.GetString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mirrorPrim.GetReferences().ClearReferences();
|
||||||
|
mirrorPrim.GetReferences().AddReference(
|
||||||
|
sourceStage->GetRootLayer()->GetIdentifier(), materialPath);
|
||||||
}
|
}
|
||||||
mirrorPrim.GetReferences().ClearReferences();
|
|
||||||
mirrorPrim.GetReferences().AddReference(
|
|
||||||
sourceStage->GetRootLayer()->GetIdentifier(), materialPath);
|
|
||||||
|
|
||||||
pxr::UsdPrim spherePrim = m_previewStage->GetPrimAtPath(kSpherePath);
|
pxr::UsdPrim shapesRoot = m_previewStage->GetPrimAtPath(kShapesRootPath);
|
||||||
pxr::UsdShadeMaterial material(m_previewStage->GetPrimAtPath(materialPath));
|
pxr::UsdShadeMaterial material(m_previewStage->GetPrimAtPath(materialPath));
|
||||||
if (!material) {
|
if (!material) {
|
||||||
LOG_WARNING("MaterialPreviewRenderer: no material composed at " + materialPath.GetString());
|
LOG_WARNING("MaterialPreviewRenderer: no material composed at " + materialPath.GetString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (spherePrim)
|
if (shapesRoot) // bound on the scope: inherits to whichever shape is active
|
||||||
pxr::UsdShadeMaterialBindingAPI::Apply(spherePrim).Bind(material);
|
pxr::UsdShadeMaterialBindingAPI::Apply(shapesRoot).Bind(material);
|
||||||
|
|
||||||
// ── Selected-node preview (Hypershade-style) ─────────────────────────
|
// ── Selected-node preview (Hypershade-style) ─────────────────────────
|
||||||
// Wipe any override left by a previous selection: the scratch wrapper
|
// All node-preview overrides (the scratch wrapper shader and the local
|
||||||
// shader and the local opinions on the material's surface outputs, all
|
// opinions on the material's surface outputs) live exclusively in the
|
||||||
// of which live only in the scratch stage's root layer.
|
// preview stage's session layer, which is stronger than the root layer's
|
||||||
pxr::SdfLayerHandle rootLayer = m_previewStage->GetRootLayer();
|
// reference. Wiping them is then just clearing that layer — no per-spec
|
||||||
if (pxr::SdfPrimSpecHandle matSpec = rootLayer->GetPrimAtPath(materialPath)) {
|
// surgery (RemoveNameChild/RemoveProperty) that could interact badly with
|
||||||
if (pxr::SdfPrimSpecHandle wrapSpec =
|
// Sdf's inert-spec cleanup, and no stale overrides on other materials.
|
||||||
rootLayer->GetPrimAtPath(materialPath.AppendChild(kNodePreviewShaderName)))
|
pxr::SdfLayerHandle sessionLayer = m_previewStage->GetSessionLayer();
|
||||||
matSpec->RemoveNameChild(wrapSpec);
|
sessionLayer->Clear();
|
||||||
for (const char* outName : {"outputs:surface", "outputs:mtlx:surface"}) {
|
|
||||||
if (pxr::SdfPropertySpecHandle prop =
|
|
||||||
rootLayer->GetPropertyAtPath(materialPath.AppendProperty(pxr::TfToken(outName))))
|
|
||||||
matSpec->RemoveProperty(prop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Route the material's surface through the selected node. Nodes outside
|
// Route the material's surface through the selected node. Nodes outside
|
||||||
// the material's subtree (possible in hand-authored cross-scope networks)
|
// the material's subtree (possible in hand-authored cross-scope networks)
|
||||||
@@ -154,6 +398,8 @@ void MaterialPreviewRenderer::SetMaterial(const pxr::UsdStageRefPtr& sourceStage
|
|||||||
previewNodePath.HasPrefix(materialPath)) {
|
previewNodePath.HasPrefix(materialPath)) {
|
||||||
pxr::UsdShadeShader nodeShader(m_previewStage->GetPrimAtPath(previewNodePath));
|
pxr::UsdShadeShader nodeShader(m_previewStage->GetPrimAtPath(previewNodePath));
|
||||||
if (nodeShader) {
|
if (nodeShader) {
|
||||||
|
pxr::UsdEditContext sessionEdit(m_previewStage,
|
||||||
|
pxr::UsdEditTarget(sessionLayer));
|
||||||
pxr::UsdShadeConnectableAPI sourceApi = nodeShader.ConnectableAPI();
|
pxr::UsdShadeConnectableAPI sourceApi = nodeShader.ConnectableAPI();
|
||||||
pxr::TfToken sourceName(previewNodeOutput);
|
pxr::TfToken sourceName(previewNodeOutput);
|
||||||
if (!previewOutputIsTerminal) {
|
if (!previewOutputIsTerminal) {
|
||||||
@@ -215,6 +461,47 @@ void MaterialPreviewRenderer::ApplyLightPreset(int index) {
|
|||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MaterialPreviewRenderer::ApplyPreviewShape(int index) {
|
||||||
|
if (index < 0 || index >= kShapePresetCount) index = 0;
|
||||||
|
// Fall back to the sphere if the requested shape's asset wasn't bundled.
|
||||||
|
if (!m_previewStage->GetPrimAtPath(
|
||||||
|
kShapesRootPath.AppendChild(pxr::TfToken(kShapePresets[index].primName))))
|
||||||
|
index = 0;
|
||||||
|
m_previewShape = index;
|
||||||
|
|
||||||
|
for (int i = 0; i < kShapePresetCount; ++i) {
|
||||||
|
pxr::UsdPrim prim = m_previewStage->GetPrimAtPath(
|
||||||
|
kShapesRootPath.AppendChild(pxr::TfToken(kShapePresets[i].primName)));
|
||||||
|
if (prim)
|
||||||
|
prim.SetActive(i == index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shapes differ wildly in size (the cloud VDB spans hundreds of units) —
|
||||||
|
// reframe on the active shape's bounds; inactive prims don't contribute.
|
||||||
|
m_previewBBox = pxr::GfBBox3d(m_renderer.ComputeStageBounds());
|
||||||
|
m_camera.FrameSelection(m_previewBBox, kShapePresets[index].frameFit);
|
||||||
|
m_renderer.SetCameraStateFromGfCamera(
|
||||||
|
m_camera.ComputeGfCamera(m_previewBBox, /*autoClip=*/true));
|
||||||
|
m_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaterialPreviewRenderer::RenderShapeDropdown(float width) {
|
||||||
|
if (!m_initialized) return;
|
||||||
|
ImGui::SetNextItemWidth(width);
|
||||||
|
if (ImGui::BeginCombo("##PreviewShape", kShapePresets[m_previewShape].label)) {
|
||||||
|
for (int i = 0; i < kShapePresetCount; ++i) {
|
||||||
|
if (!m_previewStage->GetPrimAtPath(
|
||||||
|
kShapesRootPath.AppendChild(pxr::TfToken(kShapePresets[i].primName))))
|
||||||
|
continue; // asset not bundled
|
||||||
|
if (ImGui::Selectable(kShapePresets[i].label, i == m_previewShape) && i != m_previewShape)
|
||||||
|
ApplyPreviewShape(i);
|
||||||
|
}
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
ImGui::SetTooltip("Shader-ball preview geometry");
|
||||||
|
}
|
||||||
|
|
||||||
void MaterialPreviewRenderer::UpdateDomeOrientation() {
|
void MaterialPreviewRenderer::UpdateDomeOrientation() {
|
||||||
if (!m_previewStage) return;
|
if (!m_previewStage) return;
|
||||||
pxr::UsdPrim dome = m_previewStage->GetPrimAtPath(kDomeLightPath);
|
pxr::UsdPrim dome = m_previewStage->GetPrimAtPath(kDomeLightPath);
|
||||||
@@ -252,9 +539,9 @@ void MaterialPreviewRenderer::UpdateDomeOrientation() {
|
|||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaterialPreviewRenderer::RenderLightingDropdown() {
|
void MaterialPreviewRenderer::RenderLightingDropdown(float width) {
|
||||||
if (!m_initialized) return;
|
if (!m_initialized) return;
|
||||||
ImGui::SetNextItemWidth(-1.0f);
|
ImGui::SetNextItemWidth(width);
|
||||||
if (ImGui::BeginCombo("##PreviewLighting", kLightPresets[m_lightPreset].label)) {
|
if (ImGui::BeginCombo("##PreviewLighting", kLightPresets[m_lightPreset].label)) {
|
||||||
for (int i = 0; i < kLightPresetCount; ++i) {
|
for (int i = 0; i < kLightPresetCount; ++i) {
|
||||||
if (ImGui::Selectable(kLightPresets[i].label, i == m_lightPreset) && i != m_lightPreset)
|
if (ImGui::Selectable(kLightPresets[i].label, i == m_lightPreset) && i != m_lightPreset)
|
||||||
@@ -288,7 +575,8 @@ uint32_t MaterialPreviewRenderer::Render(int width, int height) {
|
|||||||
void MaterialPreviewRenderer::OrbitDrag(float deltaX, float deltaY) {
|
void MaterialPreviewRenderer::OrbitDrag(float deltaX, float deltaY) {
|
||||||
if (!m_initialized) return;
|
if (!m_initialized) return;
|
||||||
m_camera.Tumble(deltaX * 0.5, deltaY * 0.5); // matches the viewport's tumble sensitivity
|
m_camera.Tumble(deltaX * 0.5, deltaY * 0.5); // matches the viewport's tumble sensitivity
|
||||||
m_renderer.SetCameraStateFromGfCamera(m_camera.ComputeGfCamera(m_previewBBox));
|
m_renderer.SetCameraStateFromGfCamera(
|
||||||
|
m_camera.ComputeGfCamera(m_previewBBox, /*autoClip=*/true));
|
||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,13 +592,13 @@ void MaterialPreviewRenderer::SetColorCorrection(int ccMode, const std::string&
|
|||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaterialPreviewRenderer::RenderRendererDropdown() {
|
void MaterialPreviewRenderer::RenderRendererDropdown(float width) {
|
||||||
pxr::TfToken currentId = m_renderer.GetCurrentRendererId();
|
pxr::TfToken currentId = m_renderer.GetCurrentRendererId();
|
||||||
std::string displayName = currentId.IsEmpty()
|
std::string displayName = currentId.IsEmpty()
|
||||||
? "Renderer"
|
? "Renderer"
|
||||||
: UsdSceneRenderer::GetRendererDisplayName(currentId);
|
: UsdSceneRenderer::GetRendererDisplayName(currentId);
|
||||||
|
|
||||||
ImGui::Button(displayName.c_str(), ImVec2(-1.0f, 0.0f));
|
ImGui::Button(displayName.c_str(), ImVec2(width, 0.0f));
|
||||||
if (ImGui::IsItemHovered())
|
if (ImGui::IsItemHovered())
|
||||||
ImGui::SetTooltip("Shader-ball render delegate");
|
ImGui::SetTooltip("Shader-ball render delegate");
|
||||||
|
|
||||||
|
|||||||
@@ -50,16 +50,26 @@ public:
|
|||||||
const std::string& ocioLook);
|
const std::string& ocioLook);
|
||||||
|
|
||||||
/// Renderer-delegate picker UI (matches the viewport's dropdown pattern).
|
/// Renderer-delegate picker UI (matches the viewport's dropdown pattern).
|
||||||
void RenderRendererDropdown();
|
/// width: ImGui item width (-1 = fill available).
|
||||||
|
void RenderRendererDropdown(float width = -1.0f);
|
||||||
|
|
||||||
/// HDR-environment picker (External / Room / Interior / Sunset). Presets
|
/// HDR-environment picker (External / Room / Interior / Sunset). Presets
|
||||||
/// map to bundled resources/hdri/*.exr dome-light textures; a preset
|
/// map to bundled resources/hdri/*.exr dome-light textures; a preset
|
||||||
/// whose file is missing falls back to the distant key light.
|
/// whose file is missing falls back to the distant key light.
|
||||||
void RenderLightingDropdown();
|
/// width: ImGui item width (-1 = fill available).
|
||||||
|
void RenderLightingDropdown(float width = -1.0f);
|
||||||
|
|
||||||
|
/// Preview-geometry picker (Sphere / Cube / Cylinder / Teapot / Hair /
|
||||||
|
/// Cloud Volume). Shapes whose asset is missing are omitted.
|
||||||
|
/// width: ImGui item width (-1 = fill available).
|
||||||
|
void RenderShapeDropdown(float width = -1.0f);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void EnsureInitialized();
|
void EnsureInitialized();
|
||||||
void ApplyLightPreset(int index);
|
void ApplyLightPreset(int index);
|
||||||
|
/// Activates the chosen preview shape (deactivating the others) and
|
||||||
|
/// reframes the camera to its bounds.
|
||||||
|
void ApplyPreviewShape(int index);
|
||||||
/// Compensates the dome-light pole convention per render delegate: Storm
|
/// Compensates the dome-light pole convention per render delegate: Storm
|
||||||
/// samples with the pole along local +Y, spec-following delegates
|
/// samples with the pole along local +Y, spec-following delegates
|
||||||
/// (hdArnold, hdCycles) along local +Z. Called on preset and renderer
|
/// (hdArnold, hdCycles) along local +Z. Called on preset and renderer
|
||||||
@@ -76,6 +86,7 @@ private:
|
|||||||
pxr::SdfPath m_previewNodePath; // node whose output the ball previews (empty = whole material)
|
pxr::SdfPath m_previewNodePath; // node whose output the ball previews (empty = whole material)
|
||||||
std::string m_previewNodeOutput;
|
std::string m_previewNodeOutput;
|
||||||
int m_lightPreset = 0;
|
int m_lightPreset = 0;
|
||||||
|
int m_previewShape = 0;
|
||||||
bool m_initialized = false;
|
bool m_initialized = false;
|
||||||
bool m_dirty = true;
|
bool m_dirty = true;
|
||||||
int m_lastWidth = 0;
|
int m_lastWidth = 0;
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# pragma once
|
||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
|
namespace ax {
|
||||||
|
namespace Drawing {
|
||||||
|
|
||||||
|
enum class IconType { Flow, Circle, Square, Grid, RoundSquare, Diamond };
|
||||||
|
|
||||||
|
void DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor);
|
||||||
|
|
||||||
|
} // namespace Drawing
|
||||||
|
} // namespace ax
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,642 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Math 2D
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# if !defined(__AX_MATH_2D_INL__)
|
||||||
|
# define __AX_MATH_2D_INL__
|
||||||
|
# pragma once
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# if defined(__cplusplus)
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# include <utility>
|
||||||
|
# include <cmath>
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
template <typename T, typename P>
|
||||||
|
inline ax::basic_point<T> operator + (const ax::basic_size<P>& lhs, const ax::basic_point<T>& rhs)
|
||||||
|
{
|
||||||
|
return static_cast<ax::basic_point<T>>(lhs) + rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename P>
|
||||||
|
inline ax::basic_point<T> operator + (const ax::basic_point<T>& lhs, const ax::basic_size<P>& rhs)
|
||||||
|
{
|
||||||
|
return lhs + static_cast<ax::basic_point<T>>(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
template <typename T>
|
||||||
|
inline typename ax::basic_rect<T>::point_t ax::basic_rect<T>::get_closest_point(const point_t& p, bool on_edge, float radius) const
|
||||||
|
{
|
||||||
|
auto point = get_closest_point(p, on_edge);
|
||||||
|
|
||||||
|
const auto offset = p - point;
|
||||||
|
const auto distance_sq = offset.x * offset.x + offset.y * offset.y;
|
||||||
|
if (distance_sq <= 0)
|
||||||
|
return point;
|
||||||
|
|
||||||
|
const auto distance = sqrtf(static_cast<float>(distance_sq));
|
||||||
|
|
||||||
|
return point + static_cast<basic_point<T>>(std::min(distance, static_cast<float>(radius)) * static_cast<basic_point<float>>(offset) * (1.0f / distance));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline typename ax::basic_rect<T>::point_t ax::basic_rect<T>::get_closest_point(const basic_rect& r) const
|
||||||
|
{
|
||||||
|
point_t result;
|
||||||
|
if (r.left() >= right())
|
||||||
|
result.x = right();
|
||||||
|
else if (r.right() <= left())
|
||||||
|
result.x = left();
|
||||||
|
else
|
||||||
|
result.x = (std::max(left(), r.left()) + std::min(right(), r.right())) / 2;
|
||||||
|
|
||||||
|
if (r.top() >= bottom())
|
||||||
|
result.y = bottom();
|
||||||
|
else if (r.bottom() <= top())
|
||||||
|
result.y = top();
|
||||||
|
else
|
||||||
|
result.y = (std::max(top(), r.top()) + std::min(bottom(), r.bottom())) / 2;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline typename ax::basic_rect<T>::point_t ax::basic_rect<T>::get_closest_point_hollow(const point_t& p, T rounding, rect_region* region) const
|
||||||
|
{
|
||||||
|
rounding = std::min(rounding, std::min(w, h) / 2);
|
||||||
|
|
||||||
|
const auto inner = expanded(-rounding);
|
||||||
|
|
||||||
|
if (p.x <= inner.left() && p.y <= inner.top())
|
||||||
|
{
|
||||||
|
if (region) *region = rect_region::top_left;
|
||||||
|
return inner.top_left().followed(p + point_t(-1, -1), rounding);
|
||||||
|
}
|
||||||
|
else if (p.x >= inner.right() && p.y <= inner.top())
|
||||||
|
{
|
||||||
|
if (region) *region = rect_region::top_right;
|
||||||
|
return inner.top_right().followed(p + point_t(1, -1), rounding);
|
||||||
|
}
|
||||||
|
else if (p.x <= inner.left() && p.y >= inner.bottom())
|
||||||
|
{
|
||||||
|
if (region) *region = rect_region::bottom_left;
|
||||||
|
return inner.bottom_left().followed(p + point_t(-1, 1), rounding);
|
||||||
|
}
|
||||||
|
else if (p.x >= inner.right() && p.y >= inner.bottom())
|
||||||
|
{
|
||||||
|
if (region) *region = rect_region::bottom_right;
|
||||||
|
return inner.bottom_right().followed(p + point_t(1, 1), rounding);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (contains(p) || region)
|
||||||
|
{
|
||||||
|
const auto l_diff = abs(p.x - left());
|
||||||
|
const auto r_diff = abs(p.x - right());
|
||||||
|
const auto t_diff = abs(p.y - top());
|
||||||
|
const auto b_diff = abs(p.y - bottom());
|
||||||
|
|
||||||
|
if (l_diff <= r_diff && l_diff <= t_diff && l_diff <= b_diff)
|
||||||
|
{
|
||||||
|
if (region) *region = rect_region::left;
|
||||||
|
return point_t(left(), p.y);
|
||||||
|
}
|
||||||
|
else if (r_diff <= l_diff && r_diff <= t_diff && r_diff <= b_diff)
|
||||||
|
{
|
||||||
|
if (region) *region = rect_region::right;
|
||||||
|
return point_t(right(), p.y);
|
||||||
|
}
|
||||||
|
else if (t_diff <= l_diff && t_diff <= r_diff && t_diff <= b_diff)
|
||||||
|
{
|
||||||
|
if (region) *region = rect_region::top;
|
||||||
|
return point_t(p.x, top());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (region) *region = rect_region::bottom;
|
||||||
|
return point_t(p.x, bottom());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return get_closest_point(p, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline ax::basic_line<T> ax::basic_rect<T>::get_closest_line(const basic_rect& r) const
|
||||||
|
{
|
||||||
|
auto a = get_closest_point(r);
|
||||||
|
auto b = r.get_closest_point(*this);
|
||||||
|
|
||||||
|
auto distribute = [](T& a, T& b, T a0, T a1, T b0, T b1)
|
||||||
|
{
|
||||||
|
if (a0 >= b1 || a1 <= b0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto aw = a1 - a0;
|
||||||
|
const auto bw = b1 - b0;
|
||||||
|
|
||||||
|
if (aw > bw)
|
||||||
|
{
|
||||||
|
b = b0 + bw - bw * (a - a0) / aw;
|
||||||
|
a = b;
|
||||||
|
}
|
||||||
|
else if (aw < bw)
|
||||||
|
{
|
||||||
|
a = a0 + aw - aw * (b - b0) / bw;
|
||||||
|
b = a;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
distribute(a.x, b.x, left(), right(), r.left(), r.right());
|
||||||
|
distribute(a.y, b.y, top(), bottom(), r.top(), r.bottom());
|
||||||
|
|
||||||
|
return basic_line<T>(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline ax::basic_line<T> ax::basic_rect<T>::get_closest_line(const basic_rect& r, T radius) const
|
||||||
|
{
|
||||||
|
return get_closest_line(r, radius, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline ax::basic_line<T> ax::basic_rect<T>::get_closest_line(const basic_rect& r, T radius_a, T radius_b) const
|
||||||
|
{
|
||||||
|
auto line = get_closest_line(r);
|
||||||
|
if (radius_a < 0)
|
||||||
|
radius_a = 0;
|
||||||
|
if (radius_b < 0)
|
||||||
|
radius_b = 0;
|
||||||
|
|
||||||
|
if (radius_a == 0 && radius_b == 0)
|
||||||
|
return line;
|
||||||
|
|
||||||
|
const auto offset = line.b - line.a;
|
||||||
|
const auto length_sq = offset.x * offset.x + offset.y * offset.y;
|
||||||
|
const auto radius_a_sq = radius_a * radius_a;
|
||||||
|
const auto radius_b_sq = radius_b * radius_b;
|
||||||
|
|
||||||
|
if (length_sq <= 0)
|
||||||
|
return line;
|
||||||
|
|
||||||
|
const auto length = sqrtf(static_cast<float>(length_sq));
|
||||||
|
const auto direction = ax::basic_point<float>(offset.x / length, offset.y / length);
|
||||||
|
|
||||||
|
const auto total_radius_sq = radius_a_sq + radius_b_sq;
|
||||||
|
float radius_a_f = static_cast<float>(radius_a);
|
||||||
|
float radius_b_f = static_cast<float>(radius_b);
|
||||||
|
if (total_radius_sq > length_sq)
|
||||||
|
{
|
||||||
|
const auto scale = length / (radius_a_f + radius_b_f);
|
||||||
|
radius_a_f *= scale;
|
||||||
|
radius_b_f *= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
line.a = line.a + static_cast<point_t>(direction * radius_a_f);
|
||||||
|
line.b = line.b - static_cast<point_t>(direction * radius_b_f);
|
||||||
|
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
inline void ax::matrix::zero()
|
||||||
|
{
|
||||||
|
*this = matrix(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix::reset()
|
||||||
|
{
|
||||||
|
*this = matrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ax::matrix::invert()
|
||||||
|
{
|
||||||
|
const float det = (m11 * m22 - m21 * m12);
|
||||||
|
if (det == 0.0f)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const float invDet = 1.0f / det;
|
||||||
|
|
||||||
|
*this = matrix(
|
||||||
|
m22 * invDet,
|
||||||
|
-m12 * invDet,
|
||||||
|
-m21 * invDet,
|
||||||
|
m11 * invDet,
|
||||||
|
-(-m21 * m32 + m22 * m31) * invDet,
|
||||||
|
-( m11 * m32 - m12 * m31) * invDet);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix::translate(float x, float y, matrix_order order/* = matrix_order::prepend*/)
|
||||||
|
{
|
||||||
|
combine(matrix(1.0f, 0.0f, 0.0f, 1.0f, x, y), order);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix::rotate(float angle, matrix_order order/* = matrix_order::prepend*/)
|
||||||
|
{
|
||||||
|
float angleRad = angle * AX_PI / 180.0f;
|
||||||
|
|
||||||
|
const float c = cosf(angleRad);
|
||||||
|
const float s = sinf(angleRad);
|
||||||
|
|
||||||
|
combine(matrix(c, s, -s, c, 0.0f, 0.0f), order);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix::rotate_at(float angle, float cx, float cy, matrix_order order/* = matrix_order::prepend*/)
|
||||||
|
{
|
||||||
|
const auto angleRad = angle * AX_PI / 180.0f;
|
||||||
|
|
||||||
|
const auto c = cosf(angleRad);
|
||||||
|
const auto s = sinf(angleRad);
|
||||||
|
|
||||||
|
combine(matrix(c, s, -s, c,
|
||||||
|
-cx * c - cy * -s + cx,
|
||||||
|
-cx * s - cy * c + cy), order);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix::scale(float x, float y, matrix_order order/* = matrix_order::prepend*/)
|
||||||
|
{
|
||||||
|
combine(matrix(x, 0.0f, 0.0f, y, 0.0f, 0.0f), order);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix::shear(float x, float y, matrix_order order/* = matrix_order::prepend*/)
|
||||||
|
{
|
||||||
|
combine(matrix(1.0f, y, x, 1.0f, 0.0f, 0.0f), order);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix::combine(const matrix& matrix, matrix_order order/* = matrix_order::prepend*/)
|
||||||
|
{
|
||||||
|
if (order == matrix_order::set)
|
||||||
|
{
|
||||||
|
if (this != &matrix)
|
||||||
|
*this = matrix;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* am = this;
|
||||||
|
const auto* bm = &matrix;
|
||||||
|
|
||||||
|
if (order == matrix_order::append)
|
||||||
|
{
|
||||||
|
using std::swap;
|
||||||
|
swap(am, bm);
|
||||||
|
}
|
||||||
|
|
||||||
|
*this = ax::matrix(
|
||||||
|
am->m11 * bm->m11 + am->m21 * bm->m12,
|
||||||
|
bm->m11 * am->m12 + bm->m12 * am->m22,
|
||||||
|
bm->m21 * am->m11 + bm->m22 * am->m21,
|
||||||
|
am->m12 * bm->m21 + am->m22 * bm->m22,
|
||||||
|
bm->m31 * am->m11 + bm->m32 * am->m21 + am->m31,
|
||||||
|
bm->m31 * am->m12 + bm->m32 * am->m22 + am->m32);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ax::matrix ax::matrix::inverted() const
|
||||||
|
{
|
||||||
|
matrix inverted = *this;
|
||||||
|
inverted.invert();
|
||||||
|
return inverted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
inline void ax::matrix4::zero()
|
||||||
|
{
|
||||||
|
*this = matrix4(
|
||||||
|
0.0f, 0.0f, 0.0f, 0.0f,
|
||||||
|
0.0f, 0.0f, 0.0f, 0.0f,
|
||||||
|
0.0f, 0.0f, 0.0f, 0.0f,
|
||||||
|
0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix4::reset()
|
||||||
|
{
|
||||||
|
*this = matrix4();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ax::matrix4::invert()
|
||||||
|
{
|
||||||
|
# if 0
|
||||||
|
// wild magic 4 inverse - http://www.geometrictools.com/Documentation/LaplaceExpansionTheorem.pdf
|
||||||
|
// 84 multiplications
|
||||||
|
// 66 adds/subs
|
||||||
|
// 1 division
|
||||||
|
|
||||||
|
// 24
|
||||||
|
const float fA0 = m11 * m22 - m12 * m21;
|
||||||
|
const float fA1 = m11 * m23 - m13 * m21;
|
||||||
|
const float fA2 = m11 * m24 - m14 * m21;
|
||||||
|
const float fA3 = m12 * m23 - m13 * m22;
|
||||||
|
const float fA4 = m12 * m24 - m14 * m22;
|
||||||
|
const float fA5 = m13 * m24 - m14 * m23;
|
||||||
|
const float fB0 = m31 * m42 - m32 * m41;
|
||||||
|
const float fB1 = m31 * m43 - m33 * m41;
|
||||||
|
const float fB2 = m31 * m44 - m34 * m41;
|
||||||
|
const float fB3 = m32 * m43 - m33 * m42;
|
||||||
|
const float fB4 = m32 * m44 - m34 * m42;
|
||||||
|
const float fB5 = m33 * m44 - m34 * m43;
|
||||||
|
|
||||||
|
// 6
|
||||||
|
const float det = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;
|
||||||
|
|
||||||
|
if (det == 0.0f)
|
||||||
|
{
|
||||||
|
zero();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float invDet = 1.0f / det;
|
||||||
|
|
||||||
|
// 36 + 16
|
||||||
|
*this = Matrix4(
|
||||||
|
( m22 * fB5 - m23 * fB4 + m24 * fB3) * invDet,
|
||||||
|
(-m12 * fB5 + m13 * fB4 - m14 * fB3) * invDet,
|
||||||
|
( m42 * fA5 - m43 * fA4 + m44 * fA3) * invDet,
|
||||||
|
(-m32 * fA5 + m33 * fA4 - m34 * fA3) * invDet,
|
||||||
|
(-m21 * fB5 + m23 * fB2 - m24 * fB1) * invDet,
|
||||||
|
( m11 * fB5 - m13 * fB2 + m14 * fB1) * invDet,
|
||||||
|
(-m41 * fA5 + m43 * fA2 - m44 * fA1) * invDet,
|
||||||
|
( m31 * fA5 - m33 * fA2 + m34 * fA1) * invDet,
|
||||||
|
( m21 * fB4 - m22 * fB2 + m24 * fB0) * invDet,
|
||||||
|
(-m11 * fB4 + m12 * fB2 - m14 * fB0) * invDet,
|
||||||
|
( m41 * fA4 - m42 * fA2 + m44 * fA0) * invDet,
|
||||||
|
(-m31 * fA4 + m32 * fA2 - m34 * fA0) * invDet,
|
||||||
|
(-m21 * fB3 + m22 * fB1 - m23 * fB0) * invDet,
|
||||||
|
( m11 * fB3 - m12 * fB1 + m13 * fB0) * invDet,
|
||||||
|
(-m41 * fA3 + m42 * fA1 - m43 * fA0) * invDet,
|
||||||
|
( m31 * fA3 - m32 * fA1 + m33 * fA0) * invDet);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
# else
|
||||||
|
// 214 multiplications
|
||||||
|
// 80 adds/subs
|
||||||
|
// 1 division
|
||||||
|
const float d00 = m22 * m33 * m44 + m32 * m43 * m24 + m42 * m23 * m34 - m24 * m33 * m42 - m34 * m43 * m22 - m44 * m23 * m32;
|
||||||
|
const float d01 = m12 * m33 * m44 + m32 * m43 * m14 + m42 * m13 * m34 - m14 * m33 * m42 - m34 * m43 * m12 - m44 * m13 * m32;
|
||||||
|
const float d02 = m12 * m23 * m44 + m22 * m43 * m14 + m42 * m13 * m24 - m14 * m23 * m42 - m24 * m43 * m12 - m44 * m13 * m22;
|
||||||
|
const float d03 = m12 * m23 * m34 + m22 * m33 * m14 + m32 * m13 * m24 - m14 * m23 * m32 - m24 * m33 * m12 - m34 * m13 * m22;
|
||||||
|
|
||||||
|
const float d10 = m21 * m33 * m44 + m31 * m43 * m24 + m41 * m23 * m34 - m24 * m33 * m41 - m34 * m43 * m21 - m44 * m23 * m31;
|
||||||
|
const float d11 = m11 * m33 * m44 + m31 * m43 * m14 + m41 * m13 * m34 - m14 * m33 * m41 - m34 * m43 * m11 - m44 * m13 * m31;
|
||||||
|
const float d12 = m11 * m23 * m44 + m21 * m43 * m14 + m41 * m13 * m24 - m14 * m23 * m41 - m24 * m43 * m11 - m44 * m13 * m21;
|
||||||
|
const float d13 = m11 * m23 * m34 + m21 * m33 * m14 + m31 * m13 * m24 - m14 * m23 * m31 - m24 * m33 * m11 - m34 * m13 * m21;
|
||||||
|
|
||||||
|
const float d20 = m21 * m32 * m44 + m31 * m42 * m24 + m41 * m22 * m34 - m24 * m32 * m41 - m34 * m42 * m21 - m44 * m22 * m31;
|
||||||
|
const float d21 = m11 * m32 * m44 + m31 * m42 * m14 + m41 * m12 * m34 - m14 * m32 * m41 - m34 * m42 * m11 - m44 * m12 * m31;
|
||||||
|
const float d22 = m11 * m22 * m44 + m21 * m42 * m14 + m41 * m12 * m24 - m14 * m22 * m41 - m24 * m42 * m11 - m44 * m12 * m21;
|
||||||
|
const float d23 = m11 * m22 * m34 + m21 * m32 * m14 + m31 * m12 * m24 - m14 * m22 * m31 - m24 * m32 * m11 - m34 * m12 * m21;
|
||||||
|
|
||||||
|
const float d30 = m21 * m32 * m43 + m31 * m42 * m23 + m41 * m22 * m33 - m23 * m32 * m41 - m33 * m42 * m21 - m43 * m22 * m31;
|
||||||
|
const float d31 = m11 * m32 * m43 + m31 * m42 * m13 + m41 * m12 * m33 - m13 * m32 * m41 - m33 * m42 * m11 - m43 * m12 * m31;
|
||||||
|
const float d32 = m11 * m22 * m43 + m21 * m42 * m13 + m41 * m12 * m23 - m13 * m22 * m41 - m23 * m42 * m11 - m43 * m12 * m21;
|
||||||
|
const float d33 = m11 * m22 * m33 + m21 * m32 * m13 + m31 * m12 * m23 - m13 * m22 * m31 - m23 * m32 * m11 - m33 * m12 * m21;
|
||||||
|
|
||||||
|
const float det = m11 * d00 - m21 * d01 + m31 * d02 - m41 * d03;
|
||||||
|
|
||||||
|
if (det == 0.0f)
|
||||||
|
{
|
||||||
|
zero();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float invDet = 1.0f / det;
|
||||||
|
|
||||||
|
m11 = d00 * invDet; m21 = -d10 * invDet; m31 = d20 * invDet; m41 = -d30 * invDet;
|
||||||
|
m12 = -d01 * invDet; m22 = d11 * invDet; m32 = -d21 * invDet; m42 = d31 * invDet;
|
||||||
|
m13 = d02 * invDet; m23 = -d12 * invDet; m33 = d22 * invDet; m43 = -d32 * invDet;
|
||||||
|
m14 = -d03 * invDet; m24 = d13 * invDet; m34 = -d23 * invDet; m44 = d33 * invDet;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix4::transpose()
|
||||||
|
{
|
||||||
|
using std::swap;
|
||||||
|
|
||||||
|
swap(m12, m21);
|
||||||
|
swap(m13, m31);
|
||||||
|
swap(m14, m41);
|
||||||
|
swap(m23, m32);
|
||||||
|
swap(m24, m42);
|
||||||
|
swap(m34, m43);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix4::translate(float x, float y, float z, matrix_order order/* = matrix_order::prepend*/)
|
||||||
|
{
|
||||||
|
combine(matrix4(
|
||||||
|
1.0f, 0.0f, 0.0f, 0.0f,
|
||||||
|
0.0f, 1.0f, 0.0f, 0.0f,
|
||||||
|
0.0f, 0.0f, 1.0f, 0.0f,
|
||||||
|
x, y, z, 1.0f), order);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix4::rotate_x(float angle, matrix_order order/* = matrix_order::prepend*/)
|
||||||
|
{
|
||||||
|
const auto angleRad = angle * AX_PI / 180.0f;
|
||||||
|
|
||||||
|
const auto c = cosf(angleRad);
|
||||||
|
const auto s = sinf(angleRad);
|
||||||
|
|
||||||
|
combine(matrix4(
|
||||||
|
1.0f, 0.0f, 0.0f, 0.0f,
|
||||||
|
0.0f, c, -s, 0.0f,
|
||||||
|
0.0f, s, c, 0.0f,
|
||||||
|
0.0f, 0.0f, 0.0f, 1.0f), order);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix4::rotate_y(float angle, matrix_order order/* = matrix_order::prepend*/)
|
||||||
|
{
|
||||||
|
const auto angleRad = angle * AX_PI / 180.0f;
|
||||||
|
|
||||||
|
const auto c = cosf(angleRad);
|
||||||
|
const auto s = sinf(angleRad);
|
||||||
|
|
||||||
|
combine(matrix4(
|
||||||
|
c, 0.0f, -s, 0.0f,
|
||||||
|
0.0f, 1.0f, 0.0f, 0.0f,
|
||||||
|
s, 0.0f, c, 0.0f,
|
||||||
|
0.0f, 0.0f, 0.0f, 1.0f), order);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix4::rotate_z(float angle, matrix_order order/* = matrix_order::prepend*/)
|
||||||
|
{
|
||||||
|
const auto angleRad = angle * AX_PI / 180.0f;
|
||||||
|
|
||||||
|
const auto c = cosf(angleRad);
|
||||||
|
const auto s = sinf(angleRad);
|
||||||
|
|
||||||
|
combine(matrix4(
|
||||||
|
c, -s, 0.0f, 0.0f,
|
||||||
|
s, c, 0.0f, 0.0f,
|
||||||
|
0.0f, 0.0f, 1.0f, 0.0f,
|
||||||
|
0.0f, 0.0f, 0.0f, 1.0f), order);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix4::rotate_axis(float angle, float x, float y, float z, matrix_order order/* = matrix_order::prepend*/)
|
||||||
|
{
|
||||||
|
const auto angleRad = angle * AX_PI / 180.0f;
|
||||||
|
|
||||||
|
const auto c = cosf(angleRad);
|
||||||
|
const auto s = sinf(angleRad);
|
||||||
|
|
||||||
|
const auto rc = 1.0f - c;
|
||||||
|
|
||||||
|
combine(matrix4(
|
||||||
|
(rc * x * x) + c, (rc * x * y) + (z * s), (rc * x * z) - (y * s), 0.0f,
|
||||||
|
(rc * x * y) - (z * s), (rc * y * y) + c, (rc * z * y) + (x * s), 0.0f,
|
||||||
|
(rc * x * z) + (y * s), (rc * y * z) - (x * s), (rc * z * z) + c, 0.0f,
|
||||||
|
0.0f, 0.0f, 0.0f, 1.0f), order);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix4::scale(float x, float y, float z, matrix_order order/* = matrix_order::prepend*/)
|
||||||
|
{
|
||||||
|
combine(matrix4(
|
||||||
|
x, 0.0f, 0.0f, 0.0f,
|
||||||
|
0.0f, y, 0.0f, 0.0f,
|
||||||
|
0.0f, 0.0f, z, 0.0f,
|
||||||
|
0.0f, 0.0f, 0.0f, 1.0f), order);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ax::matrix4::combine(const matrix4& matrix, matrix_order order/* = matrix_order::prepend*/)
|
||||||
|
{
|
||||||
|
if (order == matrix_order::set)
|
||||||
|
{
|
||||||
|
if (this != &matrix)
|
||||||
|
*this = matrix;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* am = this;
|
||||||
|
const auto* bm = &matrix;
|
||||||
|
|
||||||
|
if (order == matrix_order::append)
|
||||||
|
{
|
||||||
|
using std::swap;
|
||||||
|
swap(am, bm);
|
||||||
|
}
|
||||||
|
|
||||||
|
*this = matrix4(
|
||||||
|
am->m11 * bm->m11 + am->m21 * bm->m12 + am->m31 * bm->m13 + am->m41 * bm->m14,
|
||||||
|
am->m12 * bm->m11 + am->m22 * bm->m12 + am->m32 * bm->m13 + am->m42 * bm->m14,
|
||||||
|
am->m13 * bm->m11 + am->m23 * bm->m12 + am->m33 * bm->m13 + am->m43 * bm->m14,
|
||||||
|
am->m14 * bm->m11 + am->m24 * bm->m12 + am->m34 * bm->m13 + am->m44 * bm->m14,
|
||||||
|
|
||||||
|
am->m11 * bm->m21 + am->m21 * bm->m22 + am->m31 * bm->m23 + am->m41 * bm->m24,
|
||||||
|
am->m12 * bm->m21 + am->m22 * bm->m22 + am->m32 * bm->m23 + am->m42 * bm->m24,
|
||||||
|
am->m13 * bm->m21 + am->m23 * bm->m22 + am->m33 * bm->m23 + am->m43 * bm->m24,
|
||||||
|
am->m14 * bm->m21 + am->m24 * bm->m22 + am->m34 * bm->m23 + am->m44 * bm->m24,
|
||||||
|
|
||||||
|
am->m11 * bm->m31 + am->m21 * bm->m32 + am->m31 * bm->m33 + am->m41 * bm->m34,
|
||||||
|
am->m12 * bm->m31 + am->m22 * bm->m32 + am->m32 * bm->m33 + am->m42 * bm->m34,
|
||||||
|
am->m13 * bm->m31 + am->m23 * bm->m32 + am->m33 * bm->m33 + am->m43 * bm->m34,
|
||||||
|
am->m14 * bm->m31 + am->m24 * bm->m32 + am->m34 * bm->m33 + am->m44 * bm->m34,
|
||||||
|
|
||||||
|
am->m11 * bm->m41 + am->m21 * bm->m42 + am->m31 * bm->m43 + am->m41 * bm->m44,
|
||||||
|
am->m12 * bm->m41 + am->m22 * bm->m42 + am->m32 * bm->m43 + am->m42 * bm->m44,
|
||||||
|
am->m13 * bm->m41 + am->m23 * bm->m42 + am->m33 * bm->m43 + am->m43 * bm->m44,
|
||||||
|
am->m14 * bm->m41 + am->m24 * bm->m42 + am->m34 * bm->m43 + am->m44 * bm->m44);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ax::matrix4 ax::matrix4::inverted() const
|
||||||
|
{
|
||||||
|
matrix4 inverted = *this;
|
||||||
|
inverted.invert();
|
||||||
|
return inverted;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ax::matrix4 ax::matrix4::transposed() const
|
||||||
|
{
|
||||||
|
matrix4 transposed = *this;
|
||||||
|
transposed.transpose();
|
||||||
|
return transposed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
namespace ax {
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
# if defined(__clang__) || defined(__GNUC__)
|
||||||
|
template <typename M, typename T>
|
||||||
|
void transform_points(const M& m, basic_point<T>* points, size_t count);
|
||||||
|
# else
|
||||||
|
template <typename M, typename T>
|
||||||
|
inline void transform_points(const M& m, basic_point<T>* points, size_t count)
|
||||||
|
{
|
||||||
|
// UsdLayerManager local patch: static_assert(false, ...) here is
|
||||||
|
// ill-formed regardless of instantiation (its condition doesn't depend
|
||||||
|
// on a template parameter) — stricter MSVC (/permissive-, set for MSVC
|
||||||
|
// >= 1910 in this project's CMakeLists.txt) diagnoses it just from this
|
||||||
|
// primary template's declaration, even when every real call resolves to
|
||||||
|
// one of the matrix/matrix4 specializations below and this fallback is
|
||||||
|
// never instantiated. Make the condition depend on T so it only fires
|
||||||
|
// if something actually instantiates this unspecialized fallback.
|
||||||
|
static_assert(sizeof(T) == 0, "This combination of matrix type and point type is not supported");
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
|
template <typename M, typename T>
|
||||||
|
inline void transform_vectors(const M& m, basic_point<T>* points, size_t count)
|
||||||
|
{
|
||||||
|
typedef basic_point<T> point_t;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; ++i, ++points)
|
||||||
|
{
|
||||||
|
auto x = m.m11 * points->x + m.m21 * points->y;
|
||||||
|
auto y = m.m12 * points->x + m.m22 * points->y;
|
||||||
|
|
||||||
|
points->x = static_cast<typename point_t::value_type>(x);
|
||||||
|
points->y = static_cast<typename point_t::value_type>(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline void transform_points(const matrix& m, basic_point<T>* points, size_t count)
|
||||||
|
{
|
||||||
|
typedef basic_point<T> point_t;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; ++i, ++points)
|
||||||
|
{
|
||||||
|
auto x = m.m11 * points->x + m.m21 * points->y + m.m31;
|
||||||
|
auto y = m.m12 * points->x + m.m22 * points->y + m.m32;
|
||||||
|
|
||||||
|
points->x = static_cast<typename point_t::value_type>(x);
|
||||||
|
points->y = static_cast<typename point_t::value_type>(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline void transform_points(const matrix4& m, basic_point<T>* points, size_t count)
|
||||||
|
{
|
||||||
|
typedef basic_point<T> point_t;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; ++i, ++points)
|
||||||
|
{
|
||||||
|
auto x = m.m11 * points->x + m.m21 * points->y + m.m41;
|
||||||
|
auto y = m.m12 * points->x + m.m22 * points->y + m.m42;
|
||||||
|
|
||||||
|
points->x = static_cast<typename point_t::value_type>(x);
|
||||||
|
points->y = static_cast<typename point_t::value_type>(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
} // namespace ax
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# endif // defined(__cplusplus)
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# endif // __AX_MATH_2D_INL__
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "Drawing.h"
|
||||||
|
|
||||||
|
namespace ax {
|
||||||
|
namespace Widgets {
|
||||||
|
|
||||||
|
using Drawing::IconType;
|
||||||
|
|
||||||
|
void Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color = ImVec4(1, 1, 1, 1), const ImVec4& innerColor = ImVec4(0, 0, 0, 0));
|
||||||
|
|
||||||
|
} // namespace Widgets
|
||||||
|
} // namespace ax
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// LICENSE
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
//
|
||||||
|
// CREDITS
|
||||||
|
// Written by Michal Cichon
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# pragma once
|
||||||
|
# include "imgui.h"
|
||||||
|
# include "ax/Math2D.h"
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
//namespace ax {
|
||||||
|
//namespace ImGuiInterop {
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Modern ImGui (1.89.4+) already defines these operators in imgui.h itself
|
||||||
|
// when IMGUI_DEFINE_MATH_OPERATORS is set before its first include — guard
|
||||||
|
// against redefinition against this project's ImGui version.
|
||||||
|
# ifndef IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED
|
||||||
|
static inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; }
|
||||||
|
static inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; }
|
||||||
|
static inline ImVec2 operator+(const ImVec2& lhs) { return ImVec2( lhs.x, lhs.y); }
|
||||||
|
static inline ImVec2 operator-(const ImVec2& lhs) { return ImVec2(-lhs.x, -lhs.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, float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); }
|
||||||
|
static inline ImVec2 operator*(float lhs, const ImVec2& rhs) { return ImVec2(lhs * rhs.x, lhs * rhs.y); }
|
||||||
|
# endif
|
||||||
|
|
||||||
|
static inline int roundi(float value) { return static_cast<int>(value); }
|
||||||
|
static inline ax::point to_point(const ImVec2& value) { return ax::point(roundi(value.x), roundi(value.y)); }
|
||||||
|
static inline ax::pointf to_pointf(const ImVec2& value) { return ax::pointf(value.x, value.y); }
|
||||||
|
static inline ax::pointf to_pointf(const ax::point& value) { return ax::pointf(static_cast<float>(value.x), static_cast<float>(value.y)); }
|
||||||
|
static inline ax::size to_size (const ImVec2& value) { return ax::size (roundi(value.x), roundi(value.y)); }
|
||||||
|
static inline ax::sizef to_sizef(const ImVec2& value) { return ax::sizef(value.x, value.y); }
|
||||||
|
static inline ImVec2 to_imvec(const ax::point& value) { return ImVec2(static_cast<float>(value.x), static_cast<float>(value.y)); }
|
||||||
|
static inline ImVec2 to_imvec(const ax::pointf& value) { return ImVec2(value.x, value.y); }
|
||||||
|
static inline ImVec2 to_imvec(const ax::size& value) { return ImVec2(static_cast<float>(value.w), static_cast<float>(value.h)); }
|
||||||
|
static inline ImVec2 to_imvec(const ax::sizef& value) { return ImVec2(value.w, value.h); }
|
||||||
|
static inline ax::rect ImGui_GetItemRect() { return ax::rect(to_point(ImGui::GetItemRectMin()), to_point(ImGui::GetItemRectMax())); }
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
struct FringeScaleScope
|
||||||
|
{
|
||||||
|
FringeScaleScope(float scale)
|
||||||
|
: m_LastFringeScale(ImGui::GetWindowDrawList()->_FringeScale)
|
||||||
|
{
|
||||||
|
ImGui::GetWindowDrawList()->_FringeScale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
~FringeScaleScope()
|
||||||
|
{
|
||||||
|
ImGui::GetWindowDrawList()->_FringeScale = m_LastFringeScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float m_LastFringeScale;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
//} // namespace ImGuiInterop
|
||||||
|
//} // namespace ax
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
//# include "stdafx.h"
|
||||||
|
# include "ax/Drawing.h"
|
||||||
|
# include "ax/Math2D.h"
|
||||||
|
# include "Interop.h"
|
||||||
|
# include <cmath>
|
||||||
|
|
||||||
|
void ax::Drawing::DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor)
|
||||||
|
{
|
||||||
|
auto rect = ax::rect(to_point(a), to_point(b));
|
||||||
|
const auto outline_scale = rect.w / 24.0f;
|
||||||
|
const auto extra_segments = roundi(2 * outline_scale); // for full circle
|
||||||
|
|
||||||
|
if (type == IconType::Flow)
|
||||||
|
{
|
||||||
|
const auto origin_scale = rect.w / 24.0f;
|
||||||
|
|
||||||
|
const auto offset_x = 1.0f * origin_scale;
|
||||||
|
const auto offset_y = 0.0f * origin_scale;
|
||||||
|
const auto margin = (filled ? 2.0f : 2.0f) * origin_scale;
|
||||||
|
const auto rounding = 0.1f * origin_scale;
|
||||||
|
const auto tip_round = 0.7f; // percentage of triangle edge (for tip)
|
||||||
|
//const auto edge_round = 0.7f; // percentage of triangle edge (for corner)
|
||||||
|
const auto canvas = rectf(
|
||||||
|
rect.x + margin + offset_x,
|
||||||
|
rect.y + margin + offset_y,
|
||||||
|
rect.w - margin * 2.0f,
|
||||||
|
rect.h - margin * 2.0f);
|
||||||
|
|
||||||
|
const auto left = canvas.x + canvas.w * 0.5f * 0.3f;
|
||||||
|
const auto right = canvas.x + canvas.w - canvas.w * 0.5f * 0.3f;
|
||||||
|
const auto top = canvas.y + canvas.h * 0.5f * 0.2f;
|
||||||
|
const auto bottom = canvas.y + canvas.h - canvas.h * 0.5f * 0.2f;
|
||||||
|
const auto center_y = (top + bottom) * 0.5f;
|
||||||
|
//const auto angle = AX_PI * 0.5f * 0.5f * 0.5f;
|
||||||
|
|
||||||
|
const auto tip_top = ImVec2(canvas.x + canvas.w * 0.5f, top);
|
||||||
|
const auto tip_right = ImVec2(right, center_y);
|
||||||
|
const auto tip_bottom = ImVec2(canvas.x + canvas.w * 0.5f, bottom);
|
||||||
|
|
||||||
|
drawList->PathLineTo(ImVec2(left, top) + ImVec2(0, rounding));
|
||||||
|
drawList->PathBezierCubicCurveTo(
|
||||||
|
ImVec2(left, top),
|
||||||
|
ImVec2(left, top),
|
||||||
|
ImVec2(left, top) + ImVec2(rounding, 0));
|
||||||
|
drawList->PathLineTo(tip_top);
|
||||||
|
drawList->PathLineTo(tip_top + (tip_right - tip_top) * tip_round);
|
||||||
|
drawList->PathBezierCubicCurveTo(
|
||||||
|
tip_right,
|
||||||
|
tip_right,
|
||||||
|
tip_bottom + (tip_right - tip_bottom) * tip_round);
|
||||||
|
drawList->PathLineTo(tip_bottom);
|
||||||
|
drawList->PathLineTo(ImVec2(left, bottom) + ImVec2(rounding, 0));
|
||||||
|
drawList->PathBezierCubicCurveTo(
|
||||||
|
ImVec2(left, bottom),
|
||||||
|
ImVec2(left, bottom),
|
||||||
|
ImVec2(left, bottom) - ImVec2(0, rounding));
|
||||||
|
|
||||||
|
if (!filled)
|
||||||
|
{
|
||||||
|
if (innerColor & 0xFF000000)
|
||||||
|
drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor);
|
||||||
|
|
||||||
|
drawList->PathStroke(color, true, 2.0f * outline_scale);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
drawList->PathFillConvex(color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto triangleStart = rect.center_x() + 0.32f * rect.w;
|
||||||
|
|
||||||
|
rect.x -= roundi(rect.w * 0.25f * 0.25f);
|
||||||
|
|
||||||
|
if (type == IconType::Circle)
|
||||||
|
{
|
||||||
|
const auto c = to_imvec(rect.center());
|
||||||
|
|
||||||
|
if (!filled)
|
||||||
|
{
|
||||||
|
const auto r = 0.5f * rect.w / 2.0f - 0.5f;
|
||||||
|
|
||||||
|
if (innerColor & 0xFF000000)
|
||||||
|
drawList->AddCircleFilled(c, r, innerColor, 12 + extra_segments);
|
||||||
|
drawList->AddCircle(c, r, color, 12 + extra_segments, 2.0f * outline_scale);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
drawList->AddCircleFilled(c, 0.5f * rect.w / 2.0f, color, 12 + extra_segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == IconType::Square)
|
||||||
|
{
|
||||||
|
if (filled)
|
||||||
|
{
|
||||||
|
const auto r = 0.5f * rect.w / 2.0f;
|
||||||
|
const auto p0 = to_imvec(rect.center()) - ImVec2(r, r);
|
||||||
|
const auto p1 = to_imvec(rect.center()) + ImVec2(r, r);
|
||||||
|
|
||||||
|
drawList->AddRectFilled(p0, p1, color, 0, 15 + extra_segments);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto r = 0.5f * rect.w / 2.0f - 0.5f;
|
||||||
|
const auto p0 = to_imvec(rect.center()) - ImVec2(r, r);
|
||||||
|
const auto p1 = to_imvec(rect.center()) + ImVec2(r, r);
|
||||||
|
|
||||||
|
if (innerColor & 0xFF000000)
|
||||||
|
drawList->AddRectFilled(p0, p1, innerColor, 0, 15 + extra_segments);
|
||||||
|
|
||||||
|
drawList->AddRect(p0, p1, color, 0, 15 + extra_segments, 2.0f * outline_scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == IconType::Grid)
|
||||||
|
{
|
||||||
|
const auto r = 0.5f * rect.w / 2.0f;
|
||||||
|
const auto w = ceilf(r / 3.0f);
|
||||||
|
|
||||||
|
const auto baseTl = ImVec2(floorf(rect.center_x() - w * 2.5f), floorf(rect.center_y() - w * 2.5f));
|
||||||
|
const auto baseBr = ImVec2(floorf(baseTl.x + w), floorf(baseTl.y + w));
|
||||||
|
|
||||||
|
auto tl = baseTl;
|
||||||
|
auto br = baseBr;
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
tl.x = baseTl.x;
|
||||||
|
br.x = baseBr.x;
|
||||||
|
drawList->AddRectFilled(tl, br, color);
|
||||||
|
tl.x += w * 2;
|
||||||
|
br.x += w * 2;
|
||||||
|
if (i != 1 || filled)
|
||||||
|
drawList->AddRectFilled(tl, br, color);
|
||||||
|
tl.x += w * 2;
|
||||||
|
br.x += w * 2;
|
||||||
|
drawList->AddRectFilled(tl, br, color);
|
||||||
|
|
||||||
|
tl.y += w * 2;
|
||||||
|
br.y += w * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
triangleStart = br.x + w + 1.0f / 24.0f * rect.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == IconType::RoundSquare)
|
||||||
|
{
|
||||||
|
if (filled)
|
||||||
|
{
|
||||||
|
const auto r = 0.5f * rect.w / 2.0f;
|
||||||
|
const auto cr = r * 0.5f;
|
||||||
|
const auto p0 = to_imvec(rect.center()) - ImVec2(r, r);
|
||||||
|
const auto p1 = to_imvec(rect.center()) + ImVec2(r, r);
|
||||||
|
|
||||||
|
drawList->AddRectFilled(p0, p1, color, cr, 15);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto r = 0.5f * rect.w / 2.0f - 0.5f;
|
||||||
|
const auto cr = r * 0.5f;
|
||||||
|
const auto p0 = to_imvec(rect.center()) - ImVec2(r, r);
|
||||||
|
const auto p1 = to_imvec(rect.center()) + ImVec2(r, r);
|
||||||
|
|
||||||
|
if (innerColor & 0xFF000000)
|
||||||
|
drawList->AddRectFilled(p0, p1, innerColor, cr, 15);
|
||||||
|
|
||||||
|
drawList->AddRect(p0, p1, color, cr, 15, 2.0f * outline_scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == IconType::Diamond)
|
||||||
|
{
|
||||||
|
if (filled)
|
||||||
|
{
|
||||||
|
const auto r = 0.607f * rect.w / 2.0f;
|
||||||
|
const auto c = rect.center();
|
||||||
|
|
||||||
|
drawList->PathLineTo(to_imvec(c) + ImVec2( 0, -r));
|
||||||
|
drawList->PathLineTo(to_imvec(c) + ImVec2( r, 0));
|
||||||
|
drawList->PathLineTo(to_imvec(c) + ImVec2( 0, r));
|
||||||
|
drawList->PathLineTo(to_imvec(c) + ImVec2(-r, 0));
|
||||||
|
drawList->PathFillConvex(color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto r = 0.607f * rect.w / 2.0f - 0.5f;
|
||||||
|
const auto c = rect.center();
|
||||||
|
|
||||||
|
drawList->PathLineTo(to_imvec(c) + ImVec2( 0, -r));
|
||||||
|
drawList->PathLineTo(to_imvec(c) + ImVec2( r, 0));
|
||||||
|
drawList->PathLineTo(to_imvec(c) + ImVec2( 0, r));
|
||||||
|
drawList->PathLineTo(to_imvec(c) + ImVec2(-r, 0));
|
||||||
|
|
||||||
|
if (innerColor & 0xFF000000)
|
||||||
|
drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor);
|
||||||
|
|
||||||
|
drawList->PathStroke(color, true, 2.0f * outline_scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto triangleTip = triangleStart + rect.w * (0.45f - 0.32f);
|
||||||
|
|
||||||
|
drawList->AddTriangleFilled(
|
||||||
|
ImVec2(ceilf(triangleTip), rect.top() + rect.h * 0.5f),
|
||||||
|
ImVec2(triangleStart, rect.center_y() + 0.15f * rect.h),
|
||||||
|
ImVec2(triangleStart, rect.center_y() - 0.15f * rect.h),
|
||||||
|
color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#include "ax/Widgets.h"
|
||||||
|
#include "Interop.h"
|
||||||
|
|
||||||
|
void ax::Widgets::Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color/* = ImVec4(1, 1, 1, 1)*/, const ImVec4& innerColor/* = ImVec4(0, 0, 0, 0)*/)
|
||||||
|
{
|
||||||
|
if (ImGui::IsRectVisible(size))
|
||||||
|
{
|
||||||
|
auto cursorPos = ImGui::GetCursorScreenPos();
|
||||||
|
auto drawList = ImGui::GetWindowDrawList();
|
||||||
|
ax::Drawing::DrawIcon(drawList, cursorPos, cursorPos + size, type, filled, ImColor(color), ImColor(innerColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Dummy(size);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,442 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// LICENSE
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
//
|
||||||
|
// CREDITS
|
||||||
|
// Written by Michal Cichon
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# ifndef __IMGUI_NODE_EDITOR_H__
|
||||||
|
# define __IMGUI_NODE_EDITOR_H__
|
||||||
|
# pragma once
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# include <imgui.h>
|
||||||
|
# include <cstdint> // std::uintXX_t
|
||||||
|
# include <utility> // std::move
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
namespace ax {
|
||||||
|
namespace NodeEditor {
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
struct NodeId;
|
||||||
|
struct LinkId;
|
||||||
|
struct PinId;
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
enum class SaveReasonFlags: uint32_t
|
||||||
|
{
|
||||||
|
None = 0x00000000,
|
||||||
|
Navigation = 0x00000001,
|
||||||
|
Position = 0x00000002,
|
||||||
|
Size = 0x00000004,
|
||||||
|
Selection = 0x00000008,
|
||||||
|
User = 0x00000010
|
||||||
|
};
|
||||||
|
|
||||||
|
inline SaveReasonFlags operator |(SaveReasonFlags lhs, SaveReasonFlags rhs) { return static_cast<SaveReasonFlags>(static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs)); }
|
||||||
|
inline SaveReasonFlags operator &(SaveReasonFlags lhs, SaveReasonFlags rhs) { return static_cast<SaveReasonFlags>(static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs)); }
|
||||||
|
|
||||||
|
using ConfigSaveSettings = bool (*)(const char* data, size_t size, SaveReasonFlags reason, void* userPointer);
|
||||||
|
using ConfigLoadSettings = size_t (*)(char* data, void* userPointer);
|
||||||
|
|
||||||
|
using ConfigSaveNodeSettings = bool (*)(NodeId nodeId, const char* data, size_t size, SaveReasonFlags reason, void* userPointer);
|
||||||
|
using ConfigLoadNodeSettings = size_t (*)(NodeId nodeId, char* data, void* userPointer);
|
||||||
|
|
||||||
|
using ConfigSession = void (*)(void* userPointer);
|
||||||
|
|
||||||
|
struct Config
|
||||||
|
{
|
||||||
|
const char* SettingsFile;
|
||||||
|
ConfigSession BeginSaveSession;
|
||||||
|
ConfigSession EndSaveSession;
|
||||||
|
ConfigSaveSettings SaveSettings;
|
||||||
|
ConfigLoadSettings LoadSettings;
|
||||||
|
ConfigSaveNodeSettings SaveNodeSettings;
|
||||||
|
ConfigLoadNodeSettings LoadNodeSettings;
|
||||||
|
void* UserPointer;
|
||||||
|
|
||||||
|
Config()
|
||||||
|
: SettingsFile("NodeEditor.json")
|
||||||
|
, BeginSaveSession(nullptr)
|
||||||
|
, EndSaveSession(nullptr)
|
||||||
|
, SaveSettings(nullptr)
|
||||||
|
, LoadSettings(nullptr)
|
||||||
|
, SaveNodeSettings(nullptr)
|
||||||
|
, LoadNodeSettings(nullptr)
|
||||||
|
, UserPointer(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
enum class PinKind
|
||||||
|
{
|
||||||
|
Input,
|
||||||
|
Output
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
enum StyleColor
|
||||||
|
{
|
||||||
|
StyleColor_Bg,
|
||||||
|
StyleColor_Grid,
|
||||||
|
StyleColor_NodeBg,
|
||||||
|
StyleColor_NodeBorder,
|
||||||
|
StyleColor_HovNodeBorder,
|
||||||
|
StyleColor_SelNodeBorder,
|
||||||
|
StyleColor_NodeSelRect,
|
||||||
|
StyleColor_NodeSelRectBorder,
|
||||||
|
StyleColor_HovLinkBorder,
|
||||||
|
StyleColor_SelLinkBorder,
|
||||||
|
StyleColor_LinkSelRect,
|
||||||
|
StyleColor_LinkSelRectBorder,
|
||||||
|
StyleColor_PinRect,
|
||||||
|
StyleColor_PinRectBorder,
|
||||||
|
StyleColor_Flow,
|
||||||
|
StyleColor_FlowMarker,
|
||||||
|
StyleColor_GroupBg,
|
||||||
|
StyleColor_GroupBorder,
|
||||||
|
|
||||||
|
StyleColor_Count
|
||||||
|
};
|
||||||
|
|
||||||
|
enum StyleVar
|
||||||
|
{
|
||||||
|
StyleVar_NodePadding,
|
||||||
|
StyleVar_NodeRounding,
|
||||||
|
StyleVar_NodeBorderWidth,
|
||||||
|
StyleVar_HoveredNodeBorderWidth,
|
||||||
|
StyleVar_SelectedNodeBorderWidth,
|
||||||
|
StyleVar_PinRounding,
|
||||||
|
StyleVar_PinBorderWidth,
|
||||||
|
StyleVar_LinkStrength,
|
||||||
|
StyleVar_SourceDirection,
|
||||||
|
StyleVar_TargetDirection,
|
||||||
|
StyleVar_ScrollDuration,
|
||||||
|
StyleVar_FlowMarkerDistance,
|
||||||
|
StyleVar_FlowSpeed,
|
||||||
|
StyleVar_FlowDuration,
|
||||||
|
StyleVar_PivotAlignment,
|
||||||
|
StyleVar_PivotSize,
|
||||||
|
StyleVar_PivotScale,
|
||||||
|
StyleVar_PinCorners,
|
||||||
|
StyleVar_PinRadius,
|
||||||
|
StyleVar_PinArrowSize,
|
||||||
|
StyleVar_PinArrowWidth,
|
||||||
|
StyleVar_GroupRounding,
|
||||||
|
StyleVar_GroupBorderWidth,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Style
|
||||||
|
{
|
||||||
|
ImVec4 NodePadding;
|
||||||
|
float NodeRounding;
|
||||||
|
float NodeBorderWidth;
|
||||||
|
float HoveredNodeBorderWidth;
|
||||||
|
float SelectedNodeBorderWidth;
|
||||||
|
float PinRounding;
|
||||||
|
float PinBorderWidth;
|
||||||
|
float LinkStrength;
|
||||||
|
ImVec2 SourceDirection;
|
||||||
|
ImVec2 TargetDirection;
|
||||||
|
float ScrollDuration;
|
||||||
|
float FlowMarkerDistance;
|
||||||
|
float FlowSpeed;
|
||||||
|
float FlowDuration;
|
||||||
|
ImVec2 PivotAlignment;
|
||||||
|
ImVec2 PivotSize;
|
||||||
|
ImVec2 PivotScale;
|
||||||
|
float PinCorners;
|
||||||
|
float PinRadius;
|
||||||
|
float PinArrowSize;
|
||||||
|
float PinArrowWidth;
|
||||||
|
float GroupRounding;
|
||||||
|
float GroupBorderWidth;
|
||||||
|
ImVec4 Colors[StyleColor_Count];
|
||||||
|
|
||||||
|
Style()
|
||||||
|
{
|
||||||
|
NodePadding = ImVec4(8, 8, 8, 8);
|
||||||
|
NodeRounding = 12.0f;
|
||||||
|
NodeBorderWidth = 1.5f;
|
||||||
|
HoveredNodeBorderWidth = 3.5f;
|
||||||
|
SelectedNodeBorderWidth = 3.5f;
|
||||||
|
PinRounding = 4.0f;
|
||||||
|
PinBorderWidth = 0.0f;
|
||||||
|
LinkStrength = 100.0f;
|
||||||
|
SourceDirection = ImVec2(1.0f, 0.0f);
|
||||||
|
TargetDirection = ImVec2(-1.0f, 0.0f);
|
||||||
|
ScrollDuration = 0.35f;
|
||||||
|
FlowMarkerDistance = 30.0f;
|
||||||
|
FlowSpeed = 150.0f;
|
||||||
|
FlowDuration = 2.0f;
|
||||||
|
PivotAlignment = ImVec2(0.5f, 0.5f);
|
||||||
|
PivotSize = ImVec2(-1, -1);
|
||||||
|
PivotScale = ImVec2(1, 1);
|
||||||
|
PinCorners = ImDrawFlags_RoundCornersAll; // ImDrawCornerFlags_All renamed upstream in ImGui
|
||||||
|
PinRadius = 0.0f;
|
||||||
|
PinArrowSize = 0.0f;
|
||||||
|
PinArrowWidth = 0.0f;
|
||||||
|
GroupRounding = 6.0f;
|
||||||
|
GroupBorderWidth = 1.0f;
|
||||||
|
|
||||||
|
Colors[StyleColor_Bg] = ImColor( 60, 60, 70, 200);
|
||||||
|
Colors[StyleColor_Grid] = ImColor(120, 120, 120, 40);
|
||||||
|
Colors[StyleColor_NodeBg] = ImColor( 32, 32, 32, 200);
|
||||||
|
Colors[StyleColor_NodeBorder] = ImColor(255, 255, 255, 96);
|
||||||
|
Colors[StyleColor_HovNodeBorder] = ImColor( 50, 176, 255, 255);
|
||||||
|
Colors[StyleColor_SelNodeBorder] = ImColor(255, 176, 50, 255);
|
||||||
|
Colors[StyleColor_NodeSelRect] = ImColor( 5, 130, 255, 64);
|
||||||
|
Colors[StyleColor_NodeSelRectBorder] = ImColor( 5, 130, 255, 128);
|
||||||
|
Colors[StyleColor_HovLinkBorder] = ImColor( 50, 176, 255, 255);
|
||||||
|
Colors[StyleColor_SelLinkBorder] = ImColor(255, 176, 50, 255);
|
||||||
|
Colors[StyleColor_LinkSelRect] = ImColor( 5, 130, 255, 64);
|
||||||
|
Colors[StyleColor_LinkSelRectBorder] = ImColor( 5, 130, 255, 128);
|
||||||
|
Colors[StyleColor_PinRect] = ImColor( 60, 180, 255, 100);
|
||||||
|
Colors[StyleColor_PinRectBorder] = ImColor( 60, 180, 255, 128);
|
||||||
|
Colors[StyleColor_Flow] = ImColor(255, 128, 64, 255);
|
||||||
|
Colors[StyleColor_FlowMarker] = ImColor(255, 128, 64, 255);
|
||||||
|
Colors[StyleColor_GroupBg] = ImColor( 0, 0, 0, 160);
|
||||||
|
Colors[StyleColor_GroupBorder] = ImColor(255, 255, 255, 32);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
struct EditorContext;
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
void SetCurrentEditor(EditorContext* ctx);
|
||||||
|
EditorContext* GetCurrentEditor();
|
||||||
|
EditorContext* CreateEditor(const Config* config = nullptr);
|
||||||
|
void DestroyEditor(EditorContext* ctx);
|
||||||
|
|
||||||
|
Style& GetStyle();
|
||||||
|
const char* GetStyleColorName(StyleColor colorIndex);
|
||||||
|
|
||||||
|
void PushStyleColor(StyleColor colorIndex, const ImVec4& color);
|
||||||
|
void PopStyleColor(int count = 1);
|
||||||
|
|
||||||
|
void PushStyleVar(StyleVar varIndex, float value);
|
||||||
|
void PushStyleVar(StyleVar varIndex, const ImVec2& value);
|
||||||
|
void PushStyleVar(StyleVar varIndex, const ImVec4& value);
|
||||||
|
void PopStyleVar(int count = 1);
|
||||||
|
|
||||||
|
void Begin(const char* id, const ImVec2& size = ImVec2(0, 0));
|
||||||
|
void End();
|
||||||
|
|
||||||
|
void BeginNode(NodeId id);
|
||||||
|
void BeginPin(PinId id, PinKind kind);
|
||||||
|
void PinRect(const ImVec2& a, const ImVec2& b);
|
||||||
|
void PinPivotRect(const ImVec2& a, const ImVec2& b);
|
||||||
|
void PinPivotSize(const ImVec2& size);
|
||||||
|
void PinPivotScale(const ImVec2& scale);
|
||||||
|
void PinPivotAlignment(const ImVec2& alignment);
|
||||||
|
void EndPin();
|
||||||
|
void Group(const ImVec2& size);
|
||||||
|
void EndNode();
|
||||||
|
|
||||||
|
bool BeginGroupHint(NodeId nodeId);
|
||||||
|
ImVec2 GetGroupMin();
|
||||||
|
ImVec2 GetGroupMax();
|
||||||
|
ImDrawList* GetHintForegroundDrawList();
|
||||||
|
ImDrawList* GetHintBackgroundDrawList();
|
||||||
|
void EndGroupHint();
|
||||||
|
|
||||||
|
// TODO: Add a way to manage node background channels
|
||||||
|
ImDrawList* GetNodeBackgroundDrawList(NodeId nodeId);
|
||||||
|
|
||||||
|
bool Link(LinkId id, PinId startPinId, PinId endPinId, const ImVec4& color = ImVec4(1, 1, 1, 1), float thickness = 1.0f);
|
||||||
|
|
||||||
|
void Flow(LinkId linkId);
|
||||||
|
|
||||||
|
bool BeginCreate(const ImVec4& color = ImVec4(1, 1, 1, 1), float thickness = 1.0f);
|
||||||
|
bool QueryNewLink(PinId* startId, PinId* endId);
|
||||||
|
bool QueryNewLink(PinId* startId, PinId* endId, const ImVec4& color, float thickness = 1.0f);
|
||||||
|
bool QueryNewNode(PinId* pinId);
|
||||||
|
bool QueryNewNode(PinId* pinId, const ImVec4& color, float thickness = 1.0f);
|
||||||
|
bool AcceptNewItem();
|
||||||
|
bool AcceptNewItem(const ImVec4& color, float thickness = 1.0f);
|
||||||
|
void RejectNewItem();
|
||||||
|
void RejectNewItem(const ImVec4& color, float thickness = 1.0f);
|
||||||
|
void EndCreate();
|
||||||
|
|
||||||
|
bool BeginDelete();
|
||||||
|
bool QueryDeletedLink(LinkId* linkId, PinId* startId = nullptr, PinId* endId = nullptr);
|
||||||
|
bool QueryDeletedNode(NodeId* nodeId);
|
||||||
|
bool AcceptDeletedItem();
|
||||||
|
void RejectDeletedItem();
|
||||||
|
void EndDelete();
|
||||||
|
|
||||||
|
void SetNodePosition(NodeId nodeId, const ImVec2& editorPosition);
|
||||||
|
ImVec2 GetNodePosition(NodeId nodeId);
|
||||||
|
ImVec2 GetNodeSize(NodeId nodeId);
|
||||||
|
void CenterNodeOnScreen(NodeId nodeId);
|
||||||
|
|
||||||
|
void RestoreNodeState(NodeId nodeId);
|
||||||
|
|
||||||
|
void Suspend();
|
||||||
|
void Resume();
|
||||||
|
bool IsSuspended();
|
||||||
|
|
||||||
|
bool IsActive();
|
||||||
|
|
||||||
|
bool HasSelectionChanged();
|
||||||
|
int GetSelectedObjectCount();
|
||||||
|
int GetSelectedNodes(NodeId* nodes, int size);
|
||||||
|
int GetSelectedLinks(LinkId* links, int size);
|
||||||
|
void ClearSelection();
|
||||||
|
void SelectNode(NodeId nodeId, bool append = false);
|
||||||
|
void SelectLink(LinkId linkId, bool append = false);
|
||||||
|
void DeselectNode(NodeId nodeId);
|
||||||
|
void DeselectLink(LinkId linkId);
|
||||||
|
|
||||||
|
bool DeleteNode(NodeId nodeId);
|
||||||
|
bool DeleteLink(LinkId linkId);
|
||||||
|
|
||||||
|
void NavigateToContent(float duration = -1);
|
||||||
|
void NavigateToSelection(bool zoomIn = false, float duration = -1);
|
||||||
|
|
||||||
|
bool ShowNodeContextMenu(NodeId* nodeId);
|
||||||
|
bool ShowPinContextMenu(PinId* pinId);
|
||||||
|
bool ShowLinkContextMenu(LinkId* linkId);
|
||||||
|
bool ShowBackgroundContextMenu();
|
||||||
|
|
||||||
|
void EnableShortcuts(bool enable);
|
||||||
|
bool AreShortcutsEnabled();
|
||||||
|
|
||||||
|
bool BeginShortcut();
|
||||||
|
bool AcceptCut();
|
||||||
|
bool AcceptCopy();
|
||||||
|
bool AcceptPaste();
|
||||||
|
bool AcceptDuplicate();
|
||||||
|
bool AcceptCreateNode();
|
||||||
|
int GetActionContextSize();
|
||||||
|
int GetActionContextNodes(NodeId* nodes, int size);
|
||||||
|
int GetActionContextLinks(LinkId* links, int size);
|
||||||
|
void EndShortcut();
|
||||||
|
|
||||||
|
float GetCurrentZoom();
|
||||||
|
|
||||||
|
NodeId GetDoubleClickedNode();
|
||||||
|
PinId GetDoubleClickedPin();
|
||||||
|
LinkId GetDoubleClickedLink();
|
||||||
|
bool IsBackgroundClicked();
|
||||||
|
bool IsBackgroundDoubleClicked();
|
||||||
|
|
||||||
|
bool PinHadAnyLinks(PinId pinId);
|
||||||
|
|
||||||
|
ImVec2 GetScreenSize();
|
||||||
|
ImVec2 ScreenToCanvas(const ImVec2& pos);
|
||||||
|
ImVec2 CanvasToScreen(const ImVec2& pos);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
namespace Details {
|
||||||
|
|
||||||
|
template <typename T, typename Tag>
|
||||||
|
struct SafeType
|
||||||
|
{
|
||||||
|
SafeType(T t)
|
||||||
|
: m_Value(std::move(t))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SafeType(const SafeType&) = default;
|
||||||
|
|
||||||
|
template <typename T2, typename Tag2>
|
||||||
|
SafeType(
|
||||||
|
const SafeType
|
||||||
|
<
|
||||||
|
typename std::enable_if<!std::is_same<T, T2>::value, T2>::type,
|
||||||
|
typename std::enable_if<!std::is_same<Tag, Tag2>::value, Tag2>::type
|
||||||
|
>&) = delete;
|
||||||
|
|
||||||
|
SafeType& operator=(const SafeType&) = default;
|
||||||
|
|
||||||
|
explicit operator T() const { return Get(); }
|
||||||
|
|
||||||
|
T Get() const { return m_Value; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
T m_Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <typename Tag>
|
||||||
|
struct SafePointerType
|
||||||
|
: SafeType<uintptr_t, Tag>
|
||||||
|
{
|
||||||
|
static const Tag Invalid;
|
||||||
|
|
||||||
|
using SafeType<uintptr_t, Tag>::SafeType;
|
||||||
|
|
||||||
|
SafePointerType()
|
||||||
|
: SafePointerType(Invalid)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T = void> explicit SafePointerType(T* ptr): SafePointerType(reinterpret_cast<uintptr_t>(ptr)) {}
|
||||||
|
template <typename T = void> T* AsPointer() const { return reinterpret_cast<T*>(this->Get()); }
|
||||||
|
|
||||||
|
explicit operator bool() const { return *this != Invalid; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Tag>
|
||||||
|
const Tag SafePointerType<Tag>::Invalid = { 0 };
|
||||||
|
|
||||||
|
template <typename Tag>
|
||||||
|
inline bool operator==(const SafePointerType<Tag>& lhs, const SafePointerType<Tag>& rhs)
|
||||||
|
{
|
||||||
|
return lhs.Get() == rhs.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Tag>
|
||||||
|
inline bool operator!=(const SafePointerType<Tag>& lhs, const SafePointerType<Tag>& rhs)
|
||||||
|
{
|
||||||
|
return lhs.Get() != rhs.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Details
|
||||||
|
|
||||||
|
struct NodeId final: Details::SafePointerType<NodeId>
|
||||||
|
{
|
||||||
|
using SafePointerType::SafePointerType;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LinkId final: Details::SafePointerType<LinkId>
|
||||||
|
{
|
||||||
|
using SafePointerType::SafePointerType;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PinId final: Details::SafePointerType<PinId>
|
||||||
|
{
|
||||||
|
using SafePointerType::SafePointerType;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace ax
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# endif // __IMGUI_NODE_EDITOR_H__
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Michał Cichoń
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -0,0 +1,814 @@
|
|||||||
|
// Crude implementation of JSON value object and parser.
|
||||||
|
//
|
||||||
|
// LICENSE
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
//
|
||||||
|
// CREDITS
|
||||||
|
// Written by Michal Cichon
|
||||||
|
# include "crude_json.h"
|
||||||
|
# include <iomanip>
|
||||||
|
# include <limits>
|
||||||
|
# include <cstdlib>
|
||||||
|
# include <clocale>
|
||||||
|
# include <cmath>
|
||||||
|
# include <cstring>
|
||||||
|
|
||||||
|
|
||||||
|
namespace crude_json {
|
||||||
|
|
||||||
|
value::value(value&& other)
|
||||||
|
: m_Type(other.m_Type)
|
||||||
|
{
|
||||||
|
switch (m_Type)
|
||||||
|
{
|
||||||
|
case type_t::object: construct(m_Storage, std::move( *object_ptr(other.m_Storage))); break;
|
||||||
|
case type_t::array: construct(m_Storage, std::move( *array_ptr(other.m_Storage))); break;
|
||||||
|
case type_t::string: construct(m_Storage, std::move( *string_ptr(other.m_Storage))); break;
|
||||||
|
case type_t::boolean: construct(m_Storage, std::move(*boolean_ptr(other.m_Storage))); break;
|
||||||
|
case type_t::number: construct(m_Storage, std::move( *number_ptr(other.m_Storage))); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
destruct(other.m_Storage, other.m_Type);
|
||||||
|
other.m_Type = type_t::null;
|
||||||
|
}
|
||||||
|
|
||||||
|
value::value(const value& other)
|
||||||
|
: m_Type(other.m_Type)
|
||||||
|
{
|
||||||
|
switch (m_Type)
|
||||||
|
{
|
||||||
|
case type_t::object: construct(m_Storage, *object_ptr(other.m_Storage)); break;
|
||||||
|
case type_t::array: construct(m_Storage, *array_ptr(other.m_Storage)); break;
|
||||||
|
case type_t::string: construct(m_Storage, *string_ptr(other.m_Storage)); break;
|
||||||
|
case type_t::boolean: construct(m_Storage, *boolean_ptr(other.m_Storage)); break;
|
||||||
|
case type_t::number: construct(m_Storage, *number_ptr(other.m_Storage)); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value& value::operator[](size_t index)
|
||||||
|
{
|
||||||
|
if (is_null())
|
||||||
|
m_Type = construct(m_Storage, type_t::array);
|
||||||
|
|
||||||
|
if (is_array())
|
||||||
|
{
|
||||||
|
auto& v = *array_ptr(m_Storage);
|
||||||
|
if (index >= v.size())
|
||||||
|
v.insert(v.end(), index - v.size() + 1, value());
|
||||||
|
|
||||||
|
return v[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
CRUDE_ASSERT(false && "operator[] on unsupported type");
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const value& value::operator[](size_t index) const
|
||||||
|
{
|
||||||
|
if (is_array())
|
||||||
|
return (*array_ptr(m_Storage))[index];
|
||||||
|
|
||||||
|
CRUDE_ASSERT(false && "operator[] on unsupported type");
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
value& value::operator[](const string& key)
|
||||||
|
{
|
||||||
|
if (is_null())
|
||||||
|
m_Type = construct(m_Storage, type_t::object);
|
||||||
|
|
||||||
|
if (is_object())
|
||||||
|
return (*object_ptr(m_Storage))[key];
|
||||||
|
|
||||||
|
CRUDE_ASSERT(false && "operator[] on unsupported type");
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const value& value::operator[](const string& key) const
|
||||||
|
{
|
||||||
|
if (is_object())
|
||||||
|
{
|
||||||
|
auto& o = *object_ptr(m_Storage);
|
||||||
|
auto it = o.find(key);
|
||||||
|
CRUDE_ASSERT(it != o.end());
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
CRUDE_ASSERT(false && "operator[] on unsupported type");
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool value::contains(const string& key) const
|
||||||
|
{
|
||||||
|
if (is_object())
|
||||||
|
{
|
||||||
|
auto& o = *object_ptr(m_Storage);
|
||||||
|
auto it = o.find(key);
|
||||||
|
return it != o.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void value::push_back(const value& value)
|
||||||
|
{
|
||||||
|
if (is_null())
|
||||||
|
m_Type = construct(m_Storage, type_t::array);
|
||||||
|
|
||||||
|
if (is_array())
|
||||||
|
{
|
||||||
|
auto& v = *array_ptr(m_Storage);
|
||||||
|
v.push_back(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CRUDE_ASSERT(false && "operator[] on unsupported type");
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void value::push_back(value&& value)
|
||||||
|
{
|
||||||
|
if (is_null())
|
||||||
|
m_Type = construct(m_Storage, type_t::array);
|
||||||
|
|
||||||
|
if (is_array())
|
||||||
|
{
|
||||||
|
auto& v = *array_ptr(m_Storage);
|
||||||
|
v.push_back(std::move(value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CRUDE_ASSERT(false && "operator[] on unsupported type");
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void value::swap(value& other)
|
||||||
|
{
|
||||||
|
using std::swap;
|
||||||
|
|
||||||
|
if (m_Type == other.m_Type)
|
||||||
|
{
|
||||||
|
switch (m_Type)
|
||||||
|
{
|
||||||
|
case type_t::object: swap(*object_ptr(m_Storage), *object_ptr(other.m_Storage)); break;
|
||||||
|
case type_t::array: swap(*array_ptr(m_Storage), *array_ptr(other.m_Storage)); break;
|
||||||
|
case type_t::string: swap(*string_ptr(m_Storage), *string_ptr(other.m_Storage)); break;
|
||||||
|
case type_t::boolean: swap(*boolean_ptr(m_Storage), *boolean_ptr(other.m_Storage)); break;
|
||||||
|
case type_t::number: swap(*number_ptr(m_Storage), *number_ptr(other.m_Storage)); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value tmp(std::move(other));
|
||||||
|
other.~value();
|
||||||
|
new (&other) value(std::move(*this));
|
||||||
|
this->~value();
|
||||||
|
new (this) value(std::move(tmp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string value::dump(const int indent, const char indent_char) const
|
||||||
|
{
|
||||||
|
dump_context_t context(indent, indent_char);
|
||||||
|
|
||||||
|
context.out.precision(std::numeric_limits<double>::max_digits10 + 1);
|
||||||
|
context.out << std::defaultfloat;
|
||||||
|
|
||||||
|
dump(context, 0);
|
||||||
|
return context.out.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void value::dump_context_t::write_indent(int level)
|
||||||
|
{
|
||||||
|
if (indent <= 0 || level == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
out.fill(indent_char);
|
||||||
|
out.width(indent * level);
|
||||||
|
out << indent_char;
|
||||||
|
out.width(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void value::dump_context_t::write_separator()
|
||||||
|
{
|
||||||
|
if (indent < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
out.put(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
void value::dump_context_t::write_newline()
|
||||||
|
{
|
||||||
|
if (indent < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
out.put('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
void value::dump(dump_context_t& context, int level) const
|
||||||
|
{
|
||||||
|
context.write_indent(level);
|
||||||
|
|
||||||
|
switch (m_Type)
|
||||||
|
{
|
||||||
|
case type_t::null:
|
||||||
|
context.out << "null";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case type_t::object:
|
||||||
|
context.out << '{';
|
||||||
|
{
|
||||||
|
context.write_newline();
|
||||||
|
bool first = true;
|
||||||
|
for (auto& entry : *object_ptr(m_Storage))
|
||||||
|
{
|
||||||
|
if (!first) { context.out << ','; context.write_newline(); } else first = false;
|
||||||
|
context.write_indent(level + 1);
|
||||||
|
context.out << '\"' << entry.first << "\":";
|
||||||
|
if (!entry.second.is_structured())
|
||||||
|
{
|
||||||
|
context.write_separator();
|
||||||
|
entry.second.dump(context, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.write_newline();
|
||||||
|
entry.second.dump(context, level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!first)
|
||||||
|
context.write_newline();
|
||||||
|
}
|
||||||
|
context.write_indent(level);
|
||||||
|
context.out << '}';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case type_t::array:
|
||||||
|
context.out << '[';
|
||||||
|
{
|
||||||
|
context.write_newline();
|
||||||
|
bool first = true;
|
||||||
|
for (auto& entry : *array_ptr(m_Storage))
|
||||||
|
{
|
||||||
|
if (!first) { context.out << ','; context.write_newline(); } else first = false;
|
||||||
|
if (!entry.is_structured())
|
||||||
|
{
|
||||||
|
context.write_indent(level + 1);
|
||||||
|
entry.dump(context, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entry.dump(context, level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!first)
|
||||||
|
context.write_newline();
|
||||||
|
}
|
||||||
|
context.write_indent(level);
|
||||||
|
context.out << ']';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case type_t::string:
|
||||||
|
context.out << '\"';
|
||||||
|
|
||||||
|
if (string_ptr(m_Storage)->find_first_of("\"\\/\b\f\n\r") != string::npos || string_ptr(m_Storage)->find('\0') != string::npos)
|
||||||
|
{
|
||||||
|
for (auto c : *string_ptr(m_Storage))
|
||||||
|
{
|
||||||
|
if (c == '\"') context.out << "\\\"";
|
||||||
|
else if (c == '\\') context.out << "\\\\";
|
||||||
|
else if (c == '/') context.out << "\\/";
|
||||||
|
else if (c == '\b') context.out << "\\b";
|
||||||
|
else if (c == '\f') context.out << "\\f";
|
||||||
|
else if (c == '\n') context.out << "\\n";
|
||||||
|
else if (c == '\r') context.out << "\\r";
|
||||||
|
else if (c == '\t') context.out << "\\t";
|
||||||
|
else if (c == 0) context.out << "\\u0000";
|
||||||
|
else context.out << c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
context.out << *string_ptr(m_Storage);
|
||||||
|
context.out << '\"';
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case type_t::boolean:
|
||||||
|
if (*boolean_ptr(m_Storage))
|
||||||
|
context.out << "true";
|
||||||
|
else
|
||||||
|
context.out << "false";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case type_t::number:
|
||||||
|
context.out << *number_ptr(m_Storage);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct value::parser
|
||||||
|
{
|
||||||
|
parser(const char* begin, const char* end)
|
||||||
|
: m_Cursor(begin)
|
||||||
|
, m_End(end)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
value parse()
|
||||||
|
{
|
||||||
|
value v;
|
||||||
|
|
||||||
|
// Switch to C locale to make strtod and strtol work as expected
|
||||||
|
auto previous_locale = std::setlocale(LC_NUMERIC, "C");
|
||||||
|
|
||||||
|
// Accept single value only when end of the stream is reached.
|
||||||
|
if (!accept_element(v) || !eof())
|
||||||
|
v = value(type_t::discarded);
|
||||||
|
|
||||||
|
if (previous_locale && strcmp(previous_locale, "C") != 0)
|
||||||
|
std::setlocale(LC_NUMERIC, previous_locale);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct cursor_state
|
||||||
|
{
|
||||||
|
cursor_state(parser* p)
|
||||||
|
: m_Owner(p)
|
||||||
|
, m_LastCursor(p->m_Cursor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
m_Owner->m_Cursor = m_LastCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(bool accept)
|
||||||
|
{
|
||||||
|
if (!accept)
|
||||||
|
reset();
|
||||||
|
else
|
||||||
|
m_LastCursor = m_Owner->m_Cursor;
|
||||||
|
return accept;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
parser* m_Owner;
|
||||||
|
const char* m_LastCursor;
|
||||||
|
};
|
||||||
|
|
||||||
|
cursor_state state()
|
||||||
|
{
|
||||||
|
return cursor_state(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_value(value& result)
|
||||||
|
{
|
||||||
|
return accept_object(result)
|
||||||
|
|| accept_array(result)
|
||||||
|
|| accept_string(result)
|
||||||
|
|| accept_number(result)
|
||||||
|
|| accept_boolean(result)
|
||||||
|
|| accept_null(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_object(value& result)
|
||||||
|
{
|
||||||
|
auto s = state();
|
||||||
|
|
||||||
|
object o;
|
||||||
|
if (s(accept('{') && accept_ws() && accept('}')))
|
||||||
|
{
|
||||||
|
result = o;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (s(accept('{') && accept_members(o) && accept('}')))
|
||||||
|
{
|
||||||
|
result = std::move(o);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_members(object& o)
|
||||||
|
{
|
||||||
|
if (!accept_member(o))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto s = state();
|
||||||
|
if (!s(accept(',') && accept_member(o)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_member(object& o)
|
||||||
|
{
|
||||||
|
auto s = state();
|
||||||
|
|
||||||
|
value key;
|
||||||
|
value v;
|
||||||
|
if (s(accept_ws() && accept_string(key) && accept_ws() && accept(':') && accept_element(v)))
|
||||||
|
{
|
||||||
|
o.emplace(std::move(key.get<string>()), std::move(v));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_array(value& result)
|
||||||
|
{
|
||||||
|
auto s = state();
|
||||||
|
|
||||||
|
if (s(accept('[') && accept_ws() && accept(']')))
|
||||||
|
{
|
||||||
|
result = array();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
array a;
|
||||||
|
if (s(accept('[') && accept_elements(a) && accept(']')))
|
||||||
|
{
|
||||||
|
result = std::move(a);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_elements(array& a)
|
||||||
|
{
|
||||||
|
value v;
|
||||||
|
if (!accept_element(v))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
a.emplace_back(std::move(v));
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto s = state();
|
||||||
|
v = nullptr;
|
||||||
|
if (!s(accept(',') && accept_element(v)))
|
||||||
|
break;
|
||||||
|
a.emplace_back(std::move(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_element(value& result)
|
||||||
|
{
|
||||||
|
auto s = state();
|
||||||
|
return s(accept_ws() && accept_value(result) && accept_ws());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_string(value& result)
|
||||||
|
{
|
||||||
|
auto s = state();
|
||||||
|
|
||||||
|
string v;
|
||||||
|
if (s(accept('\"') && accept_characters(v) && accept('\"')))
|
||||||
|
{
|
||||||
|
result = std::move(v);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_characters(string& result)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
while (accept_character(c))
|
||||||
|
{
|
||||||
|
CRUDE_ASSERT(c < 128); // #todo: convert characters > 127 to UTF-8
|
||||||
|
result.push_back(static_cast<char>(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_character(int& c)
|
||||||
|
{
|
||||||
|
auto s = state();
|
||||||
|
|
||||||
|
if (accept('\\'))
|
||||||
|
{
|
||||||
|
return accept_escape(c);
|
||||||
|
}
|
||||||
|
else if (expect('\"'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// #todo: Handle UTF-8 sequences.
|
||||||
|
return s((c = peek()) >= 0) && advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_escape(int& c)
|
||||||
|
{
|
||||||
|
if (accept('\"')) { c = '\"'; return true; }
|
||||||
|
if (accept('\\')) { c = '\\'; return true; }
|
||||||
|
if (accept('/')) { c = '/'; return true; }
|
||||||
|
if (accept('b')) { c = '\b'; return true; }
|
||||||
|
if (accept('f')) { c = '\f'; return true; }
|
||||||
|
if (accept('n')) { c = '\n'; return true; }
|
||||||
|
if (accept('r')) { c = '\r'; return true; }
|
||||||
|
if (accept('t')) { c = '\t'; return true; }
|
||||||
|
|
||||||
|
auto s = state();
|
||||||
|
|
||||||
|
string hex;
|
||||||
|
hex.reserve(4);
|
||||||
|
if (s(accept('u') && accept_hex(hex) && accept_hex(hex) && accept_hex(hex) && accept_hex(hex)))
|
||||||
|
{
|
||||||
|
char* end = nullptr;
|
||||||
|
auto v = std::strtol(hex.c_str(), &end, 16);
|
||||||
|
if (end != hex.c_str() + hex.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
c = v;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_hex(string& result)
|
||||||
|
{
|
||||||
|
if (accept_digit(result))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto c = peek();
|
||||||
|
if ((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))
|
||||||
|
{
|
||||||
|
advance();
|
||||||
|
result.push_back(c);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_number(value& result)
|
||||||
|
{
|
||||||
|
auto s = state();
|
||||||
|
|
||||||
|
string n;
|
||||||
|
if (s(accept_int(n) && accept_frac(n) && accept_exp(n)))
|
||||||
|
{
|
||||||
|
char* end = nullptr;
|
||||||
|
auto v = std::strtod(n.c_str(), &end);
|
||||||
|
if (end != n.c_str() + n.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!std::isnormal(v))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
result = v;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_int(string& result)
|
||||||
|
{
|
||||||
|
auto s = state();
|
||||||
|
|
||||||
|
string part;
|
||||||
|
if (s(accept_onenine(part) && accept_digits(part)))
|
||||||
|
{
|
||||||
|
result += std::move(part);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
part.resize(0);
|
||||||
|
if (accept_digit(part))
|
||||||
|
{
|
||||||
|
result += std::move(part);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
part.resize(0);
|
||||||
|
if (s(accept('-') && accept_onenine(part) && accept_digits(part)))
|
||||||
|
{
|
||||||
|
result += '-';
|
||||||
|
result += std::move(part);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
part.resize(0);
|
||||||
|
if (s(accept('-') && accept_digit(part)))
|
||||||
|
{
|
||||||
|
result += '-';
|
||||||
|
result += std::move(part);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_digits(string& result)
|
||||||
|
{
|
||||||
|
string part;
|
||||||
|
if (!accept_digit(part))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (accept_digit(part))
|
||||||
|
;
|
||||||
|
|
||||||
|
result += std::move(part);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_digit(string& result)
|
||||||
|
{
|
||||||
|
if (accept('0'))
|
||||||
|
{
|
||||||
|
result.push_back('0');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (accept_onenine(result))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_onenine(string& result)
|
||||||
|
{
|
||||||
|
auto c = peek();
|
||||||
|
if (c >= '1' && c <= '9')
|
||||||
|
{
|
||||||
|
result.push_back(static_cast<char>(c));
|
||||||
|
return advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_frac(string& result)
|
||||||
|
{
|
||||||
|
auto s = state();
|
||||||
|
|
||||||
|
string part;
|
||||||
|
if (s(accept('.') && accept_digits(part)))
|
||||||
|
{
|
||||||
|
result += '.';
|
||||||
|
result += std::move(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_exp(string& result)
|
||||||
|
{
|
||||||
|
auto s = state();
|
||||||
|
|
||||||
|
string part;
|
||||||
|
if (s(accept('e') && accept_sign(part) && accept_digits(part)))
|
||||||
|
{
|
||||||
|
result += 'e';
|
||||||
|
result += std::move(part);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
part.resize(0);
|
||||||
|
if (s(accept('E') && accept_sign(part) && accept_digits(part)))
|
||||||
|
{
|
||||||
|
result += 'E';
|
||||||
|
result += std::move(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_sign(string& result)
|
||||||
|
{
|
||||||
|
if (accept('+'))
|
||||||
|
result.push_back('+');
|
||||||
|
else if (accept('-'))
|
||||||
|
result.push_back('-');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_ws()
|
||||||
|
{
|
||||||
|
while (expect('\x09') || expect('\x0A') || expect('\x0D') || expect('\x20'))
|
||||||
|
advance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_boolean(value& result)
|
||||||
|
{
|
||||||
|
if (accept("true"))
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (accept("false"))
|
||||||
|
{
|
||||||
|
result = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept_null(value& result)
|
||||||
|
{
|
||||||
|
if (accept("null"))
|
||||||
|
{
|
||||||
|
result = nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept(char c)
|
||||||
|
{
|
||||||
|
if (expect(c))
|
||||||
|
return advance();
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accept(const char* str)
|
||||||
|
{
|
||||||
|
auto last = m_Cursor;
|
||||||
|
|
||||||
|
while (*str)
|
||||||
|
{
|
||||||
|
if (eof() || *str != *m_Cursor)
|
||||||
|
{
|
||||||
|
m_Cursor = last;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance();
|
||||||
|
++str;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int peek() const
|
||||||
|
{
|
||||||
|
if (!eof())
|
||||||
|
return *m_Cursor;
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool expect(char c)
|
||||||
|
{
|
||||||
|
return peek() == c;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool advance(int count = 1)
|
||||||
|
{
|
||||||
|
if (m_Cursor + count > m_End)
|
||||||
|
{
|
||||||
|
m_Cursor = m_End;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Cursor += count;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool eof() const
|
||||||
|
{
|
||||||
|
return m_Cursor == m_End;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* m_Cursor;
|
||||||
|
const char* m_End;
|
||||||
|
};
|
||||||
|
|
||||||
|
value value::parse(const string& data)
|
||||||
|
{
|
||||||
|
auto p = parser(data.c_str(), data.c_str() + data.size());
|
||||||
|
|
||||||
|
auto v = p.parse();
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crude_json
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
// Crude implementation of JSON value object and parser.
|
||||||
|
//
|
||||||
|
// LICENSE
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
//
|
||||||
|
// CREDITS
|
||||||
|
// Written by Michal Cichon
|
||||||
|
# ifndef __CRUDE_JSON_H__
|
||||||
|
# define __CRUDE_JSON_H__
|
||||||
|
# pragma once
|
||||||
|
|
||||||
|
# include <type_traits>
|
||||||
|
# include <string>
|
||||||
|
# include <vector>
|
||||||
|
# include <map>
|
||||||
|
# include <cstddef>
|
||||||
|
# include <algorithm>
|
||||||
|
# include <sstream>
|
||||||
|
|
||||||
|
# ifndef CRUDE_ASSERT
|
||||||
|
# include <cassert>
|
||||||
|
# define CRUDE_ASSERT(expr) assert(expr)
|
||||||
|
# endif
|
||||||
|
|
||||||
|
namespace crude_json {
|
||||||
|
|
||||||
|
struct value;
|
||||||
|
|
||||||
|
using string = std::string;
|
||||||
|
using object = std::map<string, value>;
|
||||||
|
using array = std::vector<value>;
|
||||||
|
using number = double;
|
||||||
|
using boolean = bool;
|
||||||
|
using null = std::nullptr_t;
|
||||||
|
|
||||||
|
enum class type_t
|
||||||
|
{
|
||||||
|
null,
|
||||||
|
object,
|
||||||
|
array,
|
||||||
|
string,
|
||||||
|
boolean,
|
||||||
|
number,
|
||||||
|
discarded
|
||||||
|
};
|
||||||
|
|
||||||
|
struct value
|
||||||
|
{
|
||||||
|
value(type_t type = type_t::null): m_Type(construct(m_Storage, type)) {}
|
||||||
|
value(value&& other);
|
||||||
|
value(const value& other);
|
||||||
|
|
||||||
|
value( null) : m_Type(construct(m_Storage, null())) {}
|
||||||
|
value( object&& v): m_Type(construct(m_Storage, std::move(v))) {}
|
||||||
|
value(const object& v): m_Type(construct(m_Storage, v)) {}
|
||||||
|
value( array&& v): m_Type(construct(m_Storage, std::move(v))) {}
|
||||||
|
value(const array& v): m_Type(construct(m_Storage, v)) {}
|
||||||
|
value( string&& v): m_Type(construct(m_Storage, std::move(v))) {}
|
||||||
|
value(const string& v): m_Type(construct(m_Storage, v)) {}
|
||||||
|
value(const char* v): m_Type(construct(m_Storage, v)) {}
|
||||||
|
value( boolean v): m_Type(construct(m_Storage, v)) {}
|
||||||
|
value( number v): m_Type(construct(m_Storage, v)) {}
|
||||||
|
~value() { destruct(m_Storage, m_Type); }
|
||||||
|
|
||||||
|
value& operator=(value&& other) { if (this != &other) { value(std::move(other)).swap(*this); } return *this; }
|
||||||
|
value& operator=(const value& other) { if (this != &other) { value( other).swap(*this); } return *this; }
|
||||||
|
|
||||||
|
value& operator=( null) { auto other = value( ); swap(other); return *this; }
|
||||||
|
value& operator=( object&& v) { auto other = value(std::move(v)); swap(other); return *this; }
|
||||||
|
value& operator=(const object& v) { auto other = value( v); swap(other); return *this; }
|
||||||
|
value& operator=( array&& v) { auto other = value(std::move(v)); swap(other); return *this; }
|
||||||
|
value& operator=(const array& v) { auto other = value( v); swap(other); return *this; }
|
||||||
|
value& operator=( string&& v) { auto other = value(std::move(v)); swap(other); return *this; }
|
||||||
|
value& operator=(const string& v) { auto other = value( v); swap(other); return *this; }
|
||||||
|
value& operator=(const char* v) { auto other = value( v); swap(other); return *this; }
|
||||||
|
value& operator=( boolean v) { auto other = value( v); swap(other); return *this; }
|
||||||
|
value& operator=( number v) { auto other = value( v); swap(other); return *this; }
|
||||||
|
|
||||||
|
type_t type() const { return m_Type; }
|
||||||
|
|
||||||
|
operator type_t() const { return m_Type; }
|
||||||
|
|
||||||
|
value& operator[](size_t index);
|
||||||
|
const value& operator[](size_t index) const;
|
||||||
|
value& operator[](const string& key);
|
||||||
|
const value& operator[](const string& key) const;
|
||||||
|
|
||||||
|
bool contains(const string& key) const;
|
||||||
|
|
||||||
|
void push_back(const value& value);
|
||||||
|
void push_back(value&& value);
|
||||||
|
|
||||||
|
bool is_primitive() const { return is_string() || is_number() || is_boolean() || is_null(); }
|
||||||
|
bool is_structured() const { return is_object() || is_array(); }
|
||||||
|
bool is_null() const { return m_Type == type_t::null; }
|
||||||
|
bool is_object() const { return m_Type == type_t::object; }
|
||||||
|
bool is_array() const { return m_Type == type_t::array; }
|
||||||
|
bool is_string() const { return m_Type == type_t::string; }
|
||||||
|
bool is_boolean() const { return m_Type == type_t::boolean; }
|
||||||
|
bool is_number() const { return m_Type == type_t::number; }
|
||||||
|
bool is_discarded() const { return m_Type == type_t::discarded; }
|
||||||
|
|
||||||
|
template <typename T> const T& get() const;
|
||||||
|
template <typename T> T& get();
|
||||||
|
|
||||||
|
string dump(const int indent = -1, const char indent_char = ' ') const;
|
||||||
|
|
||||||
|
void swap(value& other);
|
||||||
|
|
||||||
|
inline friend void swap(value& lhs, value& rhs) { lhs.swap(rhs); }
|
||||||
|
|
||||||
|
// Returns discarded value for invalid inputs.
|
||||||
|
static value parse(const string& data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct parser;
|
||||||
|
|
||||||
|
// VS2015: std::max() is not constexpr yet.
|
||||||
|
# define CRUDE_MAX2(a, b) ((a) < (b) ? (b) : (a))
|
||||||
|
# define CRUDE_MAX3(a, b, c) CRUDE_MAX2(CRUDE_MAX2(a, b), c)
|
||||||
|
# define CRUDE_MAX4(a, b, c, d) CRUDE_MAX2(CRUDE_MAX3(a, b, c), d)
|
||||||
|
# define CRUDE_MAX5(a, b, c, d, e) CRUDE_MAX2(CRUDE_MAX4(a, b, c, d), e)
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
max_size = CRUDE_MAX5( sizeof(string), sizeof(object), sizeof(array), sizeof(number), sizeof(boolean)),
|
||||||
|
max_align = CRUDE_MAX5(alignof(string), alignof(object), alignof(array), alignof(number), alignof(boolean))
|
||||||
|
};
|
||||||
|
# undef CRUDE_MAX5
|
||||||
|
# undef CRUDE_MAX4
|
||||||
|
# undef CRUDE_MAX3
|
||||||
|
# undef CRUDE_MAX2
|
||||||
|
using storage_t = std::aligned_storage<max_size, max_align>::type;
|
||||||
|
|
||||||
|
static object* object_ptr( storage_t& storage) { return reinterpret_cast< object*>(&storage); }
|
||||||
|
static const object* object_ptr(const storage_t& storage) { return reinterpret_cast<const object*>(&storage); }
|
||||||
|
static array* array_ptr( storage_t& storage) { return reinterpret_cast< array*>(&storage); }
|
||||||
|
static const array* array_ptr(const storage_t& storage) { return reinterpret_cast<const array*>(&storage); }
|
||||||
|
static string* string_ptr( storage_t& storage) { return reinterpret_cast< string*>(&storage); }
|
||||||
|
static const string* string_ptr(const storage_t& storage) { return reinterpret_cast<const string*>(&storage); }
|
||||||
|
static boolean* boolean_ptr( storage_t& storage) { return reinterpret_cast< boolean*>(&storage); }
|
||||||
|
static const boolean* boolean_ptr(const storage_t& storage) { return reinterpret_cast<const boolean*>(&storage); }
|
||||||
|
static number* number_ptr( storage_t& storage) { return reinterpret_cast< number*>(&storage); }
|
||||||
|
static const number* number_ptr(const storage_t& storage) { return reinterpret_cast<const number*>(&storage); }
|
||||||
|
|
||||||
|
static type_t construct(storage_t& storage, type_t type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case type_t::object: new (&storage) object(); break;
|
||||||
|
case type_t::array: new (&storage) array(); break;
|
||||||
|
case type_t::string: new (&storage) string(); break;
|
||||||
|
case type_t::boolean: new (&storage) boolean(); break;
|
||||||
|
case type_t::number: new (&storage) number(); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static type_t construct(storage_t& storage, null) { return type_t::null; }
|
||||||
|
static type_t construct(storage_t& storage, object&& value) { new (&storage) object(std::forward<object>(value)); return type_t::object; }
|
||||||
|
static type_t construct(storage_t& storage, const object& value) { new (&storage) object(value); return type_t::object; }
|
||||||
|
static type_t construct(storage_t& storage, array&& value) { new (&storage) array(std::forward<array>(value)); return type_t::array; }
|
||||||
|
static type_t construct(storage_t& storage, const array& value) { new (&storage) array(value); return type_t::array; }
|
||||||
|
static type_t construct(storage_t& storage, string&& value) { new (&storage) string(std::forward<string>(value)); return type_t::string; }
|
||||||
|
static type_t construct(storage_t& storage, const string& value) { new (&storage) string(value); return type_t::string; }
|
||||||
|
static type_t construct(storage_t& storage, const char* value) { new (&storage) string(value); return type_t::string; }
|
||||||
|
static type_t construct(storage_t& storage, boolean value) { new (&storage) boolean(value); return type_t::boolean; }
|
||||||
|
static type_t construct(storage_t& storage, number value) { new (&storage) number(value); return type_t::number; }
|
||||||
|
|
||||||
|
static void destruct(storage_t& storage, type_t type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case type_t::object: object_ptr(storage)->~object(); break;
|
||||||
|
case type_t::array: array_ptr(storage)->~array(); break;
|
||||||
|
case type_t::string: string_ptr(storage)->~string(); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dump_context_t
|
||||||
|
{
|
||||||
|
std::ostringstream out;
|
||||||
|
const int indent = -1;
|
||||||
|
const char indent_char = ' ';
|
||||||
|
|
||||||
|
// VS2015: Aggregate initialization isn't a thing yet.
|
||||||
|
dump_context_t(const int indent, const char indent_char)
|
||||||
|
: indent(indent)
|
||||||
|
, indent_char(indent_char)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_indent(int level);
|
||||||
|
void write_separator();
|
||||||
|
void write_newline();
|
||||||
|
};
|
||||||
|
|
||||||
|
void dump(dump_context_t& context, int level) const;
|
||||||
|
|
||||||
|
storage_t m_Storage;
|
||||||
|
type_t m_Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> inline const object& value::get<object>() const { CRUDE_ASSERT(m_Type == type_t::object); return *object_ptr(m_Storage); }
|
||||||
|
template <> inline const array& value::get<array>() const { CRUDE_ASSERT(m_Type == type_t::array); return *array_ptr(m_Storage); }
|
||||||
|
template <> inline const string& value::get<string>() const { CRUDE_ASSERT(m_Type == type_t::string); return *string_ptr(m_Storage); }
|
||||||
|
template <> inline const boolean& value::get<boolean>() const { CRUDE_ASSERT(m_Type == type_t::boolean); return *boolean_ptr(m_Storage); }
|
||||||
|
template <> inline const number& value::get<number>() const { CRUDE_ASSERT(m_Type == type_t::number); return *number_ptr(m_Storage); }
|
||||||
|
|
||||||
|
template <> inline object& value::get<object>() { CRUDE_ASSERT(m_Type == type_t::object); return *object_ptr(m_Storage); }
|
||||||
|
template <> inline array& value::get<array>() { CRUDE_ASSERT(m_Type == type_t::array); return *array_ptr(m_Storage); }
|
||||||
|
template <> inline string& value::get<string>() { CRUDE_ASSERT(m_Type == type_t::string); return *string_ptr(m_Storage); }
|
||||||
|
template <> inline boolean& value::get<boolean>() { CRUDE_ASSERT(m_Type == type_t::boolean); return *boolean_ptr(m_Storage); }
|
||||||
|
template <> inline number& value::get<number>() { CRUDE_ASSERT(m_Type == type_t::number); return *number_ptr(m_Storage); }
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace crude_json
|
||||||
|
|
||||||
|
# endif // __CRUDE_JSON_H__
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// LICENSE
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
//
|
||||||
|
// CREDITS
|
||||||
|
// Written by Michal Cichon
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# ifndef __IMGUI_BEZIER_MATH_H__
|
||||||
|
# define __IMGUI_BEZIER_MATH_H__
|
||||||
|
# pragma once
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# include "imgui_extra_math.h"
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
template <typename T>
|
||||||
|
struct ImCubicBezierPointsT
|
||||||
|
{
|
||||||
|
T P0;
|
||||||
|
T P1;
|
||||||
|
T P2;
|
||||||
|
T P3;
|
||||||
|
};
|
||||||
|
using ImCubicBezierPoints = ImCubicBezierPointsT<ImVec2>;
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Low-level Bezier curve sampling.
|
||||||
|
template <typename T> inline T ImLinearBezier(const T& p0, const T& p1, float t);
|
||||||
|
template <typename T> inline T ImLinearBezierDt(const T& p0, const T& p1, float t);
|
||||||
|
template <typename T> inline T ImQuadraticBezier(const T& p0, const T& p1, const T& p2, float t);
|
||||||
|
template <typename T> inline T ImQuadraticBezierDt(const T& p0, const T& p1, const T& p2, float t);
|
||||||
|
template <typename T> inline T ImCubicBezier(const T& p0, const T& p1, const T& p2, const T& p3, float t);
|
||||||
|
template <typename T> inline T ImCubicBezierDt(const T& p0, const T& p1, const T& p2, const T& p3, float t);
|
||||||
|
|
||||||
|
|
||||||
|
// High-level Bezier sampling, automatically collapse to lower level Bezier curves if control points overlap.
|
||||||
|
template <typename T> inline T ImCubicBezierSample(const T& p0, const T& p1, const T& p2, const T& p3, float t);
|
||||||
|
template <typename T> inline T ImCubicBezierSample(const ImCubicBezierPointsT<T>& curve, float t);
|
||||||
|
template <typename T> inline T ImCubicBezierTangent(const T& p0, const T& p1, const T& p2, const T& p3, float t);
|
||||||
|
template <typename T> inline T ImCubicBezierTangent(const ImCubicBezierPointsT<T>& curve, float t);
|
||||||
|
|
||||||
|
|
||||||
|
// Calculate approximate length of Cubic Bezier curve.
|
||||||
|
template <typename T> inline float ImCubicBezierLength(const T& p0, const T& p1, const T& p2, const T& p3);
|
||||||
|
template <typename T> inline float ImCubicBezierLength(const ImCubicBezierPointsT<T>& curve);
|
||||||
|
|
||||||
|
|
||||||
|
// Splits Cubic Bezier curve into two curves.
|
||||||
|
template <typename T>
|
||||||
|
struct ImCubicBezierSplitResultT
|
||||||
|
{
|
||||||
|
ImCubicBezierPointsT<T> Left;
|
||||||
|
ImCubicBezierPointsT<T> Right;
|
||||||
|
};
|
||||||
|
using ImCubicBezierSplitResult = ImCubicBezierSplitResultT<ImVec2>;
|
||||||
|
|
||||||
|
template <typename T> inline ImCubicBezierSplitResultT<T> ImCubicBezierSplit(const T& p0, const T& p1, const T& p2, const T& p3, float t);
|
||||||
|
template <typename T> inline ImCubicBezierSplitResultT<T> ImCubicBezierSplit(const ImCubicBezierPointsT<T>& curve, float t);
|
||||||
|
|
||||||
|
|
||||||
|
// Returns bounding rectangle of Cubic Bezier curve.
|
||||||
|
inline ImRect ImCubicBezierBoundingRect(const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3);
|
||||||
|
inline ImRect ImCubicBezierBoundingRect(const ImCubicBezierPoints& curve);
|
||||||
|
|
||||||
|
|
||||||
|
// Project point on Cubic Bezier curve.
|
||||||
|
struct ImProjectResult
|
||||||
|
{
|
||||||
|
ImVec2 Point; // Point on curve
|
||||||
|
float Time; // [0 - 1]
|
||||||
|
float Distance; // Distance to curve
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ImProjectResult ImProjectOnCubicBezier(const ImVec2& p, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const int subdivisions = 100);
|
||||||
|
inline ImProjectResult ImProjectOnCubicBezier(const ImVec2& p, const ImCubicBezierPoints& curve, const int subdivisions = 100);
|
||||||
|
|
||||||
|
|
||||||
|
// Calculate intersection between line and a Cubic Bezier curve.
|
||||||
|
struct ImCubicBezierIntersectResult
|
||||||
|
{
|
||||||
|
int Count;
|
||||||
|
ImVec2 Points[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ImCubicBezierIntersectResult ImCubicBezierLineIntersect(const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& a0, const ImVec2& a1);
|
||||||
|
inline ImCubicBezierIntersectResult ImCubicBezierLineIntersect(const ImCubicBezierPoints& curve, const ImLine& line);
|
||||||
|
|
||||||
|
|
||||||
|
// Adaptive Cubic Bezier subdivision.
|
||||||
|
enum ImCubicBezierSubdivideFlags
|
||||||
|
{
|
||||||
|
ImCubicBezierSubdivide_None = 0,
|
||||||
|
ImCubicBezierSubdivide_SkipFirst = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ImCubicBezierSubdivideSample
|
||||||
|
{
|
||||||
|
ImVec2 Point;
|
||||||
|
ImVec2 Tangent;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ImCubicBezierSubdivideCallback = void (*)(const ImCubicBezierSubdivideSample& p, void* user_pointer);
|
||||||
|
|
||||||
|
inline void ImCubicBezierSubdivide(ImCubicBezierSubdivideCallback callback, void* user_pointer, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float tess_tol = -1.0f, ImCubicBezierSubdivideFlags flags = ImCubicBezierSubdivide_None);
|
||||||
|
inline void ImCubicBezierSubdivide(ImCubicBezierSubdivideCallback callback, void* user_pointer, const ImCubicBezierPoints& curve, float tess_tol = -1.0f, ImCubicBezierSubdivideFlags flags = ImCubicBezierSubdivide_None);
|
||||||
|
|
||||||
|
|
||||||
|
// F has signature void(const ImCubicBezierSubdivideSample& p)
|
||||||
|
template <typename F> inline void ImCubicBezierSubdivide(F& callback, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float tess_tol = -1.0f, ImCubicBezierSubdivideFlags flags = ImCubicBezierSubdivide_None);
|
||||||
|
template <typename F> inline void ImCubicBezierSubdivide(F& callback, const ImCubicBezierPoints& curve, float tess_tol = -1.0f, ImCubicBezierSubdivideFlags flags = ImCubicBezierSubdivide_None);
|
||||||
|
|
||||||
|
// Fixed step Cubic Bezier subdivision.
|
||||||
|
struct ImCubicBezierFixedStepSample
|
||||||
|
{
|
||||||
|
float T;
|
||||||
|
float Length;
|
||||||
|
ImVec2 Point;
|
||||||
|
bool BreakSearch;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ImCubicBezierFixedStepCallback = void (*)(ImCubicBezierFixedStepSample& sample, void* user_pointer);
|
||||||
|
|
||||||
|
inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void* user_pointer, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float step, bool overshoot = false, float max_value_error = 1e-3f, float max_t_error = 1e-5f);
|
||||||
|
inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void* user_pointer, const ImCubicBezierPoints& curve, float step, bool overshoot = false, float max_value_error = 1e-3f, float max_t_error = 1e-5f);
|
||||||
|
|
||||||
|
|
||||||
|
// F has signature void(const ImCubicBezierFixedStepSample& p)
|
||||||
|
template <typename F> inline void ImCubicBezierFixedStep(F& callback, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float step, bool overshoot = false, float max_value_error = 1e-3f, float max_t_error = 1e-5f);
|
||||||
|
template <typename F> inline void ImCubicBezierFixedStep(F& callback, const ImCubicBezierPoints& curve, float step, bool overshoot = false, float max_value_error = 1e-3f, float max_t_error = 1e-5f);
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# include "imgui_bezier_math.inl"
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# endif // __IMGUI_BEZIER_MATH_H__
|
||||||
@@ -0,0 +1,668 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// LICENSE
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
//
|
||||||
|
// CREDITS
|
||||||
|
// Written by Michal Cichon
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# ifndef __IMGUI_BEZIER_MATH_INL__
|
||||||
|
# define __IMGUI_BEZIER_MATH_INL__
|
||||||
|
# pragma once
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# include "imgui_bezier_math.h"
|
||||||
|
# include <map> // used in ImCubicBezierFixedStep
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
template <typename T>
|
||||||
|
inline T ImLinearBezier(const T& p0, const T& p1, float t)
|
||||||
|
{
|
||||||
|
return p0 + t * (p1 - p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T ImLinearBezierDt(const T& p0, const T& p1, float t)
|
||||||
|
{
|
||||||
|
return p1 - p0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T ImQuadraticBezier(const T& p0, const T& p1, const T& p2, float t)
|
||||||
|
{
|
||||||
|
const auto a = 1 - t;
|
||||||
|
|
||||||
|
return a * a * p0 + 2 * t * a * p1 + t * t * p2;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T ImQuadraticBezierDt(const T& p0, const T& p1, const T& p2, float t)
|
||||||
|
{
|
||||||
|
return 2 * (1 - t) * (p1 - p0) + 2 * t * (p2 - p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T ImCubicBezier(const T& p0, const T& p1, const T& p2, const T& p3, float t)
|
||||||
|
{
|
||||||
|
const auto a = 1 - t;
|
||||||
|
const auto b = a * a * a;
|
||||||
|
const auto c = t * t * t;
|
||||||
|
|
||||||
|
return b * p0 + 3 * t * a * a * p1 + 3 * t * t * a * p2 + c * p3;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T ImCubicBezierDt(const T& p0, const T& p1, const T& p2, const T& p3, float t)
|
||||||
|
{
|
||||||
|
const auto a = 1 - t;
|
||||||
|
const auto b = a * a;
|
||||||
|
const auto c = t * t;
|
||||||
|
const auto d = 2 * t * a;
|
||||||
|
|
||||||
|
return -3 * p0 * b + 3 * p1 * (b - d) + 3 * p2 * (d - c) + 3 * p3 * c;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T ImCubicBezierSample(const T& p0, const T& p1, const T& p2, const T& p3, float t)
|
||||||
|
{
|
||||||
|
const auto cp0_zero = ImLengthSqr(p1 - p0) < 1e-5f;
|
||||||
|
const auto cp1_zero = ImLengthSqr(p3 - p2) < 1e-5f;
|
||||||
|
|
||||||
|
if (cp0_zero && cp1_zero)
|
||||||
|
return ImLinearBezier(p0, p3, t);
|
||||||
|
else if (cp0_zero)
|
||||||
|
return ImQuadraticBezier(p0, p2, p3, t);
|
||||||
|
else if (cp1_zero)
|
||||||
|
return ImQuadraticBezier(p0, p1, p3, t);
|
||||||
|
else
|
||||||
|
return ImCubicBezier(p0, p1, p2, p3, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T ImCubicBezierSample(const ImCubicBezierPointsT<T>& curve, float t)
|
||||||
|
{
|
||||||
|
return ImCubicBezierSample(curve.P0, curve.P1, curve.P2, curve.P3, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T ImCubicBezierTangent(const T& p0, const T& p1, const T& p2, const T& p3, float t)
|
||||||
|
{
|
||||||
|
const auto cp0_zero = ImLengthSqr(p1 - p0) < 1e-5f;
|
||||||
|
const auto cp1_zero = ImLengthSqr(p3 - p2) < 1e-5f;
|
||||||
|
|
||||||
|
if (cp0_zero && cp1_zero)
|
||||||
|
return ImLinearBezierDt(p0, p3, t);
|
||||||
|
else if (cp0_zero)
|
||||||
|
return ImQuadraticBezierDt(p0, p2, p3, t);
|
||||||
|
else if (cp1_zero)
|
||||||
|
return ImQuadraticBezierDt(p0, p1, p3, t);
|
||||||
|
else
|
||||||
|
return ImCubicBezierDt(p0, p1, p2, p3, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T ImCubicBezierTangent(const ImCubicBezierPointsT<T>& curve, float t)
|
||||||
|
{
|
||||||
|
return ImCubicBezierTangent(curve.P0, curve.P1, curve.P2, curve.P3, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline float ImCubicBezierLength(const T& p0, const T& p1, const T& p2, const T& p3)
|
||||||
|
{
|
||||||
|
// Legendre-Gauss abscissae with n=24 (x_i values, defined at i=n as the roots of the nth order Legendre polynomial Pn(x))
|
||||||
|
static const float t_values[] =
|
||||||
|
{
|
||||||
|
-0.0640568928626056260850430826247450385909f,
|
||||||
|
0.0640568928626056260850430826247450385909f,
|
||||||
|
-0.1911188674736163091586398207570696318404f,
|
||||||
|
0.1911188674736163091586398207570696318404f,
|
||||||
|
-0.3150426796961633743867932913198102407864f,
|
||||||
|
0.3150426796961633743867932913198102407864f,
|
||||||
|
-0.4337935076260451384870842319133497124524f,
|
||||||
|
0.4337935076260451384870842319133497124524f,
|
||||||
|
-0.5454214713888395356583756172183723700107f,
|
||||||
|
0.5454214713888395356583756172183723700107f,
|
||||||
|
-0.6480936519369755692524957869107476266696f,
|
||||||
|
0.6480936519369755692524957869107476266696f,
|
||||||
|
-0.7401241915785543642438281030999784255232f,
|
||||||
|
0.7401241915785543642438281030999784255232f,
|
||||||
|
-0.8200019859739029219539498726697452080761f,
|
||||||
|
0.8200019859739029219539498726697452080761f,
|
||||||
|
-0.8864155270044010342131543419821967550873f,
|
||||||
|
0.8864155270044010342131543419821967550873f,
|
||||||
|
-0.9382745520027327585236490017087214496548f,
|
||||||
|
0.9382745520027327585236490017087214496548f,
|
||||||
|
-0.9747285559713094981983919930081690617411f,
|
||||||
|
0.9747285559713094981983919930081690617411f,
|
||||||
|
-0.9951872199970213601799974097007368118745f,
|
||||||
|
0.9951872199970213601799974097007368118745f
|
||||||
|
};
|
||||||
|
|
||||||
|
// Legendre-Gauss weights with n=24 (w_i values, defined by a function linked to in the Bezier primer article)
|
||||||
|
static const float c_values[] =
|
||||||
|
{
|
||||||
|
0.1279381953467521569740561652246953718517f,
|
||||||
|
0.1279381953467521569740561652246953718517f,
|
||||||
|
0.1258374563468282961213753825111836887264f,
|
||||||
|
0.1258374563468282961213753825111836887264f,
|
||||||
|
0.1216704729278033912044631534762624256070f,
|
||||||
|
0.1216704729278033912044631534762624256070f,
|
||||||
|
0.1155056680537256013533444839067835598622f,
|
||||||
|
0.1155056680537256013533444839067835598622f,
|
||||||
|
0.1074442701159656347825773424466062227946f,
|
||||||
|
0.1074442701159656347825773424466062227946f,
|
||||||
|
0.0976186521041138882698806644642471544279f,
|
||||||
|
0.0976186521041138882698806644642471544279f,
|
||||||
|
0.0861901615319532759171852029837426671850f,
|
||||||
|
0.0861901615319532759171852029837426671850f,
|
||||||
|
0.0733464814110803057340336152531165181193f,
|
||||||
|
0.0733464814110803057340336152531165181193f,
|
||||||
|
0.0592985849154367807463677585001085845412f,
|
||||||
|
0.0592985849154367807463677585001085845412f,
|
||||||
|
0.0442774388174198061686027482113382288593f,
|
||||||
|
0.0442774388174198061686027482113382288593f,
|
||||||
|
0.0285313886289336631813078159518782864491f,
|
||||||
|
0.0285313886289336631813078159518782864491f,
|
||||||
|
0.0123412297999871995468056670700372915759f,
|
||||||
|
0.0123412297999871995468056670700372915759f
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(t_values) / sizeof(*t_values) == sizeof(c_values) / sizeof(*c_values), "");
|
||||||
|
|
||||||
|
auto arc = [p0, p1, p2, p3](float t)
|
||||||
|
{
|
||||||
|
const auto p = ImCubicBezierDt(p0, p1, p2, p3, t);
|
||||||
|
const auto l = ImLength(p);
|
||||||
|
return l;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto z = 0.5f;
|
||||||
|
const auto n = sizeof(t_values) / sizeof(*t_values);
|
||||||
|
|
||||||
|
auto accumulator = 0.0f;
|
||||||
|
for (size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
const auto t = z * t_values[i] + z;
|
||||||
|
accumulator += c_values[i] * arc(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
return z * accumulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline float ImCubicBezierLength(const ImCubicBezierPointsT<T>& curve)
|
||||||
|
{
|
||||||
|
return ImCubicBezierLength(curve.P0, curve.P1, curve.P2, curve.P3);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline ImCubicBezierSplitResultT<T> ImCubicBezierSplit(const T& p0, const T& p1, const T& p2, const T& p3, float t)
|
||||||
|
{
|
||||||
|
const auto z1 = t;
|
||||||
|
const auto z2 = z1 * z1;
|
||||||
|
const auto z3 = z1 * z1 * z1;
|
||||||
|
const auto s1 = z1 - 1;
|
||||||
|
const auto s2 = s1 * s1;
|
||||||
|
const auto s3 = s1 * s1 * s1;
|
||||||
|
|
||||||
|
return ImCubicBezierSplitResultT<T>
|
||||||
|
{
|
||||||
|
ImCubicBezierPointsT<T>
|
||||||
|
{
|
||||||
|
p0,
|
||||||
|
z1 * p1 - s1 * p0,
|
||||||
|
z2 * p2 - 2 * z1 * s1 * p1 + s2 * p0,
|
||||||
|
z3 * p3 - 3 * z2 * s1 * p2 + 3 * z1 * s2 * p1 - s3 * p0
|
||||||
|
},
|
||||||
|
ImCubicBezierPointsT<T>
|
||||||
|
{
|
||||||
|
z3 * p0 - 3 * z2 * s1 * p1 + 3 * z1 * s2 * p2 - s3 * p3,
|
||||||
|
z2 * p1 - 2 * z1 * s1 * p2 + s2 * p3,
|
||||||
|
z1 * p2 - s1 * p3,
|
||||||
|
p3,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline ImCubicBezierSplitResultT<T> ImCubicBezierSplit(const ImCubicBezierPointsT<T>& curve, float t)
|
||||||
|
{
|
||||||
|
return ImCubicBezierSplit(curve.P0, curve.P1, curve.P2, curve.P3, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImRect ImCubicBezierBoundingRect(const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3)
|
||||||
|
{
|
||||||
|
auto a = 3 * p3 - 9 * p2 + 9 * p1 - 3 * p0;
|
||||||
|
auto b = 6 * p0 - 12 * p1 + 6 * p2;
|
||||||
|
auto c = 3 * p1 - 3 * p0;
|
||||||
|
auto delta_squared = ImMul(b, b) - 4 * ImMul(a, c);
|
||||||
|
|
||||||
|
auto tl = ImMin(p0, p3);
|
||||||
|
auto rb = ImMax(p0, p3);
|
||||||
|
|
||||||
|
# define IM_VEC2_INDEX(v, i) *(&v.x + i)
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
if (IM_VEC2_INDEX(delta_squared, i) >= 0)
|
||||||
|
{
|
||||||
|
auto delta = ImSqrt(IM_VEC2_INDEX(delta_squared, i));
|
||||||
|
|
||||||
|
auto t0 = (-IM_VEC2_INDEX(b, i) + delta) / (2 * IM_VEC2_INDEX(a, i));
|
||||||
|
if (t0 > 0 && t0 < 1)
|
||||||
|
{
|
||||||
|
auto p = ImCubicBezier(IM_VEC2_INDEX(p0, i), IM_VEC2_INDEX(p1, i), IM_VEC2_INDEX(p2, i), IM_VEC2_INDEX(p3, i), t0);
|
||||||
|
IM_VEC2_INDEX(tl, i) = ImMin(IM_VEC2_INDEX(tl, i), p);
|
||||||
|
IM_VEC2_INDEX(rb, i) = ImMax(IM_VEC2_INDEX(rb, i), p);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto t1 = (-IM_VEC2_INDEX(b, i) - delta) / (2 * IM_VEC2_INDEX(a, i));
|
||||||
|
if (t1 > 0 && t1 < 1)
|
||||||
|
{
|
||||||
|
auto p = ImCubicBezier(IM_VEC2_INDEX(p0, i), IM_VEC2_INDEX(p1, i), IM_VEC2_INDEX(p2, i), IM_VEC2_INDEX(p3, i), t1);
|
||||||
|
IM_VEC2_INDEX(tl, i) = ImMin(IM_VEC2_INDEX(tl, i), p);
|
||||||
|
IM_VEC2_INDEX(rb, i) = ImMax(IM_VEC2_INDEX(rb, i), p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# undef IM_VEC2_INDEX
|
||||||
|
|
||||||
|
return ImRect(tl, rb);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImRect ImCubicBezierBoundingRect(const ImCubicBezierPoints& curve)
|
||||||
|
{
|
||||||
|
return ImCubicBezierBoundingRect(curve.P0, curve.P1, curve.P2, curve.P3);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImProjectResult ImProjectOnCubicBezier(const ImVec2& point, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const int subdivisions)
|
||||||
|
{
|
||||||
|
// http://pomax.github.io/bezierinfo/#projections
|
||||||
|
|
||||||
|
const float epsilon = 1e-5f;
|
||||||
|
const float fixed_step = 1.0f / static_cast<float>(subdivisions - 1);
|
||||||
|
|
||||||
|
ImProjectResult result;
|
||||||
|
result.Point = point;
|
||||||
|
result.Time = 0.0f;
|
||||||
|
result.Distance = FLT_MAX;
|
||||||
|
|
||||||
|
// Step 1: Coarse check
|
||||||
|
for (int i = 0; i < subdivisions; ++i)
|
||||||
|
{
|
||||||
|
auto t = i * fixed_step;
|
||||||
|
auto p = ImCubicBezier(p0, p1, p2, p3, t);
|
||||||
|
auto s = point - p;
|
||||||
|
auto d = ImDot(s, s);
|
||||||
|
|
||||||
|
if (d < result.Distance)
|
||||||
|
{
|
||||||
|
result.Point = p;
|
||||||
|
result.Time = t;
|
||||||
|
result.Distance = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.Time == 0.0f || ImFabs(result.Time - 1.0f) <= epsilon)
|
||||||
|
{
|
||||||
|
result.Distance = ImSqrt(result.Distance);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Fine check
|
||||||
|
auto left = result.Time - fixed_step;
|
||||||
|
auto right = result.Time + fixed_step;
|
||||||
|
auto step = fixed_step * 0.1f;
|
||||||
|
|
||||||
|
for (auto t = left; t < right + step; t += step)
|
||||||
|
{
|
||||||
|
auto p = ImCubicBezier(p0, p1, p2, p3, t);
|
||||||
|
auto s = point - p;
|
||||||
|
auto d = ImDot(s, s);
|
||||||
|
|
||||||
|
if (d < result.Distance)
|
||||||
|
{
|
||||||
|
result.Point = p;
|
||||||
|
result.Time = t;
|
||||||
|
result.Distance = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Distance = ImSqrt(result.Distance);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImProjectResult ImProjectOnCubicBezier(const ImVec2& p, const ImCubicBezierPoints& curve, const int subdivisions)
|
||||||
|
{
|
||||||
|
return ImProjectOnCubicBezier(p, curve.P0, curve.P1, curve.P2, curve.P3, subdivisions);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImCubicBezierIntersectResult ImCubicBezierLineIntersect(const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& a0, const ImVec2& a1)
|
||||||
|
{
|
||||||
|
auto cubic_roots = [](float a, float b, float c, float d, float* roots) -> int
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
auto sign = [](float x) -> float { return x < 0 ? -1.0f : 1.0f; };
|
||||||
|
|
||||||
|
auto A = b / a;
|
||||||
|
auto B = c / a;
|
||||||
|
auto C = d / a;
|
||||||
|
|
||||||
|
auto Q = (3 * B - ImPow(A, 2)) / 9;
|
||||||
|
auto R = (9 * A * B - 27 * C - 2 * ImPow(A, 3)) / 54;
|
||||||
|
auto D = ImPow(Q, 3) + ImPow(R, 2); // polynomial discriminant
|
||||||
|
|
||||||
|
if (D >= 0) // complex or duplicate roots
|
||||||
|
{
|
||||||
|
auto S = sign(R + ImSqrt(D)) * ImPow(ImFabs(R + ImSqrt(D)), (1.0f / 3.0f));
|
||||||
|
auto T = sign(R - ImSqrt(D)) * ImPow(ImFabs(R - ImSqrt(D)), (1.0f / 3.0f));
|
||||||
|
|
||||||
|
roots[0] = -A / 3 + (S + T); // real root
|
||||||
|
roots[1] = -A / 3 - (S + T) / 2; // real part of complex root
|
||||||
|
roots[2] = -A / 3 - (S + T) / 2; // real part of complex root
|
||||||
|
auto Im = ImFabs(ImSqrt(3) * (S - T) / 2); // complex part of root pair
|
||||||
|
|
||||||
|
// discard complex roots
|
||||||
|
if (Im != 0)
|
||||||
|
count = 1;
|
||||||
|
else
|
||||||
|
count = 3;
|
||||||
|
}
|
||||||
|
else // distinct real roots
|
||||||
|
{
|
||||||
|
auto th = ImAcos(R / ImSqrt(-ImPow(Q, 3)));
|
||||||
|
|
||||||
|
roots[0] = 2 * ImSqrt(-Q) * ImCos(th / 3) - A / 3;
|
||||||
|
roots[1] = 2 * ImSqrt(-Q) * ImCos((th + 2 * IM_PI) / 3) - A / 3;
|
||||||
|
roots[2] = 2 * ImSqrt(-Q) * ImCos((th + 4 * IM_PI) / 3) - A / 3;
|
||||||
|
|
||||||
|
count = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://github.com/kaishiqi/Geometric-Bezier/blob/master/GeometricBezier/src/kaishiqi/geometric/intersection/Intersection.as
|
||||||
|
//
|
||||||
|
// Start with Bezier using Bernstein polynomials for weighting functions:
|
||||||
|
// (1-t^3)P0 + 3t(1-t)^2P1 + 3t^2(1-t)P2 + t^3P3
|
||||||
|
//
|
||||||
|
// Expand and collect terms to form linear combinations of original Bezier
|
||||||
|
// controls. This ends up with a vector cubic in t:
|
||||||
|
// (-P0+3P1-3P2+P3)t^3 + (3P0-6P1+3P2)t^2 + (-3P0+3P1)t + P0
|
||||||
|
// /\ /\ /\ /\
|
||||||
|
// || || || ||
|
||||||
|
// c3 c2 c1 c0
|
||||||
|
|
||||||
|
// Calculate the coefficients
|
||||||
|
auto c3 = -p0 + 3 * p1 - 3 * p2 + p3;
|
||||||
|
auto c2 = 3 * p0 - 6 * p1 + 3 * p2;
|
||||||
|
auto c1 = -3 * p0 + 3 * p1;
|
||||||
|
auto c0 = p0;
|
||||||
|
|
||||||
|
// Convert line to normal form: ax + by + c = 0
|
||||||
|
auto a = a1.y - a0.y;
|
||||||
|
auto b = a0.x - a1.x;
|
||||||
|
auto c = a0.x * (a0.y - a1.y) + a0.y * (a1.x - a0.x);
|
||||||
|
|
||||||
|
// Rotate each cubic coefficient using line for new coordinate system?
|
||||||
|
// Find roots of rotated cubic
|
||||||
|
float roots[3];
|
||||||
|
auto rootCount = cubic_roots(
|
||||||
|
a * c3.x + b * c3.y,
|
||||||
|
a * c2.x + b * c2.y,
|
||||||
|
a * c1.x + b * c1.y,
|
||||||
|
a * c0.x + b * c0.y + c,
|
||||||
|
roots);
|
||||||
|
|
||||||
|
// Any roots in closed interval [0,1] are intersections on Bezier, but
|
||||||
|
// might not be on the line segment.
|
||||||
|
// Find intersections and calculate point coordinates
|
||||||
|
|
||||||
|
auto min = ImMin(a0, a1);
|
||||||
|
auto max = ImMax(a0, a1);
|
||||||
|
|
||||||
|
ImCubicBezierIntersectResult result;
|
||||||
|
auto points = result.Points;
|
||||||
|
|
||||||
|
for (int i = 0; i < rootCount; ++i)
|
||||||
|
{
|
||||||
|
auto root = roots[i];
|
||||||
|
|
||||||
|
if (0 <= root && root <= 1)
|
||||||
|
{
|
||||||
|
// We're within the Bezier curve
|
||||||
|
// Find point on Bezier
|
||||||
|
auto p = ImCubicBezier(p0, p1, p2, p3, root);
|
||||||
|
|
||||||
|
// See if point is on line segment
|
||||||
|
// Had to make special cases for vertical and horizontal lines due
|
||||||
|
// to slight errors in calculation of p00
|
||||||
|
if (a0.x == a1.x)
|
||||||
|
{
|
||||||
|
if (min.y <= p.y && p.y <= max.y)
|
||||||
|
*points++ = p;
|
||||||
|
}
|
||||||
|
else if (a0.y == a1.y)
|
||||||
|
{
|
||||||
|
if (min.x <= p.x && p.x <= max.x)
|
||||||
|
*points++ = p;
|
||||||
|
}
|
||||||
|
else if (p.x >= min.x && p.y >= min.y && p.x <= max.x && p.y <= max.y)
|
||||||
|
{
|
||||||
|
*points++ = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Count = static_cast<int>(points - result.Points);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImCubicBezierIntersectResult ImCubicBezierLineIntersect(const ImCubicBezierPoints& curve, const ImLine& line)
|
||||||
|
{
|
||||||
|
return ImCubicBezierLineIntersect(curve.P0, curve.P1, curve.P2, curve.P3, line.A, line.B);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ImCubicBezierSubdivide(ImCubicBezierSubdivideCallback callback, void* user_pointer, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float tess_tol, ImCubicBezierSubdivideFlags flags)
|
||||||
|
{
|
||||||
|
return ImCubicBezierSubdivide(callback, user_pointer, ImCubicBezierPoints{ p0, p1, p2, p3 }, tess_tol, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ImCubicBezierSubdivide(ImCubicBezierSubdivideCallback callback, void* user_pointer, const ImCubicBezierPoints& curve, float tess_tol, ImCubicBezierSubdivideFlags flags)
|
||||||
|
{
|
||||||
|
struct Tesselator
|
||||||
|
{
|
||||||
|
ImCubicBezierSubdivideCallback Callback;
|
||||||
|
void* UserPointer;
|
||||||
|
float TesselationTollerance;
|
||||||
|
ImCubicBezierSubdivideFlags Flags;
|
||||||
|
|
||||||
|
void Commit(const ImVec2& p, const ImVec2& t)
|
||||||
|
{
|
||||||
|
ImCubicBezierSubdivideSample sample;
|
||||||
|
sample.Point = p;
|
||||||
|
sample.Tangent = t;
|
||||||
|
Callback(sample, UserPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Subdivide(const ImCubicBezierPoints& curve, int level = 0)
|
||||||
|
{
|
||||||
|
float dx = curve.P3.x - curve.P0.x;
|
||||||
|
float dy = curve.P3.y - curve.P0.y;
|
||||||
|
float d2 = ((curve.P1.x - curve.P3.x) * dy - (curve.P1.y - curve.P3.y) * dx);
|
||||||
|
float d3 = ((curve.P2.x - curve.P3.x) * dy - (curve.P2.y - curve.P3.y) * dx);
|
||||||
|
d2 = (d2 >= 0) ? d2 : -d2;
|
||||||
|
d3 = (d3 >= 0) ? d3 : -d3;
|
||||||
|
if ((d2 + d3) * (d2 + d3) < TesselationTollerance * (dx * dx + dy * dy))
|
||||||
|
{
|
||||||
|
Commit(curve.P3, ImCubicBezierTangent(curve, 1.0f));
|
||||||
|
}
|
||||||
|
else if (level < 10)
|
||||||
|
{
|
||||||
|
const auto p12 = (curve.P0 + curve.P1) * 0.5f;
|
||||||
|
const auto p23 = (curve.P1 + curve.P2) * 0.5f;
|
||||||
|
const auto p34 = (curve.P2 + curve.P3) * 0.5f;
|
||||||
|
const auto p123 = (p12 + p23) * 0.5f;
|
||||||
|
const auto p234 = (p23 + p34) * 0.5f;
|
||||||
|
const auto p1234 = (p123 + p234) * 0.5f;
|
||||||
|
|
||||||
|
Subdivide(ImCubicBezierPoints { curve.P0, p12, p123, p1234 }, level + 1);
|
||||||
|
Subdivide(ImCubicBezierPoints { p1234, p234, p34, curve.P3 }, level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tess_tol < 0)
|
||||||
|
tess_tol = 1.118f; // sqrtf(1.25f)
|
||||||
|
|
||||||
|
Tesselator tesselator;
|
||||||
|
tesselator.Callback = callback;
|
||||||
|
tesselator.UserPointer = user_pointer;
|
||||||
|
tesselator.TesselationTollerance = tess_tol * tess_tol;
|
||||||
|
tesselator.Flags = flags;
|
||||||
|
|
||||||
|
if (!(tesselator.Flags & ImCubicBezierSubdivide_SkipFirst))
|
||||||
|
tesselator.Commit(curve.P0, ImCubicBezierTangent(curve, 0.0f));
|
||||||
|
|
||||||
|
tesselator.Subdivide(curve, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename F> inline void ImCubicBezierSubdivide(F& callback, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float tess_tol, ImCubicBezierSubdivideFlags flags)
|
||||||
|
{
|
||||||
|
auto handler = [](const ImCubicBezierSubdivideSample& p, void* user_pointer)
|
||||||
|
{
|
||||||
|
auto& callback = *reinterpret_cast<F*>(user_pointer);
|
||||||
|
callback(p);
|
||||||
|
};
|
||||||
|
|
||||||
|
ImCubicBezierSubdivide(handler, &callback, ImCubicBezierPoints{ p0, p1, p2, p3 }, tess_tol, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename F> inline void ImCubicBezierSubdivide(F& callback, const ImCubicBezierPoints& curve, float tess_tol, ImCubicBezierSubdivideFlags flags)
|
||||||
|
{
|
||||||
|
auto handler = [](const ImCubicBezierSubdivideSample& p, void* user_pointer)
|
||||||
|
{
|
||||||
|
auto& callback = *reinterpret_cast<F*>(user_pointer);
|
||||||
|
callback(p);
|
||||||
|
};
|
||||||
|
|
||||||
|
ImCubicBezierSubdivide(handler, &callback, curve, tess_tol, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void* user_pointer, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float step, bool overshoot, float max_value_error, float max_t_error)
|
||||||
|
{
|
||||||
|
if (step <= 0.0f || !callback || max_value_error <= 0 || max_t_error <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImCubicBezierFixedStepSample sample;
|
||||||
|
sample.T = 0.0f;
|
||||||
|
sample.Length = 0.0f;
|
||||||
|
sample.Point = p0;
|
||||||
|
sample.BreakSearch = false;
|
||||||
|
|
||||||
|
callback(sample, user_pointer);
|
||||||
|
if (sample.BreakSearch)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto length = ImCubicBezierLength(p0, p1, p2, p3);
|
||||||
|
const auto point_count = static_cast<int>(length / step) + (overshoot ? 2 : 1);
|
||||||
|
const auto t_min = 0.0f;
|
||||||
|
const auto t_max = step * point_count / length;
|
||||||
|
const auto t_0 = (t_min + t_max) * 0.5f;
|
||||||
|
|
||||||
|
// #todo: replace map with ImVector + binary search
|
||||||
|
std::map<float, float> cache;
|
||||||
|
for (int point_index = 1; point_index < point_count; ++point_index)
|
||||||
|
{
|
||||||
|
const auto targetLength = point_index * step;
|
||||||
|
|
||||||
|
float t_start = t_min;
|
||||||
|
float t_end = t_max;
|
||||||
|
float t = t_0;
|
||||||
|
|
||||||
|
float t_best = t;
|
||||||
|
float error_best = length;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto cacheIt = cache.find(t);
|
||||||
|
if (cacheIt == cache.end())
|
||||||
|
{
|
||||||
|
const auto front = ImCubicBezierSplit(p0, p1, p2, p3, t).Left;
|
||||||
|
const auto length = ImCubicBezierLength(front);
|
||||||
|
|
||||||
|
cacheIt = cache.emplace(t, length).first;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto length = cacheIt->second;
|
||||||
|
const auto error = targetLength - length;
|
||||||
|
|
||||||
|
if (error < error_best)
|
||||||
|
{
|
||||||
|
error_best = error;
|
||||||
|
t_best = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImFabs(error) <= max_value_error || ImFabs(t_start - t_end) <= max_t_error)
|
||||||
|
{
|
||||||
|
sample.T = t;
|
||||||
|
sample.Length = length;
|
||||||
|
sample.Point = ImCubicBezier(p0, p1, p2, p3, t);
|
||||||
|
|
||||||
|
callback(sample, user_pointer);
|
||||||
|
if (sample.BreakSearch)
|
||||||
|
return;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (error < 0.0f)
|
||||||
|
t_end = t;
|
||||||
|
else // if (error > 0.0f)
|
||||||
|
t_start = t;
|
||||||
|
|
||||||
|
t = (t_start + t_end) * 0.5f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void* user_pointer, const ImCubicBezierPoints& curve, float step, bool overshoot, float max_value_error, float max_t_error)
|
||||||
|
{
|
||||||
|
ImCubicBezierFixedStep(callback, user_pointer, curve.P0, curve.P1, curve.P2, curve.P3, step, overshoot, max_value_error, max_t_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// F has signature void(const ImCubicBezierFixedStepSample& p)
|
||||||
|
template <typename F>
|
||||||
|
inline void ImCubicBezierFixedStep(F& callback, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float step, bool overshoot, float max_value_error, float max_t_error)
|
||||||
|
{
|
||||||
|
auto handler = [](ImCubicBezierFixedStepSample& sample, void* user_pointer)
|
||||||
|
{
|
||||||
|
auto& callback = *reinterpret_cast<F*>(user_pointer);
|
||||||
|
callback(sample);
|
||||||
|
};
|
||||||
|
|
||||||
|
ImCubicBezierFixedStep(handler, &callback, p0, p1, p2, p3, step, overshoot, max_value_error, max_t_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
inline void ImCubicBezierFixedStep(F& callback, const ImCubicBezierPoints& curve, float step, bool overshoot, float max_value_error, float max_t_error)
|
||||||
|
{
|
||||||
|
auto handler = [](ImCubicBezierFixedStepSample& sample, void* user_pointer)
|
||||||
|
{
|
||||||
|
auto& callback = *reinterpret_cast<F*>(user_pointer);
|
||||||
|
callback(sample);
|
||||||
|
};
|
||||||
|
|
||||||
|
ImCubicBezierFixedStep(handler, &callback, curve.P0, curve.P1, curve.P2, curve.P3, step, overshoot, max_value_error, max_t_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# endif // __IMGUI_BEZIER_MATH_INL__
|
||||||
@@ -0,0 +1,452 @@
|
|||||||
|
# define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
|
# include "imgui_canvas.h"
|
||||||
|
# include <type_traits>
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/36079786
|
||||||
|
# define DECLARE_HAS_MEMBER(__trait_name__, __member_name__) \
|
||||||
|
\
|
||||||
|
template <typename __boost_has_member_T__> \
|
||||||
|
class __trait_name__ \
|
||||||
|
{ \
|
||||||
|
using check_type = ::std::remove_const_t<__boost_has_member_T__>; \
|
||||||
|
struct no_type {char x[2];}; \
|
||||||
|
using yes_type = char; \
|
||||||
|
\
|
||||||
|
struct base { void __member_name__() {}}; \
|
||||||
|
struct mixin : public base, public check_type {}; \
|
||||||
|
\
|
||||||
|
template <void (base::*)()> struct aux {}; \
|
||||||
|
\
|
||||||
|
template <typename U> static no_type test(aux<&U::__member_name__>*); \
|
||||||
|
template <typename U> static yes_type test(...); \
|
||||||
|
\
|
||||||
|
public: \
|
||||||
|
\
|
||||||
|
static constexpr bool value = (sizeof(yes_type) == sizeof(test<mixin>(0))); \
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ImCanvasDetails {
|
||||||
|
|
||||||
|
DECLARE_HAS_MEMBER(HasFringeScale, _FringeScale);
|
||||||
|
|
||||||
|
struct FringeScaleRef
|
||||||
|
{
|
||||||
|
// Overload is present when ImDrawList does have _FringeScale member variable.
|
||||||
|
template <typename T>
|
||||||
|
static float& Get(typename std::enable_if<HasFringeScale<T>::value, T>::type* drawList)
|
||||||
|
{
|
||||||
|
return drawList->_FringeScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload is present when ImDrawList does not have _FringeScale member variable.
|
||||||
|
template <typename T>
|
||||||
|
static float& Get(typename std::enable_if<!HasFringeScale<T>::value, T>::type*)
|
||||||
|
{
|
||||||
|
static float placeholder = 1.0f;
|
||||||
|
return placeholder;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ImCanvasDetails
|
||||||
|
|
||||||
|
// Returns a reference to _FringeScale extension to ImDrawList
|
||||||
|
//
|
||||||
|
// If ImDrawList does not have _FringeScale a placeholder is returned.
|
||||||
|
static inline float& ImFringeScaleRef(ImDrawList* drawList)
|
||||||
|
{
|
||||||
|
using namespace ImCanvasDetails;
|
||||||
|
return FringeScaleRef::Get<ImDrawList>(drawList);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline ImVec2 ImSelectPositive(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x > 0.0f ? lhs.x : rhs.x, lhs.y > 0.0f ? lhs.y : rhs.y); }
|
||||||
|
|
||||||
|
bool ImGuiEx::Canvas::Begin(const char* id, const ImVec2& size)
|
||||||
|
{
|
||||||
|
return Begin(ImGui::GetID(id), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImGuiEx::Canvas::Begin(ImGuiID id, const ImVec2& size)
|
||||||
|
{
|
||||||
|
IM_ASSERT(m_InBeginEnd == false);
|
||||||
|
|
||||||
|
m_WidgetPosition = ImGui::GetCursorScreenPos();
|
||||||
|
m_WidgetSize = ImSelectPositive(size, ImGui::GetContentRegionAvail());
|
||||||
|
m_WidgetRect = ImRect(m_WidgetPosition, m_WidgetPosition + m_WidgetSize);
|
||||||
|
m_DrawList = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
|
UpdateViewTransformPosition();
|
||||||
|
|
||||||
|
if (ImGui::IsClippedEx(m_WidgetRect, id)) // IsClippedEx dropped its 3rd arg upstream in ImGui
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Save current channel, so we can assert when user
|
||||||
|
// call canvas API with different one.
|
||||||
|
m_ExpectedChannel = m_DrawList->_Splitter._Current;
|
||||||
|
|
||||||
|
// #debug: Canvas content.
|
||||||
|
//m_DrawList->AddRectFilled(m_StartPos, m_StartPos + m_CurrentSize, IM_COL32(0, 0, 0, 64));
|
||||||
|
m_DrawList->AddRect(m_WidgetRect.Min, m_WidgetRect.Max, IM_COL32(255, 0, 255, 64));
|
||||||
|
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(0.0f, 0.0f));
|
||||||
|
|
||||||
|
# if IMGUI_EX_CANVAS_DEFERED()
|
||||||
|
m_Ranges.resize(0);
|
||||||
|
# endif
|
||||||
|
|
||||||
|
SaveInputState();
|
||||||
|
|
||||||
|
EnterLocalSpace();
|
||||||
|
|
||||||
|
// Emit dummy widget matching bounds of the canvas.
|
||||||
|
ImGui::SetCursorScreenPos(m_ViewRect.Min);
|
||||||
|
ImGui::Dummy(m_ViewRect.GetSize());
|
||||||
|
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(0.0f, 0.0f));
|
||||||
|
|
||||||
|
m_InBeginEnd = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiEx::Canvas::End()
|
||||||
|
{
|
||||||
|
// If you're here your call to Begin() returned false,
|
||||||
|
// or Begin() wasn't called at all.
|
||||||
|
IM_ASSERT(m_InBeginEnd == true);
|
||||||
|
|
||||||
|
// If you're here, please make sure you do not interleave
|
||||||
|
// channel splitter with canvas.
|
||||||
|
// Always call canvas function with using same channel.
|
||||||
|
IM_ASSERT(m_DrawList->_Splitter._Current == m_ExpectedChannel);
|
||||||
|
|
||||||
|
//auto& io = ImGui::GetIO();
|
||||||
|
|
||||||
|
// Check: Unmatched calls to Suspend() / Resume(). Please check your code.
|
||||||
|
IM_ASSERT(m_SuspendCounter == 0);
|
||||||
|
|
||||||
|
LeaveLocalSpace();
|
||||||
|
|
||||||
|
// Emit dummy widget matching bounds of the canvas.
|
||||||
|
ImGui::SetCursorScreenPos(m_WidgetPosition);
|
||||||
|
ImGui::Dummy(m_WidgetSize);
|
||||||
|
|
||||||
|
// #debug: Rect around canvas. Content should be inside these bounds.
|
||||||
|
//m_DrawList->AddRect(m_WidgetPosition - ImVec2(1.0f, 1.0f), m_WidgetPosition + m_WidgetSize + ImVec2(1.0f, 1.0f), IM_COL32(196, 0, 0, 255));
|
||||||
|
|
||||||
|
m_InBeginEnd = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiEx::Canvas::SetView(const ImVec2& origin, float scale)
|
||||||
|
{
|
||||||
|
SetView(CanvasView(origin, scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiEx::Canvas::SetView(const CanvasView& view)
|
||||||
|
{
|
||||||
|
if (m_InBeginEnd)
|
||||||
|
LeaveLocalSpace();
|
||||||
|
|
||||||
|
if (m_View.Origin.x != view.Origin.x || m_View.Origin.y != view.Origin.y)
|
||||||
|
{
|
||||||
|
m_View.Origin = view.Origin;
|
||||||
|
|
||||||
|
UpdateViewTransformPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_View.Scale != view.Scale)
|
||||||
|
{
|
||||||
|
m_View.Scale = view.Scale;
|
||||||
|
m_View.InvScale = view.InvScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_InBeginEnd)
|
||||||
|
EnterLocalSpace();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiEx::Canvas::CenterView(const ImVec2& canvasPoint)
|
||||||
|
{
|
||||||
|
auto view = CalcCenterView(canvasPoint);
|
||||||
|
SetView(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiEx::CanvasView ImGuiEx::Canvas::CalcCenterView(const ImVec2& canvasPoint) const
|
||||||
|
{
|
||||||
|
auto localCenter = ToLocal(m_WidgetPosition + m_WidgetSize * 0.5f);
|
||||||
|
auto localOffset = canvasPoint - localCenter;
|
||||||
|
auto offset = FromLocalV(localOffset);
|
||||||
|
|
||||||
|
return CanvasView{ m_View.Origin - offset, m_View.Scale };
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiEx::Canvas::CenterView(const ImRect& canvasRect)
|
||||||
|
{
|
||||||
|
auto view = CalcCenterView(canvasRect);
|
||||||
|
|
||||||
|
SetView(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiEx::CanvasView ImGuiEx::Canvas::CalcCenterView(const ImRect& canvasRect) const
|
||||||
|
{
|
||||||
|
auto canvasRectSize = canvasRect.GetSize();
|
||||||
|
|
||||||
|
if (canvasRectSize.x <= 0.0f || canvasRectSize.y <= 0.0f)
|
||||||
|
return View();
|
||||||
|
|
||||||
|
auto widgetAspectRatio = m_WidgetSize.y > 0.0f ? m_WidgetSize.x / m_WidgetSize.y : 0.0f;
|
||||||
|
auto canvasRectAspectRatio = canvasRectSize.y > 0.0f ? canvasRectSize.x / canvasRectSize.y : 0.0f;
|
||||||
|
|
||||||
|
if (widgetAspectRatio <= 0.0f || canvasRectAspectRatio <= 0.0f)
|
||||||
|
return View();
|
||||||
|
|
||||||
|
auto newOrigin = m_View.Origin;
|
||||||
|
auto newScale = m_View.Scale;
|
||||||
|
if (canvasRectAspectRatio > widgetAspectRatio)
|
||||||
|
{
|
||||||
|
// width span across view
|
||||||
|
newScale = m_WidgetSize.x / canvasRectSize.x;
|
||||||
|
newOrigin = canvasRect.Min * -newScale;
|
||||||
|
newOrigin.y += (m_WidgetSize.y - canvasRectSize.y * newScale) * 0.5f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// height span across view
|
||||||
|
newScale = m_WidgetSize.y / canvasRectSize.y;
|
||||||
|
newOrigin = canvasRect.Min * -newScale;
|
||||||
|
newOrigin.x += (m_WidgetSize.x - canvasRectSize.x * newScale) * 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CanvasView{ newOrigin, newScale };
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiEx::Canvas::Suspend()
|
||||||
|
{
|
||||||
|
// If you're here, please make sure you do not interleave
|
||||||
|
// channel splitter with canvas.
|
||||||
|
// Always call canvas function with using same channel.
|
||||||
|
IM_ASSERT(m_DrawList->_Splitter._Current == m_ExpectedChannel);
|
||||||
|
|
||||||
|
if (m_SuspendCounter == 0)
|
||||||
|
LeaveLocalSpace();
|
||||||
|
|
||||||
|
++m_SuspendCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiEx::Canvas::Resume()
|
||||||
|
{
|
||||||
|
// If you're here, please make sure you do not interleave
|
||||||
|
// channel splitter with canvas.
|
||||||
|
// Always call canvas function with using same channel.
|
||||||
|
IM_ASSERT(m_DrawList->_Splitter._Current == m_ExpectedChannel);
|
||||||
|
|
||||||
|
// Check: Number of calls to Resume() do not match calls to Suspend(). Please check your code.
|
||||||
|
IM_ASSERT(m_SuspendCounter > 0);
|
||||||
|
if (--m_SuspendCounter == 0)
|
||||||
|
EnterLocalSpace();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ImGuiEx::Canvas::FromLocal(const ImVec2& point) const
|
||||||
|
{
|
||||||
|
return point * m_View.Scale + m_ViewTransformPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ImGuiEx::Canvas::FromLocal(const ImVec2& point, const CanvasView& view) const
|
||||||
|
{
|
||||||
|
return point * view.Scale + view.Origin + m_WidgetPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ImGuiEx::Canvas::FromLocalV(const ImVec2& vector) const
|
||||||
|
{
|
||||||
|
return vector * m_View.Scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ImGuiEx::Canvas::FromLocalV(const ImVec2& vector, const CanvasView& view) const
|
||||||
|
{
|
||||||
|
return vector * view.Scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ImGuiEx::Canvas::ToLocal(const ImVec2& point) const
|
||||||
|
{
|
||||||
|
return (point - m_ViewTransformPosition) * m_View.InvScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ImGuiEx::Canvas::ToLocal(const ImVec2& point, const CanvasView& view) const
|
||||||
|
{
|
||||||
|
return (point - view.Origin - m_WidgetPosition) * view.InvScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ImGuiEx::Canvas::ToLocalV(const ImVec2& vector) const
|
||||||
|
{
|
||||||
|
return vector * m_View.InvScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ImGuiEx::Canvas::ToLocalV(const ImVec2& vector, const CanvasView& view) const
|
||||||
|
{
|
||||||
|
return vector * view.InvScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImRect ImGuiEx::Canvas::CalcViewRect(const CanvasView& view) const
|
||||||
|
{
|
||||||
|
ImRect result;
|
||||||
|
result.Min = ImVec2(-view.Origin.x, -view.Origin.y) * view.InvScale;
|
||||||
|
result.Max = (m_WidgetSize - view.Origin) * view.InvScale;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiEx::Canvas::UpdateViewTransformPosition()
|
||||||
|
{
|
||||||
|
m_ViewTransformPosition = m_View.Origin + m_WidgetPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiEx::Canvas::SaveInputState()
|
||||||
|
{
|
||||||
|
auto& io = ImGui::GetIO();
|
||||||
|
m_MousePosBackup = io.MousePos;
|
||||||
|
m_MousePosPrevBackup = io.MousePosPrev;
|
||||||
|
for (auto i = 0; i < IM_ARRAYSIZE(m_MouseClickedPosBackup); ++i)
|
||||||
|
m_MouseClickedPosBackup[i] = io.MouseClickedPos[i];
|
||||||
|
|
||||||
|
// Record cursor max to prevent scrollbars from appearing.
|
||||||
|
m_WindowCursorMaxBackup = ImGui::GetCurrentWindow()->DC.CursorMaxPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiEx::Canvas::RestoreInputState()
|
||||||
|
{
|
||||||
|
auto& io = ImGui::GetIO();
|
||||||
|
io.MousePos = m_MousePosBackup;
|
||||||
|
io.MousePosPrev = m_MousePosPrevBackup;
|
||||||
|
for (auto i = 0; i < IM_ARRAYSIZE(m_MouseClickedPosBackup); ++i)
|
||||||
|
io.MouseClickedPos[i] = m_MouseClickedPosBackup[i];
|
||||||
|
ImGui::GetCurrentWindow()->DC.CursorMaxPos = m_WindowCursorMaxBackup;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiEx::Canvas::EnterLocalSpace()
|
||||||
|
{
|
||||||
|
// Prepare ImDrawList for drawing in local coordinate system:
|
||||||
|
// - determine visible part of the canvas
|
||||||
|
// - start unique draw command
|
||||||
|
// - add clip rect matching canvas size
|
||||||
|
// - record current command index
|
||||||
|
// - record current vertex write index
|
||||||
|
|
||||||
|
// Determine visible part of the canvas. Make it before
|
||||||
|
// adding new command, to avoid round rip where command
|
||||||
|
// is removed in PopClipRect() and added again next PushClipRect().
|
||||||
|
ImGui::PushClipRect(m_WidgetPosition, m_WidgetPosition + m_WidgetSize, true);
|
||||||
|
auto clipped_clip_rect = m_DrawList->_ClipRectStack.back();
|
||||||
|
ImGui::PopClipRect();
|
||||||
|
|
||||||
|
// Make sure we do not share draw command with anyone. We don't want to mess
|
||||||
|
// with someones clip rectangle.
|
||||||
|
|
||||||
|
// #FIXME:
|
||||||
|
// This condition is not enough to avoid when user choose
|
||||||
|
// to use channel splitter.
|
||||||
|
//
|
||||||
|
// To deal with Suspend()/Resume() calls empty draw command
|
||||||
|
// is always added then splitter is active. Otherwise
|
||||||
|
// channel merger will collapse our draw command one with
|
||||||
|
// different clip rectangle.
|
||||||
|
//
|
||||||
|
// More investigation is needed. To get to the bottom of this.
|
||||||
|
if ((!m_DrawList->CmdBuffer.empty() && m_DrawList->CmdBuffer.back().ElemCount > 0) || m_DrawList->_Splitter._Count > 1)
|
||||||
|
m_DrawList->AddDrawCmd();
|
||||||
|
|
||||||
|
# if IMGUI_EX_CANVAS_DEFERED()
|
||||||
|
m_Ranges.resize(m_Ranges.Size + 1);
|
||||||
|
m_CurrentRange = &m_Ranges.back();
|
||||||
|
m_CurrentRange->BeginComandIndex = ImMax(m_DrawList->CmdBuffer.Size - 1, 0);
|
||||||
|
m_CurrentRange->BeginVertexIndex = m_DrawList->_VtxCurrentIdx;
|
||||||
|
# endif
|
||||||
|
m_DrawListCommadBufferSize = ImMax(m_DrawList->CmdBuffer.Size - 1, 0);
|
||||||
|
m_DrawListStartVertexIndex = m_DrawList->_VtxCurrentIdx;
|
||||||
|
|
||||||
|
// Clip rectangle in parent canvas space and move it to local space.
|
||||||
|
clipped_clip_rect.x = (clipped_clip_rect.x - m_ViewTransformPosition.x) * m_View.InvScale;
|
||||||
|
clipped_clip_rect.y = (clipped_clip_rect.y - m_ViewTransformPosition.y) * m_View.InvScale;
|
||||||
|
clipped_clip_rect.z = (clipped_clip_rect.z - m_ViewTransformPosition.x) * m_View.InvScale;
|
||||||
|
clipped_clip_rect.w = (clipped_clip_rect.w - m_ViewTransformPosition.y) * m_View.InvScale;
|
||||||
|
ImGui::PushClipRect(ImVec2(clipped_clip_rect.x, clipped_clip_rect.y), ImVec2(clipped_clip_rect.z, clipped_clip_rect.w), false);
|
||||||
|
|
||||||
|
// Transform mouse position to local space.
|
||||||
|
auto& io = ImGui::GetIO();
|
||||||
|
io.MousePos = (m_MousePosBackup - m_ViewTransformPosition) * m_View.InvScale;
|
||||||
|
io.MousePosPrev = (m_MousePosPrevBackup - m_ViewTransformPosition) * m_View.InvScale;
|
||||||
|
for (auto i = 0; i < IM_ARRAYSIZE(m_MouseClickedPosBackup); ++i)
|
||||||
|
io.MouseClickedPos[i] = (m_MouseClickedPosBackup[i] - m_ViewTransformPosition) * m_View.InvScale;
|
||||||
|
|
||||||
|
m_ViewRect = CalcViewRect(m_View);;
|
||||||
|
|
||||||
|
auto& fringeScale = ImFringeScaleRef(m_DrawList);
|
||||||
|
m_LastFringeScale = fringeScale;
|
||||||
|
fringeScale *= m_View.InvScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiEx::Canvas::LeaveLocalSpace()
|
||||||
|
{
|
||||||
|
IM_ASSERT(m_DrawList->_Splitter._Current == m_ExpectedChannel);
|
||||||
|
|
||||||
|
# if IMGUI_EX_CANVAS_DEFERED()
|
||||||
|
IM_ASSERT(m_CurrentRange != nullptr);
|
||||||
|
|
||||||
|
m_CurrentRange->EndVertexIndex = m_DrawList->_VtxCurrentIdx;
|
||||||
|
m_CurrentRange->EndCommandIndex = m_DrawList->CmdBuffer.size();
|
||||||
|
if (m_CurrentRange->BeginVertexIndex == m_CurrentRange->EndVertexIndex)
|
||||||
|
{
|
||||||
|
// Drop empty range
|
||||||
|
m_Ranges.resize(m_Ranges.Size - 1);
|
||||||
|
}
|
||||||
|
m_CurrentRange = nullptr;
|
||||||
|
# endif
|
||||||
|
|
||||||
|
// Move vertices to screen space.
|
||||||
|
auto vertex = m_DrawList->VtxBuffer.Data + m_DrawListStartVertexIndex;
|
||||||
|
auto vertexEnd = m_DrawList->VtxBuffer.Data + m_DrawList->_VtxCurrentIdx;
|
||||||
|
|
||||||
|
// If canvas view is not scaled take a faster path.
|
||||||
|
if (m_View.Scale != 1.0f)
|
||||||
|
{
|
||||||
|
while (vertex < vertexEnd)
|
||||||
|
{
|
||||||
|
vertex->pos.x = vertex->pos.x * m_View.Scale + m_ViewTransformPosition.x;
|
||||||
|
vertex->pos.y = vertex->pos.y * m_View.Scale + m_ViewTransformPosition.y;
|
||||||
|
++vertex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move clip rectangles to screen space.
|
||||||
|
for (int i = m_DrawListCommadBufferSize; i < m_DrawList->CmdBuffer.size(); ++i)
|
||||||
|
{
|
||||||
|
auto& command = m_DrawList->CmdBuffer[i];
|
||||||
|
command.ClipRect.x = command.ClipRect.x * m_View.Scale + m_ViewTransformPosition.x;
|
||||||
|
command.ClipRect.y = command.ClipRect.y * m_View.Scale + m_ViewTransformPosition.y;
|
||||||
|
command.ClipRect.z = command.ClipRect.z * m_View.Scale + m_ViewTransformPosition.x;
|
||||||
|
command.ClipRect.w = command.ClipRect.w * m_View.Scale + m_ViewTransformPosition.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (vertex < vertexEnd)
|
||||||
|
{
|
||||||
|
vertex->pos.x = vertex->pos.x + m_ViewTransformPosition.x;
|
||||||
|
vertex->pos.y = vertex->pos.y + m_ViewTransformPosition.y;
|
||||||
|
++vertex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move clip rectangles to screen space.
|
||||||
|
for (int i = m_DrawListCommadBufferSize; i < m_DrawList->CmdBuffer.size(); ++i)
|
||||||
|
{
|
||||||
|
auto& command = m_DrawList->CmdBuffer[i];
|
||||||
|
command.ClipRect.x = command.ClipRect.x + m_ViewTransformPosition.x;
|
||||||
|
command.ClipRect.y = command.ClipRect.y + m_ViewTransformPosition.y;
|
||||||
|
command.ClipRect.z = command.ClipRect.z + m_ViewTransformPosition.x;
|
||||||
|
command.ClipRect.w = command.ClipRect.w + m_ViewTransformPosition.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& fringeScale = ImFringeScaleRef(m_DrawList);
|
||||||
|
fringeScale = m_LastFringeScale;
|
||||||
|
|
||||||
|
// And pop \o/
|
||||||
|
ImGui::PopClipRect();
|
||||||
|
|
||||||
|
RestoreInputState();
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
// Canvas widget - view over infinite virtual space.
|
||||||
|
//
|
||||||
|
// Canvas allows you to draw your widgets anywhere over infinite space and provide
|
||||||
|
// view over it with support for panning and scaling.
|
||||||
|
//
|
||||||
|
// When you enter a canvas ImGui is moved to virtual space which mean:
|
||||||
|
// - ImGui::GetCursorScreenPos() return (0, 0) and which correspond to top left corner
|
||||||
|
// of the canvas on the screen (this can be changed usign CanvasView()).
|
||||||
|
// - Mouse input is brought to canvas space, so widgets works as usual.
|
||||||
|
// - Everything you draw with ImDrawList will be in virtual space.
|
||||||
|
//
|
||||||
|
// By default origin point is on top left corner of canvas widget. It can be
|
||||||
|
// changed with call to CanvasView() where you can specify what part of space
|
||||||
|
// should be viewed by setting viewport origin point and scale. Current state
|
||||||
|
// can be queried with CanvasViewOrigin() and CanvasViewScale().
|
||||||
|
//
|
||||||
|
// Viewport size is controlled by 'size' parameter in BeginCanvas(). You can query
|
||||||
|
// it using CanvasContentMin/Max/Size functions. They are useful if you to not specify
|
||||||
|
// canvas size in which case all free space is used.
|
||||||
|
//
|
||||||
|
// Bounds of visible region of infinite space can be queried using CanvasViewMin/Max/Size
|
||||||
|
// functions. Everything that is drawn outside of this region will be clipped
|
||||||
|
// as usual in ImGui.
|
||||||
|
//
|
||||||
|
// While drawing inside canvas you can translate position from world (usual ImGui space)
|
||||||
|
// to virtual space and back usign CanvasFromWorld()/CanvasToWorld().
|
||||||
|
//
|
||||||
|
// Canvas can be nested in each other (they are regular widgets after all). There
|
||||||
|
// is a way to transform position between current and parent canvas with
|
||||||
|
// CanvasFromParent()/CanvasToParent().
|
||||||
|
//
|
||||||
|
// Sometimes in more elaborate scenarios you want to move out canvas virtual space,
|
||||||
|
// do something and came back. You can do that with SuspendCanvas() and ResumeCanvas().
|
||||||
|
//
|
||||||
|
// Note:
|
||||||
|
// It is not valid to call canvas API outside of BeginCanvas() / EndCanvas() scope.
|
||||||
|
//
|
||||||
|
// LICENSE
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
//
|
||||||
|
// CREDITS
|
||||||
|
// Written by Michal Cichon
|
||||||
|
# ifndef __IMGUI_EX_CANVAS_H__
|
||||||
|
# define __IMGUI_EX_CANVAS_H__
|
||||||
|
# pragma once
|
||||||
|
|
||||||
|
# include <imgui.h>
|
||||||
|
# include <imgui_internal.h> // ImRect, ImFloor
|
||||||
|
|
||||||
|
namespace ImGuiEx {
|
||||||
|
|
||||||
|
struct CanvasView
|
||||||
|
{
|
||||||
|
ImVec2 Origin;
|
||||||
|
float Scale = 1.0f;
|
||||||
|
float InvScale = 1.0f;
|
||||||
|
|
||||||
|
CanvasView() = default;
|
||||||
|
CanvasView(const ImVec2& origin, float scale)
|
||||||
|
: Origin(origin)
|
||||||
|
, Scale(scale)
|
||||||
|
, InvScale(scale ? 1.0f / scale : 0.0f)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Set(const ImVec2& origin, float scale)
|
||||||
|
{
|
||||||
|
*this = CanvasView(origin, scale);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Canvas widget represent view over infinite plane.
|
||||||
|
//
|
||||||
|
// It acts like a child window without scroll bars with
|
||||||
|
// ability to zoom to specific part of canvas plane.
|
||||||
|
//
|
||||||
|
// Widgets are clipped according to current view exactly
|
||||||
|
// same way ImGui do. To avoid `missing widgets` artifacts first
|
||||||
|
// setup visible region with SetView() then draw content.
|
||||||
|
//
|
||||||
|
// Everything drawn with ImDrawList betwen calls to Begin()/End()
|
||||||
|
// will be drawn on canvas plane. This behavior can be suspended
|
||||||
|
// by calling Suspend() and resumed by calling Resume().
|
||||||
|
//
|
||||||
|
// Warning:
|
||||||
|
// Please do not interleave canvas with use of channel splitter.
|
||||||
|
// Keep channel splitter contained inside canvas or always
|
||||||
|
// call canvas functions from same channel.
|
||||||
|
struct Canvas
|
||||||
|
{
|
||||||
|
// Begins drawing content of canvas plane.
|
||||||
|
//
|
||||||
|
// When false is returned that mean canvas is not visible to the
|
||||||
|
// user can drawing should be skipped and End() not called.
|
||||||
|
// When true is returned drawing must be ended with call to End().
|
||||||
|
//
|
||||||
|
// If any size component is equal to zero or less canvas will
|
||||||
|
// automatically expand to all available area on that axis.
|
||||||
|
// So (0, 300) will take horizontal space and have height
|
||||||
|
// of 300 points. (0, 0) will take all remaining space of
|
||||||
|
// the window.
|
||||||
|
//
|
||||||
|
// You can query size of the canvas while it is being drawn
|
||||||
|
// by calling Rect().
|
||||||
|
bool Begin(const char* id, const ImVec2& size);
|
||||||
|
bool Begin(ImGuiID id, const ImVec2& size);
|
||||||
|
|
||||||
|
// Ends interaction with canvas plane.
|
||||||
|
//
|
||||||
|
// Must be called only when Begin() retuned true.
|
||||||
|
void End();
|
||||||
|
|
||||||
|
// Sets visible region of canvas plane.
|
||||||
|
//
|
||||||
|
// Origin is an offset of infinite plane origin from top left
|
||||||
|
// corner of the canvas.
|
||||||
|
//
|
||||||
|
// Scale greater than 1 make canvas content be bigger, less than 1 smaller.
|
||||||
|
void SetView(const ImVec2& origin, float scale);
|
||||||
|
void SetView(const CanvasView& view);
|
||||||
|
|
||||||
|
// Centers view over specific point on canvas plane.
|
||||||
|
//
|
||||||
|
// View will be centered on specific point by changing origin
|
||||||
|
// but not scale.
|
||||||
|
void CenterView(const ImVec2& canvasPoint);
|
||||||
|
|
||||||
|
// Calculates view over specific point on canvas plane.
|
||||||
|
CanvasView CalcCenterView(const ImVec2& canvasPoint) const;
|
||||||
|
|
||||||
|
// Centers view over specific rectangle on canvas plane.
|
||||||
|
//
|
||||||
|
// Whole rectangle will fit in canvas view. This will affect both
|
||||||
|
// origin and scale.
|
||||||
|
void CenterView(const ImRect& canvasRect);
|
||||||
|
|
||||||
|
// Calculates view over specific rectangle on canvas plane.
|
||||||
|
CanvasView CalcCenterView(const ImRect& canvasRect) const;
|
||||||
|
|
||||||
|
// Suspends canvas by returning to normal ImGui transformation space.
|
||||||
|
// While suspended UI will not be drawn on canvas plane.
|
||||||
|
//
|
||||||
|
// Calls to Suspend()/Resume() are symetrical. Each call to Suspend()
|
||||||
|
// must be matched with call to Resume().
|
||||||
|
void Suspend();
|
||||||
|
void Resume();
|
||||||
|
|
||||||
|
// Transforms point from canvas plane to ImGui.
|
||||||
|
ImVec2 FromLocal(const ImVec2& point) const;
|
||||||
|
ImVec2 FromLocal(const ImVec2& point, const CanvasView& view) const;
|
||||||
|
|
||||||
|
// Transforms vector from canvas plant to ImGui.
|
||||||
|
ImVec2 FromLocalV(const ImVec2& vector) const;
|
||||||
|
ImVec2 FromLocalV(const ImVec2& vector, const CanvasView& view) const;
|
||||||
|
|
||||||
|
// Transforms point from ImGui to canvas plane.
|
||||||
|
ImVec2 ToLocal(const ImVec2& point) const;
|
||||||
|
ImVec2 ToLocal(const ImVec2& point, const CanvasView& view) const;
|
||||||
|
|
||||||
|
// Transforms vector from ImGui to canvas plane.
|
||||||
|
ImVec2 ToLocalV(const ImVec2& vector) const;
|
||||||
|
ImVec2 ToLocalV(const ImVec2& vector, const CanvasView& view) const;
|
||||||
|
|
||||||
|
// Returns widget bounds.
|
||||||
|
//
|
||||||
|
// Note:
|
||||||
|
// Rect is valid after call to Begin().
|
||||||
|
const ImRect& Rect() const { return m_WidgetRect; }
|
||||||
|
|
||||||
|
// Returns visible region on canvas plane (in canvas plane coordinates).
|
||||||
|
const ImRect& ViewRect() const { return m_ViewRect; }
|
||||||
|
|
||||||
|
// Calculates visible region for view.
|
||||||
|
ImRect CalcViewRect(const CanvasView& view) const;
|
||||||
|
|
||||||
|
// Returns current view.
|
||||||
|
const CanvasView& View() const { return m_View; }
|
||||||
|
|
||||||
|
// Returns origin of the view.
|
||||||
|
//
|
||||||
|
// Origin is an offset of infinite plane origin from top left
|
||||||
|
// corner of the canvas.
|
||||||
|
const ImVec2& ViewOrigin() const { return m_View.Origin; }
|
||||||
|
|
||||||
|
// Returns scale of the view.
|
||||||
|
float ViewScale() const { return m_View.Scale; }
|
||||||
|
|
||||||
|
// Returns true if canvas is suspended.
|
||||||
|
//
|
||||||
|
// See: Suspend()/Resume()
|
||||||
|
bool IsSuspended() const { return m_SuspendCounter > 0; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
# define IMGUI_EX_CANVAS_DEFERED() 0
|
||||||
|
|
||||||
|
# if IMGUI_EX_CANVAS_DEFERED()
|
||||||
|
struct Range
|
||||||
|
{
|
||||||
|
int BeginVertexIndex = 0;
|
||||||
|
int EndVertexIndex = 0;
|
||||||
|
int BeginComandIndex = 0;
|
||||||
|
int EndCommandIndex = 0;
|
||||||
|
};
|
||||||
|
# endif
|
||||||
|
|
||||||
|
void UpdateViewTransformPosition();
|
||||||
|
|
||||||
|
void SaveInputState();
|
||||||
|
void RestoreInputState();
|
||||||
|
|
||||||
|
void EnterLocalSpace();
|
||||||
|
void LeaveLocalSpace();
|
||||||
|
|
||||||
|
bool m_InBeginEnd = false;
|
||||||
|
|
||||||
|
ImVec2 m_WidgetPosition;
|
||||||
|
ImVec2 m_WidgetSize;
|
||||||
|
ImRect m_WidgetRect;
|
||||||
|
|
||||||
|
ImDrawList* m_DrawList = nullptr;
|
||||||
|
int m_ExpectedChannel = 0;
|
||||||
|
|
||||||
|
# if IMGUI_EX_CANVAS_DEFERED()
|
||||||
|
ImVector<Range> m_Ranges;
|
||||||
|
Range* m_CurrentRange = nullptr;
|
||||||
|
# endif
|
||||||
|
|
||||||
|
int m_DrawListCommadBufferSize = 0;
|
||||||
|
int m_DrawListStartVertexIndex = 0;
|
||||||
|
|
||||||
|
CanvasView m_View;
|
||||||
|
ImRect m_ViewRect;
|
||||||
|
|
||||||
|
ImVec2 m_ViewTransformPosition;
|
||||||
|
|
||||||
|
int m_SuspendCounter = 0;
|
||||||
|
|
||||||
|
float m_LastFringeScale = 1.0f;
|
||||||
|
|
||||||
|
ImVec2 m_MousePosBackup;
|
||||||
|
ImVec2 m_MousePosPrevBackup;
|
||||||
|
ImVec2 m_MouseClickedPosBackup[IM_ARRAYSIZE(ImGuiIO::MouseClickedPos)];
|
||||||
|
ImVec2 m_WindowCursorMaxBackup;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ImGuiEx
|
||||||
|
|
||||||
|
# endif // __IMGUI_EX_CANVAS_H__
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// LICENSE
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
//
|
||||||
|
// CREDITS
|
||||||
|
// Written by Michal Cichon
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# ifndef __IMGUI_EXTRA_MATH_H__
|
||||||
|
# define __IMGUI_EXTRA_MATH_H__
|
||||||
|
# pragma once
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# include <imgui.h>
|
||||||
|
# ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||||
|
# define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
|
# endif
|
||||||
|
# include <imgui_internal.h>
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
struct ImLine
|
||||||
|
{
|
||||||
|
ImVec2 A, B;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Modern ImGui (1.89.4+) already defines these four operators in imgui.h
|
||||||
|
// itself when IMGUI_DEFINE_MATH_OPERATORS is set before its first include —
|
||||||
|
// guard against redefinition against this project's ImGui version.
|
||||||
|
# ifndef IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED
|
||||||
|
inline bool operator==(const ImVec2& lhs, const ImVec2& rhs);
|
||||||
|
inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs);
|
||||||
|
inline ImVec2 operator*(const float lhs, const ImVec2& rhs);
|
||||||
|
inline ImVec2 operator-(const ImVec2& lhs);
|
||||||
|
# endif
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
inline float ImLength(float v);
|
||||||
|
inline float ImLength(const ImVec2& v);
|
||||||
|
inline float ImLengthSqr(float v);
|
||||||
|
inline ImVec2 ImNormalized(const ImVec2& v);
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
inline bool ImRect_IsEmpty(const ImRect& rect);
|
||||||
|
inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImVec2& p, bool snap_to_edge);
|
||||||
|
inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImVec2& p, bool snap_to_edge, float radius);
|
||||||
|
inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImRect& b);
|
||||||
|
inline ImLine ImRect_ClosestLine(const ImRect& rect_a, const ImRect& rect_b);
|
||||||
|
inline ImLine ImRect_ClosestLine(const ImRect& rect_a, const ImRect& rect_b, float radius_a, float radius_b);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
namespace ImEasing {
|
||||||
|
|
||||||
|
template <typename V, typename T>
|
||||||
|
inline V EaseOutQuad(V b, V c, T t)
|
||||||
|
{
|
||||||
|
return b - c * (t * (t - 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ImEasing
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# include "imgui_extra_math.inl"
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# endif // __IMGUI_EXTRA_MATH_H__
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// LICENSE
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
//
|
||||||
|
// CREDITS
|
||||||
|
// Written by Michal Cichon
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# ifndef __IMGUI_EXTRA_MATH_INL__
|
||||||
|
# define __IMGUI_EXTRA_MATH_INL__
|
||||||
|
# pragma once
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# include "imgui_extra_math.h"
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// See guard note in imgui_extra_math.h — already provided by imgui.h on
|
||||||
|
// this project's ImGui version.
|
||||||
|
# ifndef IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED
|
||||||
|
inline bool operator==(const ImVec2& lhs, const ImVec2& rhs)
|
||||||
|
{
|
||||||
|
return lhs.x == rhs.x && lhs.y == rhs.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs)
|
||||||
|
{
|
||||||
|
return lhs.x != rhs.x || lhs.y != rhs.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec2 operator*(const float lhs, const ImVec2& rhs)
|
||||||
|
{
|
||||||
|
return ImVec2(lhs * rhs.x, lhs * rhs.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec2 operator-(const ImVec2& lhs)
|
||||||
|
{
|
||||||
|
return ImVec2(-lhs.x, -lhs.y);
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
inline float ImLength(float v)
|
||||||
|
{
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float ImLength(const ImVec2& v)
|
||||||
|
{
|
||||||
|
return ImSqrt(ImLengthSqr(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float ImLengthSqr(float v)
|
||||||
|
{
|
||||||
|
return v * v;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec2 ImNormalized(const ImVec2& v)
|
||||||
|
{
|
||||||
|
return v * ImInvLength(v, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
inline bool ImRect_IsEmpty(const ImRect& rect)
|
||||||
|
{
|
||||||
|
return rect.Min.x >= rect.Max.x
|
||||||
|
|| rect.Min.y >= rect.Max.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImVec2& p, bool snap_to_edge)
|
||||||
|
{
|
||||||
|
if (!snap_to_edge && rect.Contains(p))
|
||||||
|
return p;
|
||||||
|
|
||||||
|
return ImVec2(
|
||||||
|
(p.x > rect.Max.x) ? rect.Max.x : (p.x < rect.Min.x ? rect.Min.x : p.x),
|
||||||
|
(p.y > rect.Max.y) ? rect.Max.y : (p.y < rect.Min.y ? rect.Min.y : p.y)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImVec2& p, bool snap_to_edge, float radius)
|
||||||
|
{
|
||||||
|
auto point = ImRect_ClosestPoint(rect, p, snap_to_edge);
|
||||||
|
|
||||||
|
const auto offset = p - point;
|
||||||
|
const auto distance_sq = offset.x * offset.x + offset.y * offset.y;
|
||||||
|
if (distance_sq <= 0)
|
||||||
|
return point;
|
||||||
|
|
||||||
|
const auto distance = ImSqrt(distance_sq);
|
||||||
|
|
||||||
|
return point + offset * (ImMin(distance, radius) * (1.0f / distance));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImRect& other)
|
||||||
|
{
|
||||||
|
ImVec2 result;
|
||||||
|
if (other.Min.x >= rect.Max.x)
|
||||||
|
result.x = rect.Max.x;
|
||||||
|
else if (other.Max.x <= rect.Min.x)
|
||||||
|
result.x = rect.Min.x;
|
||||||
|
else
|
||||||
|
result.x = (ImMax(rect.Min.x, other.Min.x) + ImMin(rect.Max.x, other.Max.x)) / 2;
|
||||||
|
|
||||||
|
if (other.Min.y >= rect.Max.y)
|
||||||
|
result.y = rect.Max.y;
|
||||||
|
else if (other.Max.y <= rect.Min.y)
|
||||||
|
result.y = rect.Min.y;
|
||||||
|
else
|
||||||
|
result.y = (ImMax(rect.Min.y, other.Min.y) + ImMin(rect.Max.y, other.Max.y)) / 2;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImLine ImRect_ClosestLine(const ImRect& rect_a, const ImRect& rect_b)
|
||||||
|
{
|
||||||
|
ImLine result;
|
||||||
|
result.A = ImRect_ClosestPoint(rect_a, rect_b);
|
||||||
|
result.B = ImRect_ClosestPoint(rect_b, rect_a);
|
||||||
|
|
||||||
|
auto distribute = [](float& a, float& b, float a0, float a1, float b0, float b1)
|
||||||
|
{
|
||||||
|
if (a0 >= b1 || a1 <= b0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto aw = a1 - a0;
|
||||||
|
const auto bw = b1 - b0;
|
||||||
|
|
||||||
|
if (aw > bw)
|
||||||
|
{
|
||||||
|
b = b0 + bw - bw * (a - a0) / aw;
|
||||||
|
a = b;
|
||||||
|
}
|
||||||
|
else if (aw < bw)
|
||||||
|
{
|
||||||
|
a = a0 + aw - aw * (b - b0) / bw;
|
||||||
|
b = a;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
distribute(result.A.x, result.B.x, rect_a.Min.x, rect_a.Max.x, rect_b.Min.x, rect_b.Max.x);
|
||||||
|
distribute(result.A.y, result.B.y, rect_a.Min.y, rect_a.Max.y, rect_b.Min.y, rect_b.Max.y);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImLine ImRect_ClosestLine(const ImRect& rect_a, const ImRect& rect_b, float radius_a, float radius_b)
|
||||||
|
{
|
||||||
|
auto line = ImRect_ClosestLine(rect_a, rect_b);
|
||||||
|
if (radius_a < 0)
|
||||||
|
radius_a = 0;
|
||||||
|
if (radius_b < 0)
|
||||||
|
radius_b = 0;
|
||||||
|
|
||||||
|
if (radius_a == 0 && radius_b == 0)
|
||||||
|
return line;
|
||||||
|
|
||||||
|
const auto offset = line.B - line.A;
|
||||||
|
const auto length_sq = offset.x * offset.x + offset.y * offset.y;
|
||||||
|
const auto radius_a_sq = radius_a * radius_a;
|
||||||
|
const auto radius_b_sq = radius_b * radius_b;
|
||||||
|
|
||||||
|
if (length_sq <= 0)
|
||||||
|
return line;
|
||||||
|
|
||||||
|
const auto length = ImSqrt(length_sq);
|
||||||
|
const auto direction = ImVec2(offset.x / length, offset.y / length);
|
||||||
|
|
||||||
|
const auto total_radius_sq = radius_a_sq + radius_b_sq;
|
||||||
|
if (total_radius_sq > length_sq)
|
||||||
|
{
|
||||||
|
const auto scale = length / (radius_a + radius_b);
|
||||||
|
radius_a *= scale;
|
||||||
|
radius_b *= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
line.A = line.A + (direction * radius_a);
|
||||||
|
line.B = line.B - (direction * radius_b);
|
||||||
|
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# endif // __IMGUI_EXTRA_MATH_INL__
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,637 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// LICENSE
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
//
|
||||||
|
// CREDITS
|
||||||
|
// Written by Michal Cichon
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# include "imgui_node_editor_internal.h"
|
||||||
|
# include <algorithm>
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
static ax::NodeEditor::Detail::EditorContext* s_Editor = nullptr;
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
template <typename C, typename I, typename F>
|
||||||
|
static int BuildIdList(C& container, I* list, int listSize, F&& accept)
|
||||||
|
{
|
||||||
|
if (list != nullptr)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (auto object : container)
|
||||||
|
{
|
||||||
|
if (listSize <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (accept(object))
|
||||||
|
{
|
||||||
|
list[count] = I(object->ID().AsPointer());
|
||||||
|
++count;
|
||||||
|
--listSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return static_cast<int>(std::count_if(container.begin(), container.end(), accept));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
ax::NodeEditor::EditorContext* ax::NodeEditor::CreateEditor(const Config* config)
|
||||||
|
{
|
||||||
|
return reinterpret_cast<ax::NodeEditor::EditorContext*>(new ax::NodeEditor::Detail::EditorContext(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::DestroyEditor(EditorContext* ctx)
|
||||||
|
{
|
||||||
|
if (GetCurrentEditor() == ctx)
|
||||||
|
SetCurrentEditor(nullptr);
|
||||||
|
|
||||||
|
auto editor = reinterpret_cast<ax::NodeEditor::Detail::EditorContext*>(ctx);
|
||||||
|
|
||||||
|
delete editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::SetCurrentEditor(EditorContext* ctx)
|
||||||
|
{
|
||||||
|
s_Editor = reinterpret_cast<ax::NodeEditor::Detail::EditorContext*>(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
ax::NodeEditor::EditorContext* ax::NodeEditor::GetCurrentEditor()
|
||||||
|
{
|
||||||
|
return reinterpret_cast<ax::NodeEditor::EditorContext*>(s_Editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
ax::NodeEditor::Style& ax::NodeEditor::GetStyle()
|
||||||
|
{
|
||||||
|
return s_Editor->GetStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ax::NodeEditor::GetStyleColorName(StyleColor colorIndex)
|
||||||
|
{
|
||||||
|
return s_Editor->GetStyle().GetColorName(colorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::PushStyleColor(StyleColor colorIndex, const ImVec4& color)
|
||||||
|
{
|
||||||
|
s_Editor->GetStyle().PushColor(colorIndex, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::PopStyleColor(int count)
|
||||||
|
{
|
||||||
|
s_Editor->GetStyle().PopColor(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::PushStyleVar(StyleVar varIndex, float value)
|
||||||
|
{
|
||||||
|
s_Editor->GetStyle().PushVar(varIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::PushStyleVar(StyleVar varIndex, const ImVec2& value)
|
||||||
|
{
|
||||||
|
s_Editor->GetStyle().PushVar(varIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::PushStyleVar(StyleVar varIndex, const ImVec4& value)
|
||||||
|
{
|
||||||
|
s_Editor->GetStyle().PushVar(varIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::PopStyleVar(int count)
|
||||||
|
{
|
||||||
|
s_Editor->GetStyle().PopVar(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::Begin(const char* id, const ImVec2& size)
|
||||||
|
{
|
||||||
|
s_Editor->Begin(id, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::End()
|
||||||
|
{
|
||||||
|
s_Editor->End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::BeginNode(NodeId id)
|
||||||
|
{
|
||||||
|
s_Editor->GetNodeBuilder().Begin(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::BeginPin(PinId id, PinKind kind)
|
||||||
|
{
|
||||||
|
s_Editor->GetNodeBuilder().BeginPin(id, kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::PinRect(const ImVec2& a, const ImVec2& b)
|
||||||
|
{
|
||||||
|
s_Editor->GetNodeBuilder().PinRect(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::PinPivotRect(const ImVec2& a, const ImVec2& b)
|
||||||
|
{
|
||||||
|
s_Editor->GetNodeBuilder().PinPivotRect(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::PinPivotSize(const ImVec2& size)
|
||||||
|
{
|
||||||
|
s_Editor->GetNodeBuilder().PinPivotSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::PinPivotScale(const ImVec2& scale)
|
||||||
|
{
|
||||||
|
s_Editor->GetNodeBuilder().PinPivotScale(scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::PinPivotAlignment(const ImVec2& alignment)
|
||||||
|
{
|
||||||
|
s_Editor->GetNodeBuilder().PinPivotAlignment(alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::EndPin()
|
||||||
|
{
|
||||||
|
s_Editor->GetNodeBuilder().EndPin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::Group(const ImVec2& size)
|
||||||
|
{
|
||||||
|
s_Editor->GetNodeBuilder().Group(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::EndNode()
|
||||||
|
{
|
||||||
|
s_Editor->GetNodeBuilder().End();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::BeginGroupHint(NodeId nodeId)
|
||||||
|
{
|
||||||
|
return s_Editor->GetHintBuilder().Begin(nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ax::NodeEditor::GetGroupMin()
|
||||||
|
{
|
||||||
|
return s_Editor->GetHintBuilder().GetGroupMin();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ax::NodeEditor::GetGroupMax()
|
||||||
|
{
|
||||||
|
return s_Editor->GetHintBuilder().GetGroupMax();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImDrawList* ax::NodeEditor::GetHintForegroundDrawList()
|
||||||
|
{
|
||||||
|
return s_Editor->GetHintBuilder().GetForegroundDrawList();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImDrawList* ax::NodeEditor::GetHintBackgroundDrawList()
|
||||||
|
{
|
||||||
|
return s_Editor->GetHintBuilder().GetBackgroundDrawList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::EndGroupHint()
|
||||||
|
{
|
||||||
|
s_Editor->GetHintBuilder().End();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImDrawList* ax::NodeEditor::GetNodeBackgroundDrawList(NodeId nodeId)
|
||||||
|
{
|
||||||
|
if (auto node = s_Editor->FindNode(nodeId))
|
||||||
|
return s_Editor->GetNodeBuilder().GetUserBackgroundDrawList(node);
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::Link(LinkId id, PinId startPinId, PinId endPinId, const ImVec4& color/* = ImVec4(1, 1, 1, 1)*/, float thickness/* = 1.0f*/)
|
||||||
|
{
|
||||||
|
return s_Editor->DoLink(id, startPinId, endPinId, ImColor(color), thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::Flow(LinkId linkId)
|
||||||
|
{
|
||||||
|
if (auto link = s_Editor->FindLink(linkId))
|
||||||
|
s_Editor->Flow(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::BeginCreate(const ImVec4& color, float thickness)
|
||||||
|
{
|
||||||
|
auto& context = s_Editor->GetItemCreator();
|
||||||
|
|
||||||
|
if (context.Begin())
|
||||||
|
{
|
||||||
|
context.SetStyle(ImColor(color), thickness);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::QueryNewLink(PinId* startId, PinId* endId)
|
||||||
|
{
|
||||||
|
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
|
||||||
|
|
||||||
|
auto& context = s_Editor->GetItemCreator();
|
||||||
|
|
||||||
|
return context.QueryLink(startId, endId) == Result::True;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::QueryNewLink(PinId* startId, PinId* endId, const ImVec4& color, float thickness)
|
||||||
|
{
|
||||||
|
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
|
||||||
|
|
||||||
|
auto& context = s_Editor->GetItemCreator();
|
||||||
|
|
||||||
|
auto result = context.QueryLink(startId, endId);
|
||||||
|
if (result != Result::Indeterminate)
|
||||||
|
context.SetStyle(ImColor(color), thickness);
|
||||||
|
|
||||||
|
return result == Result::True;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::QueryNewNode(PinId* pinId)
|
||||||
|
{
|
||||||
|
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
|
||||||
|
|
||||||
|
auto& context = s_Editor->GetItemCreator();
|
||||||
|
|
||||||
|
return context.QueryNode(pinId) == Result::True;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::QueryNewNode(PinId* pinId, const ImVec4& color, float thickness)
|
||||||
|
{
|
||||||
|
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
|
||||||
|
|
||||||
|
auto& context = s_Editor->GetItemCreator();
|
||||||
|
|
||||||
|
auto result = context.QueryNode(pinId);
|
||||||
|
if (result != Result::Indeterminate)
|
||||||
|
context.SetStyle(ImColor(color), thickness);
|
||||||
|
|
||||||
|
return result == Result::True;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::AcceptNewItem()
|
||||||
|
{
|
||||||
|
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
|
||||||
|
|
||||||
|
auto& context = s_Editor->GetItemCreator();
|
||||||
|
|
||||||
|
return context.AcceptItem() == Result::True;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::AcceptNewItem(const ImVec4& color, float thickness)
|
||||||
|
{
|
||||||
|
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
|
||||||
|
|
||||||
|
auto& context = s_Editor->GetItemCreator();
|
||||||
|
|
||||||
|
auto result = context.AcceptItem();
|
||||||
|
if (result != Result::Indeterminate)
|
||||||
|
context.SetStyle(ImColor(color), thickness);
|
||||||
|
|
||||||
|
return result == Result::True;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::RejectNewItem()
|
||||||
|
{
|
||||||
|
auto& context = s_Editor->GetItemCreator();
|
||||||
|
|
||||||
|
context.RejectItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::RejectNewItem(const ImVec4& color, float thickness)
|
||||||
|
{
|
||||||
|
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
|
||||||
|
|
||||||
|
auto& context = s_Editor->GetItemCreator();
|
||||||
|
|
||||||
|
if (context.RejectItem() != Result::Indeterminate)
|
||||||
|
context.SetStyle(ImColor(color), thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::EndCreate()
|
||||||
|
{
|
||||||
|
auto& context = s_Editor->GetItemCreator();
|
||||||
|
|
||||||
|
context.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::BeginDelete()
|
||||||
|
{
|
||||||
|
auto& context = s_Editor->GetItemDeleter();
|
||||||
|
|
||||||
|
return context.Begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::QueryDeletedLink(LinkId* linkId, PinId* startId, PinId* endId)
|
||||||
|
{
|
||||||
|
auto& context = s_Editor->GetItemDeleter();
|
||||||
|
|
||||||
|
return context.QueryLink(linkId, startId, endId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::QueryDeletedNode(NodeId* nodeId)
|
||||||
|
{
|
||||||
|
auto& context = s_Editor->GetItemDeleter();
|
||||||
|
|
||||||
|
return context.QueryNode(nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::AcceptDeletedItem()
|
||||||
|
{
|
||||||
|
auto& context = s_Editor->GetItemDeleter();
|
||||||
|
|
||||||
|
return context.AcceptItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::RejectDeletedItem()
|
||||||
|
{
|
||||||
|
auto& context = s_Editor->GetItemDeleter();
|
||||||
|
|
||||||
|
context.RejectItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::EndDelete()
|
||||||
|
{
|
||||||
|
auto& context = s_Editor->GetItemDeleter();
|
||||||
|
|
||||||
|
context.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::SetNodePosition(NodeId nodeId, const ImVec2& position)
|
||||||
|
{
|
||||||
|
s_Editor->SetNodePosition(nodeId, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ax::NodeEditor::GetNodePosition(NodeId nodeId)
|
||||||
|
{
|
||||||
|
return s_Editor->GetNodePosition(nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ax::NodeEditor::GetNodeSize(NodeId nodeId)
|
||||||
|
{
|
||||||
|
return s_Editor->GetNodeSize(nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::CenterNodeOnScreen(NodeId nodeId)
|
||||||
|
{
|
||||||
|
if (auto node = s_Editor->FindNode(nodeId))
|
||||||
|
node->CenterOnScreenInNextFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::RestoreNodeState(NodeId nodeId)
|
||||||
|
{
|
||||||
|
if (auto node = s_Editor->FindNode(nodeId))
|
||||||
|
s_Editor->MarkNodeToRestoreState(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::Suspend()
|
||||||
|
{
|
||||||
|
s_Editor->Suspend();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::Resume()
|
||||||
|
{
|
||||||
|
s_Editor->Resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::IsSuspended()
|
||||||
|
{
|
||||||
|
return s_Editor->IsSuspended();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::IsActive()
|
||||||
|
{
|
||||||
|
return s_Editor->IsActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::HasSelectionChanged()
|
||||||
|
{
|
||||||
|
return s_Editor->HasSelectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ax::NodeEditor::GetSelectedObjectCount()
|
||||||
|
{
|
||||||
|
return (int)s_Editor->GetSelectedObjects().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ax::NodeEditor::GetSelectedNodes(NodeId* nodes, int size)
|
||||||
|
{
|
||||||
|
return BuildIdList(s_Editor->GetSelectedObjects(), nodes, size, [](auto object)
|
||||||
|
{
|
||||||
|
return object->AsNode() != nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int ax::NodeEditor::GetSelectedLinks(LinkId* links, int size)
|
||||||
|
{
|
||||||
|
return BuildIdList(s_Editor->GetSelectedObjects(), links, size, [](auto object)
|
||||||
|
{
|
||||||
|
return object->AsLink() != nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::ClearSelection()
|
||||||
|
{
|
||||||
|
s_Editor->ClearSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::SelectNode(NodeId nodeId, bool append)
|
||||||
|
{
|
||||||
|
if (auto node = s_Editor->FindNode(nodeId))
|
||||||
|
{
|
||||||
|
if (append)
|
||||||
|
s_Editor->SelectObject(node);
|
||||||
|
else
|
||||||
|
s_Editor->SetSelectedObject(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::SelectLink(LinkId linkId, bool append)
|
||||||
|
{
|
||||||
|
if (auto link = s_Editor->FindLink(linkId))
|
||||||
|
{
|
||||||
|
if (append)
|
||||||
|
s_Editor->SelectObject(link);
|
||||||
|
else
|
||||||
|
s_Editor->SetSelectedObject(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::DeselectNode(NodeId nodeId)
|
||||||
|
{
|
||||||
|
if (auto node = s_Editor->FindNode(nodeId))
|
||||||
|
s_Editor->DeselectObject(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::DeselectLink(LinkId linkId)
|
||||||
|
{
|
||||||
|
if (auto link = s_Editor->FindLink(linkId))
|
||||||
|
s_Editor->DeselectObject(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::DeleteNode(NodeId nodeId)
|
||||||
|
{
|
||||||
|
if (auto node = s_Editor->FindNode(nodeId))
|
||||||
|
return s_Editor->GetItemDeleter().Add(node);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::DeleteLink(LinkId linkId)
|
||||||
|
{
|
||||||
|
if (auto link = s_Editor->FindLink(linkId))
|
||||||
|
return s_Editor->GetItemDeleter().Add(link);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::NavigateToContent(float duration)
|
||||||
|
{
|
||||||
|
s_Editor->NavigateTo(s_Editor->GetContentBounds(), true, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::NavigateToSelection(bool zoomIn, float duration)
|
||||||
|
{
|
||||||
|
s_Editor->NavigateTo(s_Editor->GetSelectionBounds(), zoomIn, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::ShowNodeContextMenu(NodeId* nodeId)
|
||||||
|
{
|
||||||
|
return s_Editor->GetContextMenu().ShowNodeContextMenu(nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::ShowPinContextMenu(PinId* pinId)
|
||||||
|
{
|
||||||
|
return s_Editor->GetContextMenu().ShowPinContextMenu(pinId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::ShowLinkContextMenu(LinkId* linkId)
|
||||||
|
{
|
||||||
|
return s_Editor->GetContextMenu().ShowLinkContextMenu(linkId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::ShowBackgroundContextMenu()
|
||||||
|
{
|
||||||
|
return s_Editor->GetContextMenu().ShowBackgroundContextMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::EnableShortcuts(bool enable)
|
||||||
|
{
|
||||||
|
s_Editor->EnableShortcuts(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::AreShortcutsEnabled()
|
||||||
|
{
|
||||||
|
return s_Editor->AreShortcutsEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::BeginShortcut()
|
||||||
|
{
|
||||||
|
return s_Editor->GetShortcut().Begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::AcceptCut()
|
||||||
|
{
|
||||||
|
return s_Editor->GetShortcut().AcceptCut();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::AcceptCopy()
|
||||||
|
{
|
||||||
|
return s_Editor->GetShortcut().AcceptCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::AcceptPaste()
|
||||||
|
{
|
||||||
|
return s_Editor->GetShortcut().AcceptPaste();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::AcceptDuplicate()
|
||||||
|
{
|
||||||
|
return s_Editor->GetShortcut().AcceptDuplicate();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::AcceptCreateNode()
|
||||||
|
{
|
||||||
|
return s_Editor->GetShortcut().AcceptCreateNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ax::NodeEditor::GetActionContextSize()
|
||||||
|
{
|
||||||
|
return static_cast<int>(s_Editor->GetShortcut().m_Context.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int ax::NodeEditor::GetActionContextNodes(NodeId* nodes, int size)
|
||||||
|
{
|
||||||
|
return BuildIdList(s_Editor->GetSelectedObjects(), nodes, size, [](auto object)
|
||||||
|
{
|
||||||
|
return object->AsNode() != nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int ax::NodeEditor::GetActionContextLinks(LinkId* links, int size)
|
||||||
|
{
|
||||||
|
return BuildIdList(s_Editor->GetSelectedObjects(), links, size, [](auto object)
|
||||||
|
{
|
||||||
|
return object->AsLink() != nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ax::NodeEditor::EndShortcut()
|
||||||
|
{
|
||||||
|
return s_Editor->GetShortcut().End();
|
||||||
|
}
|
||||||
|
|
||||||
|
float ax::NodeEditor::GetCurrentZoom()
|
||||||
|
{
|
||||||
|
return s_Editor->GetView().InvScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
ax::NodeEditor::NodeId ax::NodeEditor::GetDoubleClickedNode()
|
||||||
|
{
|
||||||
|
return s_Editor->GetDoubleClickedNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
ax::NodeEditor::PinId ax::NodeEditor::GetDoubleClickedPin()
|
||||||
|
{
|
||||||
|
return s_Editor->GetDoubleClickedPin();
|
||||||
|
}
|
||||||
|
|
||||||
|
ax::NodeEditor::LinkId ax::NodeEditor::GetDoubleClickedLink()
|
||||||
|
{
|
||||||
|
return s_Editor->GetDoubleClickedLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::IsBackgroundClicked()
|
||||||
|
{
|
||||||
|
return s_Editor->IsBackgroundClicked();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::IsBackgroundDoubleClicked()
|
||||||
|
{
|
||||||
|
return s_Editor->IsBackgroundDoubleClicked();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ax::NodeEditor::PinHadAnyLinks(PinId pinId)
|
||||||
|
{
|
||||||
|
return s_Editor->PinHadAnyLinks(pinId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ax::NodeEditor::GetScreenSize()
|
||||||
|
{
|
||||||
|
return s_Editor->GetRect().GetSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ax::NodeEditor::ScreenToCanvas(const ImVec2& pos)
|
||||||
|
{
|
||||||
|
return s_Editor->ToCanvas(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ax::NodeEditor::CanvasToScreen(const ImVec2& pos)
|
||||||
|
{
|
||||||
|
return s_Editor->ToScreen(pos);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// LICENSE
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
//
|
||||||
|
// CREDITS
|
||||||
|
// Written by Michal Cichon
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# ifndef __IMGUI_NODE_EDITOR_INTERNAL_INL__
|
||||||
|
# define __IMGUI_NODE_EDITOR_INTERNAL_INL__
|
||||||
|
# pragma once
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# include "imgui_node_editor_internal.h"
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
namespace ax {
|
||||||
|
namespace NodeEditor {
|
||||||
|
namespace Detail {
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
//inline ImRect ToRect(const ax::rectf& rect)
|
||||||
|
//{
|
||||||
|
// return ImRect(
|
||||||
|
// to_imvec(rect.top_left()),
|
||||||
|
// to_imvec(rect.bottom_right())
|
||||||
|
// );
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//inline ImRect ToRect(const ax::rect& rect)
|
||||||
|
//{
|
||||||
|
// return ImRect(
|
||||||
|
// to_imvec(rect.top_left()),
|
||||||
|
// to_imvec(rect.bottom_right())
|
||||||
|
// );
|
||||||
|
//}
|
||||||
|
|
||||||
|
inline ImRect ImGui_GetItemRect()
|
||||||
|
{
|
||||||
|
return ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
} // namespace Detail
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace ax
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# endif // __IMGUI_NODE_EDITOR_INTERNAL_INL__
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
#include "NodeThumbnailCache.h"
|
||||||
|
#include "../utils/GLExt.h"
|
||||||
|
#include "../utils/Logger.h"
|
||||||
|
|
||||||
|
#include <pxr/imaging/hio/image.h>
|
||||||
|
#include <pxr/imaging/hio/types.h>
|
||||||
|
#include <pxr/base/gf/half.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace UsdLayerManager {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Mix a graph revision with a node path so switching which node the shared
|
||||||
|
// preview renderer draws always reads as "changed" to MaterialPreviewRenderer's
|
||||||
|
// revision gate (it treats the value as an opaque change token), while a real
|
||||||
|
// graph edit (revision bump) still forces a re-render.
|
||||||
|
size_t RenderToken(size_t revision, const std::string& nodeKey) {
|
||||||
|
size_t h = revision * 1099511628211ull;
|
||||||
|
h ^= std::hash<std::string>{}(nodeKey) + 0x9e3779b97f4a7c15ull + (h << 6) + (h >> 2);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
NodeThumbnailCache::NodeThumbnailCache() {
|
||||||
|
// Prime Hio's plugin registry on the main thread so the worker never
|
||||||
|
// first-touches plugin discovery off-thread.
|
||||||
|
pxr::HioImage::IsSupportedImageFile("prime.png");
|
||||||
|
|
||||||
|
m_running = true;
|
||||||
|
m_worker = std::thread(&NodeThumbnailCache::WorkerLoop, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeThumbnailCache::~NodeThumbnailCache() {
|
||||||
|
m_running = false;
|
||||||
|
m_jobCv.notify_all();
|
||||||
|
if (m_worker.joinable())
|
||||||
|
m_worker.join();
|
||||||
|
Clear();
|
||||||
|
if (m_readFbo) glDeleteFramebuffers(1, &m_readFbo);
|
||||||
|
if (m_drawFbo) glDeleteFramebuffers(1, &m_drawFbo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Texture thumbnails
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ImTextureID NodeThumbnailCache::GetTextureThumbnail(const pxr::SdfPath& node,
|
||||||
|
const std::string& resolvedFile) {
|
||||||
|
const std::string key = node.GetString();
|
||||||
|
TexEntry& e = m_texEntries[key];
|
||||||
|
e.touched = true;
|
||||||
|
if (resolvedFile.empty())
|
||||||
|
return e.texId;
|
||||||
|
// (Re)request when the file differs from both the uploaded and in-flight one.
|
||||||
|
if (resolvedFile != e.fileKey && resolvedFile != e.reqFile) {
|
||||||
|
e.reqFile = resolvedFile;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(m_jobMutex);
|
||||||
|
m_jobs.push_back(TexJob{key, resolvedFile});
|
||||||
|
}
|
||||||
|
m_jobCv.notify_one();
|
||||||
|
}
|
||||||
|
return e.texId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NodeThumbnailCache::WorkerLoop() {
|
||||||
|
for (;;) {
|
||||||
|
TexJob job;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(m_jobMutex);
|
||||||
|
m_jobCv.wait(lk, [this] { return !m_running || !m_jobs.empty(); });
|
||||||
|
if (!m_running) return;
|
||||||
|
job = std::move(m_jobs.front());
|
||||||
|
m_jobs.pop_front();
|
||||||
|
}
|
||||||
|
TexResult result;
|
||||||
|
result.nodeKey = job.nodeKey;
|
||||||
|
result.file = job.file;
|
||||||
|
result.ok = DecodeThumbnail(job.file, result);
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(m_resultMutex);
|
||||||
|
m_results.push_back(std::move(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NodeThumbnailCache::DecodeThumbnail(const std::string& file, TexResult& out) {
|
||||||
|
pxr::HioImageSharedPtr img = pxr::HioImage::OpenForReading(file, /*subimage=*/0, /*mip=*/0,
|
||||||
|
pxr::HioImage::SourceColorSpace::Auto,
|
||||||
|
/*suppressErrors=*/true);
|
||||||
|
if (!img) return false;
|
||||||
|
const int sw = img->GetWidth();
|
||||||
|
const int sh = img->GetHeight();
|
||||||
|
if (sw <= 0 || sh <= 0) return false;
|
||||||
|
|
||||||
|
// Hio's stb reader does no format conversion: it requires the requested
|
||||||
|
// StorageSpec.format to *exactly* equal the file's own format (channel
|
||||||
|
// count + component type + sRGB-ness) or it raises "Image format mismatch".
|
||||||
|
// So read into the image's native format and normalize to RGBA8 ourselves.
|
||||||
|
const pxr::HioFormat fmt = img->GetFormat();
|
||||||
|
const int nComp = pxr::HioGetComponentCount(fmt);
|
||||||
|
const pxr::HioType type = pxr::HioGetHioType(fmt);
|
||||||
|
if (nComp < 1 || nComp > 4) return false;
|
||||||
|
|
||||||
|
size_t bpc = 0; // bytes per component
|
||||||
|
switch (type) {
|
||||||
|
case pxr::HioTypeUnsignedByte:
|
||||||
|
case pxr::HioTypeUnsignedByteSRGB:
|
||||||
|
case pxr::HioTypeSignedByte: bpc = 1; break;
|
||||||
|
case pxr::HioTypeUnsignedShort:
|
||||||
|
case pxr::HioTypeSignedShort:
|
||||||
|
case pxr::HioTypeHalfFloat: bpc = 2; break;
|
||||||
|
case pxr::HioTypeUnsignedInt:
|
||||||
|
case pxr::HioTypeInt:
|
||||||
|
case pxr::HioTypeFloat: bpc = 4; break;
|
||||||
|
case pxr::HioTypeDouble: bpc = 8; break;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t pixels = static_cast<size_t>(sw) * sh;
|
||||||
|
std::vector<unsigned char> raw(pixels * nComp * bpc);
|
||||||
|
pxr::HioImage::StorageSpec spec;
|
||||||
|
spec.width = sw;
|
||||||
|
spec.height = sh;
|
||||||
|
spec.depth = 1;
|
||||||
|
spec.format = fmt;
|
||||||
|
spec.flipped = false; // keep top row first for ImGui display
|
||||||
|
spec.data = raw.data();
|
||||||
|
if (!img->Read(spec)) return false;
|
||||||
|
|
||||||
|
// Collapse whatever component type the file uses down to an 8-bit value.
|
||||||
|
// sRGB byte data is passed through as-is (ImGui does no color management,
|
||||||
|
// so the encoded bytes display correctly); float/half is clamped to [0,1].
|
||||||
|
const void* rp = raw.data();
|
||||||
|
auto toU8 = [&](size_t i) -> unsigned char {
|
||||||
|
switch (type) {
|
||||||
|
case pxr::HioTypeUnsignedByte:
|
||||||
|
case pxr::HioTypeUnsignedByteSRGB:
|
||||||
|
return reinterpret_cast<const uint8_t*>(rp)[i];
|
||||||
|
case pxr::HioTypeSignedByte:
|
||||||
|
return static_cast<unsigned char>(std::max(0, static_cast<int>(reinterpret_cast<const int8_t*>(rp)[i]) * 2));
|
||||||
|
case pxr::HioTypeUnsignedShort:
|
||||||
|
return static_cast<unsigned char>(reinterpret_cast<const uint16_t*>(rp)[i] >> 8);
|
||||||
|
case pxr::HioTypeSignedShort:
|
||||||
|
return static_cast<unsigned char>(std::clamp(static_cast<int>(reinterpret_cast<const int16_t*>(rp)[i]) >> 7, 0, 255));
|
||||||
|
case pxr::HioTypeHalfFloat:
|
||||||
|
return static_cast<unsigned char>(std::clamp(static_cast<float>(reinterpret_cast<const pxr::GfHalf*>(rp)[i]), 0.f, 1.f) * 255.f + 0.5f);
|
||||||
|
case pxr::HioTypeFloat:
|
||||||
|
return static_cast<unsigned char>(std::clamp(reinterpret_cast<const float*>(rp)[i], 0.f, 1.f) * 255.f + 0.5f);
|
||||||
|
case pxr::HioTypeUnsignedInt:
|
||||||
|
return static_cast<unsigned char>(reinterpret_cast<const uint32_t*>(rp)[i] >> 24);
|
||||||
|
case pxr::HioTypeInt:
|
||||||
|
return static_cast<unsigned char>(std::clamp(reinterpret_cast<const int32_t*>(rp)[i] >> 23, 0, 255));
|
||||||
|
case pxr::HioTypeDouble:
|
||||||
|
return static_cast<unsigned char>(std::clamp(reinterpret_cast<const double*>(rp)[i], 0.0, 1.0) * 255.0 + 0.5);
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Expand to RGBA8: 1ch → grey, 2ch → grey+alpha, 3ch → opaque, 4ch → as-is.
|
||||||
|
std::vector<unsigned char> native(pixels * 4);
|
||||||
|
for (size_t px = 0; px < pixels; ++px) {
|
||||||
|
const size_t s = px * nComp;
|
||||||
|
const unsigned char c0 = toU8(s);
|
||||||
|
unsigned char* d = &native[px * 4];
|
||||||
|
d[0] = c0;
|
||||||
|
d[1] = nComp >= 3 ? toU8(s + 1) : c0;
|
||||||
|
d[2] = nComp >= 3 ? toU8(s + 2) : c0;
|
||||||
|
d[3] = nComp == 4 ? toU8(s + 3) : (nComp == 2 ? toU8(s + 1) : 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int longSide = std::max(sw, sh);
|
||||||
|
const float scale = longSide > kThumbPx ? static_cast<float>(kThumbPx) / longSide : 1.0f;
|
||||||
|
const int dw = std::max(1, static_cast<int>(std::lround(sw * scale)));
|
||||||
|
const int dh = std::max(1, static_cast<int>(std::lround(sh * scale)));
|
||||||
|
|
||||||
|
if (dw == sw && dh == sh) {
|
||||||
|
out.rgba = std::move(native);
|
||||||
|
} else {
|
||||||
|
out.rgba.assign(static_cast<size_t>(dw) * dh * 4, 0);
|
||||||
|
for (int y = 0; y < dh; ++y) {
|
||||||
|
const int sy0 = y * sh / dh, sy1 = std::max(sy0 + 1, (y + 1) * sh / dh);
|
||||||
|
for (int x = 0; x < dw; ++x) {
|
||||||
|
const int sx0 = x * sw / dw, sx1 = std::max(sx0 + 1, (x + 1) * sw / dw);
|
||||||
|
unsigned int acc[4] = {0, 0, 0, 0};
|
||||||
|
unsigned int n = 0;
|
||||||
|
for (int sy = sy0; sy < sy1; ++sy)
|
||||||
|
for (int sx = sx0; sx < sx1; ++sx) {
|
||||||
|
const unsigned char* p = &native[(static_cast<size_t>(sy) * sw + sx) * 4];
|
||||||
|
acc[0] += p[0]; acc[1] += p[1]; acc[2] += p[2]; acc[3] += p[3];
|
||||||
|
++n;
|
||||||
|
}
|
||||||
|
unsigned char* d = &out.rgba[(static_cast<size_t>(y) * dw + x) * 4];
|
||||||
|
d[0] = static_cast<unsigned char>(acc[0] / n);
|
||||||
|
d[1] = static_cast<unsigned char>(acc[1] / n);
|
||||||
|
d[2] = static_cast<unsigned char>(acc[2] / n);
|
||||||
|
d[3] = static_cast<unsigned char>(acc[3] / n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.w = dw;
|
||||||
|
out.h = dh;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NodeThumbnailCache::UploadTexture(TexEntry& e, const TexResult& r) {
|
||||||
|
DeleteTex(e.texId);
|
||||||
|
GLuint tex = 0;
|
||||||
|
glGenTextures(1, &tex);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, tex);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, r.w, r.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, r.rgba.data());
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
e.texId = static_cast<ImTextureID>(static_cast<ImU64>(tex));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Material thumbnails
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ImTextureID NodeThumbnailCache::GetMaterialThumbnail(const pxr::UsdStageRefPtr& stage,
|
||||||
|
const pxr::SdfPath& materialPath,
|
||||||
|
const pxr::SdfPath& node,
|
||||||
|
const std::string& output, bool terminal,
|
||||||
|
size_t revision) {
|
||||||
|
MatEntry& e = m_matEntries[node.GetString()];
|
||||||
|
e.touched = true;
|
||||||
|
e.stage = stage;
|
||||||
|
e.materialPath = materialPath;
|
||||||
|
e.nodePath = node;
|
||||||
|
e.output = output;
|
||||||
|
e.terminal = terminal;
|
||||||
|
if (revision != e.revision || e.texId == 0) {
|
||||||
|
e.revision = revision;
|
||||||
|
e.stale = true;
|
||||||
|
}
|
||||||
|
return e.texId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NodeThumbnailCache::EnsureBlitFbos() {
|
||||||
|
if (!m_readFbo) glGenFramebuffers(1, &m_readFbo);
|
||||||
|
if (!m_drawFbo) glGenFramebuffers(1, &m_drawFbo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NodeThumbnailCache::BlitToEntry(MatEntry& e, uint32_t srcTex) {
|
||||||
|
int sw = 0, sh = 0;
|
||||||
|
glBindTexture(GL_TEXTURE_2D, srcTex);
|
||||||
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &sw);
|
||||||
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &sh);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
if (sw <= 0 || sh <= 0) return;
|
||||||
|
|
||||||
|
if (e.texId == 0) {
|
||||||
|
GLuint tex = 0;
|
||||||
|
glGenTextures(1, &tex);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, tex);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kThumbPx, kThumbPx, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
e.texId = static_cast<ImTextureID>(static_cast<ImU64>(tex));
|
||||||
|
}
|
||||||
|
const GLuint dst = static_cast<GLuint>(static_cast<ImU64>(e.texId));
|
||||||
|
|
||||||
|
EnsureBlitFbos();
|
||||||
|
GLint prevRead = 0, prevDraw = 0;
|
||||||
|
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prevRead);
|
||||||
|
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &prevDraw);
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_readFbo);
|
||||||
|
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, srcTex, 0);
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_drawFbo);
|
||||||
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst, 0);
|
||||||
|
// Flip Y (dst rows reversed): the draw target is a GL bottom-up texture, so
|
||||||
|
// this leaves the cache texture top-down for uniform ImGui (0,0)-(1,1) UVs.
|
||||||
|
glBlitFramebuffer(0, 0, sw, sh, 0, kThumbPx, kThumbPx, 0, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, static_cast<GLuint>(prevRead));
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, static_cast<GLuint>(prevDraw));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Per-frame pump / lifecycle
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void NodeThumbnailCache::PumpMainThread() {
|
||||||
|
// 1. Upload finished texture decodes.
|
||||||
|
std::vector<TexResult> done;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(m_resultMutex);
|
||||||
|
done.swap(m_results);
|
||||||
|
}
|
||||||
|
for (const TexResult& r : done) {
|
||||||
|
auto it = m_texEntries.find(r.nodeKey);
|
||||||
|
if (it == m_texEntries.end()) continue;
|
||||||
|
TexEntry& e = it->second;
|
||||||
|
if (r.file != e.reqFile) continue; // superseded by a newer request
|
||||||
|
if (r.ok) {
|
||||||
|
UploadTexture(e, r);
|
||||||
|
e.fileKey = r.file;
|
||||||
|
}
|
||||||
|
e.reqFile.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Render at most one stale material thumbnail (amortized).
|
||||||
|
for (auto& kv : m_matEntries) {
|
||||||
|
MatEntry& e = kv.second;
|
||||||
|
if (!e.stale || !e.touched || !e.stage) continue;
|
||||||
|
m_matRenderer.SetMaterial(e.stage, e.materialPath, RenderToken(e.revision, kv.first),
|
||||||
|
e.nodePath, e.output, e.terminal);
|
||||||
|
const uint32_t src = m_matRenderer.Render(kThumbPx, kThumbPx);
|
||||||
|
if (src != 0) {
|
||||||
|
BlitToEntry(e, src);
|
||||||
|
e.stale = false;
|
||||||
|
}
|
||||||
|
break; // one per frame
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Prune entries not requested since the previous pump (node deleted or
|
||||||
|
// no longer a thumbnailed category), then reset touch flags for this frame.
|
||||||
|
for (auto it = m_texEntries.begin(); it != m_texEntries.end();) {
|
||||||
|
if (!it->second.touched) { DeleteTex(it->second.texId); it = m_texEntries.erase(it); }
|
||||||
|
else { it->second.touched = false; ++it; }
|
||||||
|
}
|
||||||
|
for (auto it = m_matEntries.begin(); it != m_matEntries.end();) {
|
||||||
|
if (!it->second.touched) { DeleteTex(it->second.texId); it = m_matEntries.erase(it); }
|
||||||
|
else { it->second.touched = false; ++it; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NodeThumbnailCache::Clear() {
|
||||||
|
for (auto& kv : m_texEntries) DeleteTex(kv.second.texId);
|
||||||
|
for (auto& kv : m_matEntries) DeleteTex(kv.second.texId);
|
||||||
|
m_texEntries.clear();
|
||||||
|
m_matEntries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NodeThumbnailCache::DeleteTex(ImTextureID& id) {
|
||||||
|
if (id) {
|
||||||
|
GLuint t = static_cast<GLuint>(static_cast<ImU64>(id));
|
||||||
|
glDeleteTextures(1, &t);
|
||||||
|
id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace UsdLayerManager
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MaterialPreviewRenderer.h"
|
||||||
|
#include <pxr/usd/usd/stage.h>
|
||||||
|
#include <pxr/usd/sdf/path.h>
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <deque>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace UsdLayerManager {
|
||||||
|
|
||||||
|
/// Owns the in-canvas node thumbnails for the material editor.
|
||||||
|
///
|
||||||
|
/// Two kinds, both handed back as an ImTextureID (0 while not ready):
|
||||||
|
/// - Texture nodes: the source image, decoded on a background worker thread
|
||||||
|
/// (USD HioImage / OpenImageIO) and uploaded to GL on the main thread.
|
||||||
|
/// - Material nodes: a shader-ball render through a dedicated
|
||||||
|
/// MaterialPreviewRenderer. Hydra is GL-/main-thread-bound, so these are
|
||||||
|
/// rendered one-per-frame (amortized) and cached by graph revision.
|
||||||
|
///
|
||||||
|
/// Lifecycle is driven from the panel: Get*Thumbnail() per node during the
|
||||||
|
/// canvas draw, then PumpMainThread() once per frame (GL context current).
|
||||||
|
class NodeThumbnailCache {
|
||||||
|
public:
|
||||||
|
NodeThumbnailCache();
|
||||||
|
~NodeThumbnailCache();
|
||||||
|
|
||||||
|
NodeThumbnailCache(const NodeThumbnailCache&) = delete;
|
||||||
|
NodeThumbnailCache& operator=(const NodeThumbnailCache&) = delete;
|
||||||
|
|
||||||
|
/// Displayed edge length; also the material render size.
|
||||||
|
static constexpr int kThumbPx = 96;
|
||||||
|
|
||||||
|
/// Returns the node's texture thumbnail (0 = not ready). Enqueues a
|
||||||
|
/// background decode when resolvedFile is new or has changed. An empty
|
||||||
|
/// resolvedFile (unresolvable asset) yields whatever is cached (usually 0).
|
||||||
|
ImTextureID GetTextureThumbnail(const pxr::SdfPath& node, const std::string& resolvedFile);
|
||||||
|
|
||||||
|
/// Returns the node's material shader-ball thumbnail (0 = not ready yet).
|
||||||
|
/// Marks it stale (to be re-rendered on a later pump) when revision changes.
|
||||||
|
/// output/terminal select the node output to preview (a token-typed
|
||||||
|
/// terminal is wired as the surface; otherwise routed into diffuseColor).
|
||||||
|
ImTextureID GetMaterialThumbnail(const pxr::UsdStageRefPtr& stage,
|
||||||
|
const pxr::SdfPath& materialPath,
|
||||||
|
const pxr::SdfPath& node,
|
||||||
|
const std::string& output, bool terminal,
|
||||||
|
size_t revision);
|
||||||
|
|
||||||
|
/// Main thread, once per frame, GL context current: drains finished
|
||||||
|
/// decodes and uploads them, renders at most one stale material thumbnail,
|
||||||
|
/// and prunes entries not requested since the previous pump.
|
||||||
|
void PumpMainThread();
|
||||||
|
|
||||||
|
/// Frees every thumbnail (call on material/stage change). Main thread.
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// ---- texture thumbnails (background decode) ----
|
||||||
|
struct TexJob { std::string nodeKey; std::string file; };
|
||||||
|
struct TexResult { std::string nodeKey; std::string file;
|
||||||
|
std::vector<unsigned char> rgba; int w = 0, h = 0; bool ok = false; };
|
||||||
|
struct TexEntry { ImTextureID texId = 0; std::string fileKey; std::string reqFile;
|
||||||
|
bool touched = false; };
|
||||||
|
|
||||||
|
void WorkerLoop();
|
||||||
|
static bool DecodeThumbnail(const std::string& file, TexResult& out);
|
||||||
|
void UploadTexture(TexEntry& e, const TexResult& r);
|
||||||
|
|
||||||
|
std::thread m_worker;
|
||||||
|
std::atomic<bool> m_running{false};
|
||||||
|
std::mutex m_jobMutex;
|
||||||
|
std::condition_variable m_jobCv;
|
||||||
|
std::deque<TexJob> m_jobs;
|
||||||
|
std::mutex m_resultMutex;
|
||||||
|
std::vector<TexResult> m_results;
|
||||||
|
std::unordered_map<std::string, TexEntry> m_texEntries;
|
||||||
|
|
||||||
|
// ---- material thumbnails (main-thread, amortized) ----
|
||||||
|
struct MatEntry {
|
||||||
|
ImTextureID texId = 0;
|
||||||
|
size_t revision = 0;
|
||||||
|
bool stale = true;
|
||||||
|
bool touched = false;
|
||||||
|
pxr::UsdStageRefPtr stage;
|
||||||
|
pxr::SdfPath materialPath;
|
||||||
|
pxr::SdfPath nodePath;
|
||||||
|
std::string output;
|
||||||
|
bool terminal = false;
|
||||||
|
};
|
||||||
|
std::unordered_map<std::string, MatEntry> m_matEntries;
|
||||||
|
MaterialPreviewRenderer m_matRenderer;
|
||||||
|
unsigned int m_readFbo = 0; // scratch GL FBOs for the render->cache blit
|
||||||
|
unsigned int m_drawFbo = 0;
|
||||||
|
|
||||||
|
void EnsureBlitFbos();
|
||||||
|
void BlitToEntry(MatEntry& e, uint32_t srcTex);
|
||||||
|
static void DeleteTex(ImTextureID& id);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace UsdLayerManager
|
||||||
+199
-1
@@ -1,8 +1,16 @@
|
|||||||
#include "PropertyPanel.h"
|
#include "PropertyPanel.h"
|
||||||
#include "../utils/Logger.h"
|
#include "../utils/Logger.h"
|
||||||
|
#include "../utils/FileDialog.h"
|
||||||
#include "../core/commands/TransformCommand.h"
|
#include "../core/commands/TransformCommand.h"
|
||||||
#include "../core/commands/AttributeSetCommand.h"
|
#include "../core/commands/AttributeSetCommand.h"
|
||||||
|
|
||||||
|
#include <pxr/usd/sdf/listOp.h>
|
||||||
|
#include <pxr/usd/sdf/reference.h>
|
||||||
|
#include <pxr/usd/sdf/payload.h>
|
||||||
|
#include <pxr/usd/sdf/schema.h>
|
||||||
|
#include <pxr/base/vt/dictionary.h>
|
||||||
|
#include <pxr/base/tf/stringUtils.h>
|
||||||
|
|
||||||
#include <pxr/usd/usd/prim.h>
|
#include <pxr/usd/usd/prim.h>
|
||||||
#include <pxr/usd/usd/attribute.h>
|
#include <pxr/usd/usd/attribute.h>
|
||||||
#include <pxr/usd/usd/relationship.h>
|
#include <pxr/usd/usd/relationship.h>
|
||||||
@@ -1116,6 +1124,186 @@ void PropertyPanel::RenderPrimHeader(const pxr::UsdPrim& prim) {
|
|||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// References & Payloads – one row per list-op entry; clicking the asset path
|
||||||
|
// browses for a replacement file (undoable whole-list-op swap).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
pxr::SdfReference WithAssetPath(const pxr::SdfReference& r, const std::string& assetPath) {
|
||||||
|
return pxr::SdfReference(assetPath, r.GetPrimPath(), r.GetLayerOffset(), r.GetCustomData());
|
||||||
|
}
|
||||||
|
pxr::SdfPayload WithAssetPath(const pxr::SdfPayload& p, const std::string& assetPath) {
|
||||||
|
return pxr::SdfPayload(assetPath, p.GetPrimPath(), p.GetLayerOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared row renderer for SdfReferenceListOp / SdfPayloadListOp: one row per
|
||||||
|
// item in each list-op bucket, labelled with its op. On replace, the whole
|
||||||
|
// modified list op is authored to the edit target as one undoable command.
|
||||||
|
template <typename ListOpT>
|
||||||
|
void RenderArcListRows(const pxr::UsdPrim& prim, CommandHistory* history,
|
||||||
|
IconManager* icons,
|
||||||
|
const char* tag, const char* displayKind,
|
||||||
|
const pxr::TfToken& fieldKey, const ListOpT& listOp, int& uid)
|
||||||
|
{
|
||||||
|
using ItemVector = typename ListOpT::ItemVector;
|
||||||
|
struct Bucket { const char* opName; ItemVector items; };
|
||||||
|
Bucket buckets[] = {
|
||||||
|
{"explicit", listOp.GetExplicitItems()},
|
||||||
|
{"add", listOp.GetAddedItems()},
|
||||||
|
{"prepend", listOp.GetPrependedItems()},
|
||||||
|
{"append", listOp.GetAppendedItems()},
|
||||||
|
{"delete", listOp.GetDeletedItems()},
|
||||||
|
};
|
||||||
|
const float rowH = ImGui::GetFrameHeight();
|
||||||
|
|
||||||
|
for (size_t b = 0; b < sizeof(buckets) / sizeof(buckets[0]); ++b) {
|
||||||
|
for (size_t i = 0; i < buckets[b].items.size(); ++i) {
|
||||||
|
const auto& item = buckets[b].items[i];
|
||||||
|
std::string display = item.GetAssetPath();
|
||||||
|
if (!item.GetPrimPath().IsEmpty())
|
||||||
|
display += " <" + item.GetPrimPath().GetString() + ">";
|
||||||
|
|
||||||
|
ImGui::TableNextRow(ImGuiTableRowFlags_None, rowH);
|
||||||
|
ImGui::PushID(uid++);
|
||||||
|
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
DrawPropertyMiniButton(tag, /*authored=*/true,
|
||||||
|
item.GetAssetPath().c_str(), displayKind);
|
||||||
|
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::TextDisabled("%s", buckets[b].opName);
|
||||||
|
|
||||||
|
ImGui::TableSetColumnIndex(2);
|
||||||
|
// Browse icon button first, then the path as plain text.
|
||||||
|
bool clicked;
|
||||||
|
if (icons) {
|
||||||
|
clicked = ImGui::ImageButton("##browseArc",
|
||||||
|
ImTextureRef(icons->Get(Icon::FolderOpen)),
|
||||||
|
ImVec2(14.f, 14.f));
|
||||||
|
} else {
|
||||||
|
clicked = ImGui::SmallButton("...");
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
ImGui::SetTooltip("Browse for a replacement file");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::TextUnformatted(display.empty() ? "(no asset)" : display.c_str());
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
ImGui::SetTooltip("%s (%s)\n%s", displayKind, buckets[b].opName,
|
||||||
|
item.GetAssetPath().c_str());
|
||||||
|
if (clicked) {
|
||||||
|
// Anything USD can compose is a valid arc target: native USD
|
||||||
|
// layers plus file formats with layer plugins (MaterialX,
|
||||||
|
// Alembic).
|
||||||
|
static const char* kArcFileFilter =
|
||||||
|
"Referenceable (*.usd;*.usda;*.usdc;*.usdz;*.mtlx;*.abc)\0"
|
||||||
|
"*.usd;*.usda;*.usdc;*.usdz;*.mtlx;*.abc\0"
|
||||||
|
"USD Files (*.usd;*.usda;*.usdc;*.usdz)\0*.usd;*.usda;*.usdc;*.usdz\0"
|
||||||
|
"MaterialX (*.mtlx)\0*.mtlx\0"
|
||||||
|
"Alembic (*.abc)\0*.abc\0"
|
||||||
|
"All Files (*.*)\0*.*\0";
|
||||||
|
std::string picked = FileDialog::OpenFile(kArcFileFilter, "Select Reference File");
|
||||||
|
if (!picked.empty() && picked != item.GetAssetPath()) {
|
||||||
|
ListOpT newOp = listOp;
|
||||||
|
ItemVector items = buckets[b].items;
|
||||||
|
items[i] = WithAssetPath(item, picked);
|
||||||
|
switch (b) {
|
||||||
|
case 0: newOp.SetExplicitItems(items); break;
|
||||||
|
case 1: newOp.SetAddedItems(items); break;
|
||||||
|
case 2: newOp.SetPrependedItems(items); break;
|
||||||
|
case 3: newOp.SetAppendedItems(items); break;
|
||||||
|
case 4: newOp.SetDeletedItems(items); break;
|
||||||
|
}
|
||||||
|
// Authors the whole (possibly multi-layer-composed) list
|
||||||
|
// op into the edit target — the pragmatic single-layer
|
||||||
|
// behaviour; undo restores the previous composed op.
|
||||||
|
if (history) {
|
||||||
|
pxr::UsdPrim p = prim;
|
||||||
|
ListOpT oldOp = listOp;
|
||||||
|
history->Push(std::make_unique<AttributeSetCommand>(
|
||||||
|
std::string("Replace ") + displayKind + " Path",
|
||||||
|
[p, fieldKey, newOp]() mutable { p.SetMetadata(fieldKey, newOp); },
|
||||||
|
[p, fieldKey, oldOp]() mutable { p.SetMetadata(fieldKey, oldOp); }));
|
||||||
|
} else {
|
||||||
|
pxr::UsdPrim(prim).SetMetadata(fieldKey, newOp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void PropertyPanel::RenderCompositionArcsSection(const pxr::UsdPrim& prim) {
|
||||||
|
if (!ImGui::CollapsingHeader("References & Payloads", ImGuiTreeNodeFlags_DefaultOpen))
|
||||||
|
return;
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags kFlags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg;
|
||||||
|
if (!ImGui::BeginTable("##compArcs", 3, kFlags)) return;
|
||||||
|
ImGui::TableSetupColumn("##dot", ImGuiTableColumnFlags_WidthFixed, 20.f);
|
||||||
|
ImGui::TableSetupColumn("##op", ImGuiTableColumnFlags_WidthFixed, 60.f);
|
||||||
|
ImGui::TableSetupColumn("##val", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
|
||||||
|
int uid = 0;
|
||||||
|
if (prim.HasAuthoredReferences()) {
|
||||||
|
pxr::SdfReferenceListOp refOp;
|
||||||
|
if (prim.GetMetadata(pxr::SdfFieldKeys->References, &refOp))
|
||||||
|
RenderArcListRows(prim, m_commandHistory, m_iconManager, "(R)", "Reference",
|
||||||
|
pxr::SdfFieldKeys->References, refOp, uid);
|
||||||
|
}
|
||||||
|
if (prim.HasAuthoredPayloads()) {
|
||||||
|
pxr::SdfPayloadListOp payloadOp;
|
||||||
|
if (prim.GetMetadata(pxr::SdfFieldKeys->Payload, &payloadOp))
|
||||||
|
RenderArcListRows(prim, m_commandHistory, m_iconManager, "(P)", "Payload",
|
||||||
|
pxr::SdfFieldKeys->Payload, payloadOp, uid);
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Asset Info – read-only view of the composed assetInfo dictionary
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
void PropertyPanel::RenderAssetInfoSection(const pxr::UsdPrim& prim) {
|
||||||
|
if (!ImGui::CollapsingHeader("Asset Info", ImGuiTreeNodeFlags_DefaultOpen))
|
||||||
|
return;
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags kFlags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg;
|
||||||
|
if (!ImGui::BeginTable("##assetInfo", 2, kFlags)) return;
|
||||||
|
ImGui::TableSetupColumn("##key", ImGuiTableColumnFlags_WidthFixed, 110.f);
|
||||||
|
ImGui::TableSetupColumn("##val", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
|
||||||
|
const pxr::VtDictionary info = prim.GetAssetInfo();
|
||||||
|
std::vector<std::string> keys;
|
||||||
|
keys.reserve(info.size());
|
||||||
|
for (const auto& entry : info)
|
||||||
|
keys.push_back(entry.first);
|
||||||
|
std::sort(keys.begin(), keys.end());
|
||||||
|
|
||||||
|
for (const std::string& key : keys) {
|
||||||
|
const pxr::VtValue& value = info.find(key)->second;
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
ImGui::TextDisabled("%s", key.c_str());
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
if (value.IsHolding<pxr::SdfAssetPath>()) {
|
||||||
|
const auto& assetPath = value.UncheckedGet<pxr::SdfAssetPath>();
|
||||||
|
ImGui::TextUnformatted(assetPath.GetAssetPath().c_str());
|
||||||
|
if (ImGui::IsItemHovered() && !assetPath.GetResolvedPath().empty())
|
||||||
|
ImGui::SetTooltip("resolved: %s", assetPath.GetResolvedPath().c_str());
|
||||||
|
} else {
|
||||||
|
const std::string text = pxr::TfStringify(value);
|
||||||
|
ImGui::TextUnformatted(text.c_str());
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
ImGui::SetTooltip("%s", text.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Variant Sets – no borders, no header (mirrors DrawVariantSetsCombos in usdtweak)
|
// Variant Sets – no borders, no header (mirrors DrawVariantSetsCombos in usdtweak)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -1605,9 +1793,19 @@ void PropertyPanel::Render() {
|
|||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
// Scrollable body – variant sets → transform → all properties
|
// Scrollable body – composition arcs → variant sets → transform → all properties
|
||||||
ImGui::BeginChild("##body");
|
ImGui::BeginChild("##body");
|
||||||
|
|
||||||
|
if (prim.HasAuthoredReferences() || prim.HasAuthoredPayloads()) {
|
||||||
|
RenderCompositionArcsSection(prim);
|
||||||
|
ImGui::Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prim.GetAssetInfo().empty()) {
|
||||||
|
RenderAssetInfoSection(prim);
|
||||||
|
ImGui::Separator();
|
||||||
|
}
|
||||||
|
|
||||||
if (prim.HasVariantSets()) {
|
if (prim.HasVariantSets()) {
|
||||||
RenderVariantSetsSection(prim);
|
RenderVariantSetsSection(prim);
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "../core/PropertyManager.h"
|
#include "../core/PropertyManager.h"
|
||||||
#include "../core/CommandHistory.h"
|
#include "../core/CommandHistory.h"
|
||||||
#include "../core/commands/TransformCommand.h"
|
#include "../core/commands/TransformCommand.h"
|
||||||
|
#include "IconManager.h"
|
||||||
#include <pxr/usd/usd/stage.h>
|
#include <pxr/usd/usd/stage.h>
|
||||||
#include <pxr/usd/usd/timeCode.h>
|
#include <pxr/usd/usd/timeCode.h>
|
||||||
#include <pxr/usd/sdf/path.h>
|
#include <pxr/usd/sdf/path.h>
|
||||||
@@ -25,6 +26,7 @@ public:
|
|||||||
|
|
||||||
void SetPropertyManager(PropertyManager* manager);
|
void SetPropertyManager(PropertyManager* manager);
|
||||||
void SetCommandHistory(CommandHistory* history) { m_commandHistory = history; }
|
void SetCommandHistory(CommandHistory* history) { m_commandHistory = history; }
|
||||||
|
void SetIconManager(IconManager* icons) { m_iconManager = icons; }
|
||||||
void SetStage(pxr::UsdStageRefPtr stage);
|
void SetStage(pxr::UsdStageRefPtr stage);
|
||||||
void SetSelectedPrimPath(const std::string& path);
|
void SetSelectedPrimPath(const std::string& path);
|
||||||
|
|
||||||
@@ -60,12 +62,20 @@ private:
|
|||||||
void RenderTransformSection();
|
void RenderTransformSection();
|
||||||
void RenderPropertiesTable(const pxr::UsdPrim& prim); ///< groups attrs by API schema
|
void RenderPropertiesTable(const pxr::UsdPrim& prim); ///< groups attrs by API schema
|
||||||
void RenderMaterialBindSection(const pxr::UsdPrim& prim); ///< material:binding info
|
void RenderMaterialBindSection(const pxr::UsdPrim& prim); ///< material:binding info
|
||||||
|
/// References/payloads authored on the prim, one row per list-op entry
|
||||||
|
/// (explicit/add/prepend/append/delete) with its asset path; clicking a
|
||||||
|
/// path browses for a replacement file (undoable).
|
||||||
|
void RenderCompositionArcsSection(const pxr::UsdPrim& prim);
|
||||||
|
/// Read-only view of the prim's composed assetInfo dictionary
|
||||||
|
/// (identifier, name, version, ...).
|
||||||
|
void RenderAssetInfoSection(const pxr::UsdPrim& prim);
|
||||||
void RenderAttrRelSubTable(const std::vector<pxr::UsdAttribute>& attrs,
|
void RenderAttrRelSubTable(const std::vector<pxr::UsdAttribute>& attrs,
|
||||||
const std::vector<pxr::UsdRelationship>& rels,
|
const std::vector<pxr::UsdRelationship>& rels,
|
||||||
const pxr::UsdEditTarget& editTarget); ///< inner 3-col table
|
const pxr::UsdEditTarget& editTarget); ///< inner 3-col table
|
||||||
|
|
||||||
PropertyManager* m_propertyManager = nullptr;
|
PropertyManager* m_propertyManager = nullptr;
|
||||||
CommandHistory* m_commandHistory = nullptr;
|
CommandHistory* m_commandHistory = nullptr;
|
||||||
|
IconManager* m_iconManager = nullptr;
|
||||||
pxr::UsdStageRefPtr m_stage;
|
pxr::UsdStageRefPtr m_stage;
|
||||||
std::string m_selectedPrimPath;
|
std::string m_selectedPrimPath;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user