From 46ca3ba24da9cf7dae88504c589e2559115b511e Mon Sep 17 00:00:00 2001 From: Jefferson Amstutz Date: Fri, 20 Feb 2026 21:02:38 -0600 Subject: [PATCH] only use camera objects in the scene --- .../demos/network/server/RenderServer.cpp | 20 +- tsd/apps/tools/tsdOffline.cpp | 136 +-- tsd/apps/tools/tsdRender.cpp | 21 +- tsd/src/tsd/core/AnariObjectCache.cpp | 13 + tsd/src/tsd/core/AnariObjectCache.hpp | 1 + tsd/src/tsd/core/Any.hpp | 8 + tsd/src/tsd/core/DataTree.hpp | 25 +- tsd/src/tsd/core/ObjectVersion.hpp | 27 + tsd/src/tsd/core/scene/Object.cpp | 26 +- tsd/src/tsd/core/scene/Object.hpp | 22 +- tsd/src/tsd/core/scene/ObjectUsePtr.hpp | 8 + tsd/src/tsd/core/scene/Scene.cpp | 60 +- tsd/src/tsd/core/scene/Scene.hpp | 10 +- tsd/src/tsd/core/scene/objects/Camera.cpp | 6 +- tsd/src/tsd/core/scene/objects/Camera.hpp | 3 +- tsd/src/tsd/core/scene/objects/Renderer.cpp | 26 +- tsd/src/tsd/core/scene/objects/Renderer.hpp | 2 + tsd/src/tsd/rendering/CMakeLists.txt | 1 + tsd/src/tsd/rendering/index/RenderIndex.cpp | 33 + tsd/src/tsd/rendering/index/RenderIndex.hpp | 4 + .../pipeline/passes/AnariAxesRenderPass.cpp | 1 + .../pipeline/passes/AnariSceneRenderPass.cpp | 31 +- .../pipeline/passes/AnariSceneRenderPass.h | 3 + tsd/src/tsd/rendering/view/Manipulator.hpp | 27 +- .../tsd/rendering/view/ManipulatorToTSD.cpp | 44 + .../tsd/rendering/view/ManipulatorToTSD.hpp | 18 + tsd/src/tsd/ui/imgui/Application.cpp | 104 +- tsd/src/tsd/ui/imgui/Application.h | 11 +- tsd/src/tsd/ui/imgui/tsd_ui_imgui.cpp | 7 +- tsd/src/tsd/ui/imgui/windows/CameraPoses.cpp | 5 +- tsd/src/tsd/ui/imgui/windows/CameraPoses.h | 2 +- tsd/src/tsd/ui/imgui/windows/LayerTree.cpp | 14 +- tsd/src/tsd/ui/imgui/windows/Viewport.cpp | 1002 ++++++----------- tsd/src/tsd/ui/imgui/windows/Viewport.h | 37 +- 34 files changed, 848 insertions(+), 910 deletions(-) create mode 100644 tsd/src/tsd/core/ObjectVersion.hpp create mode 100644 tsd/src/tsd/rendering/view/ManipulatorToTSD.cpp create mode 100644 tsd/src/tsd/rendering/view/ManipulatorToTSD.hpp diff --git a/tsd/apps/interactive/demos/network/server/RenderServer.cpp b/tsd/apps/interactive/demos/network/server/RenderServer.cpp index 6b2777a9e..655648246 100644 --- a/tsd/apps/interactive/demos/network/server/RenderServer.cpp +++ b/tsd/apps/interactive/demos/network/server/RenderServer.cpp @@ -133,28 +133,10 @@ void RenderServer::setup_ANARIDevice() void RenderServer::setup_Manipulator() { tsd::core::logStatus("[Server] Setting up manipulator..."); - - tsd::math::float3 bounds[2] = {{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; - auto &scene = m_core.tsd.scene; - if (!anariGetProperty(m_device, - m_renderIndex->world(), - "bounds", - ANARI_FLOAT32_BOX3, - &bounds[0], - sizeof(bounds), - ANARI_WAIT)) { - tsd::core::logWarning("[Server] anari::World returned no bounds!"); - } - - auto center = 0.5f * (bounds[0] + bounds[1]); - auto diag = bounds[1] - bounds[0]; - - m_manipulator.setConfig(center, 1.25f * linalg::length(diag), {0.f, 20.f}); - + m_manipulator.setConfig(m_renderIndex->computeDefaultView()); auto azel = m_manipulator.azel(); auto dist = m_manipulator.distance(); auto lookat = m_manipulator.at(); - m_session.view.azeldist = {azel.x, azel.y, dist}; m_session.view.lookat = lookat; } diff --git a/tsd/apps/tools/tsdOffline.cpp b/tsd/apps/tools/tsdOffline.cpp index 9c0f5fcc9..af22b9e5b 100644 --- a/tsd/apps/tools/tsdOffline.cpp +++ b/tsd/apps/tools/tsdOffline.cpp @@ -1,22 +1,22 @@ -// Copyright 2025 NVIDIA Corporation +// Copyright 2026 NVIDIA Corporation // SPDX-License-Identifier: Apache-2.0 +#include +#include #include #include #include -#include -#include -#include -#include #include #include +#include +#include #include "stb_image_write.h" #include #include +#include #include #include -#include static std::unique_ptr g_renderIndex; static std::unique_ptr g_renderPipeline; @@ -30,7 +30,6 @@ static anari::Library g_library{nullptr}; static anari::Device g_device{nullptr}; static anari::Camera g_camera{nullptr}; - struct Config { tsd::math::float3 cameraPos = {0.f, 0.f, 3.f}; @@ -47,7 +46,8 @@ struct Config tsd::math::float3 ambientColor = {1.f, 1.f, 1.f}; bool hasBackground = false; - struct DirectionalLight { + struct DirectionalLight + { tsd::math::float3 direction; tsd::math::float3 color; float irradiance; @@ -63,24 +63,34 @@ static void printUsage(const char *programName) std::cout << "Rendering Options:\n"; std::cout << " -w, --width Frame width (default: 1024)\n"; std::cout << " -h, --height Frame height (default: 768)\n"; - std::cout << " -s, --samples Samples per pixel (default: 128)\n"; - std::cout << " -o, --output Output filename (default: tsdOffline.png)\n"; - std::cout << " --lib ANARI library name (default: TSD_ANARI_LIBRARIES[0], environment, or visrtx)\n"; - std::cout << " --renderer Renderer name (default: default)\n"; + std::cout + << " -s, --samples Samples per pixel (default: 128)\n"; + std::cout + << " -o, --output Output filename (default: tsdOffline.png)\n"; + std::cout + << " --lib ANARI library name (default: TSD_ANARI_LIBRARIES[0], environment, or visrtx)\n"; + std::cout + << " --renderer Renderer name (default: default)\n"; std::cout << " --campos Camera position (3 floats)\n"; std::cout << " --lookpos Camera look-at point (3 floats)\n"; std::cout << " --upvec Camera up vector (3 floats)\n"; - std::cout << " --fovy Field of view Y in degrees (default: 40)\n"; + std::cout + << " --fovy Field of view Y in degrees (default: 40)\n"; std::cout << " --aperture Aperture radius (default: 0)\n"; std::cout << " --focus Focus distance (default: 1)\n"; std::cout << "\n"; std::cout << "Lighting and Background:\n"; - std::cout << " --bg-color Background color (4 floats, default: 0.05 0.05 0.05 1.0)\n"; - std::cout << " --no-bg Set background to black (0 0 0 0)\n"; - std::cout << " --ambient Ambient radiance (default: 0.25)\n"; - std::cout << " --ambient-color Ambient color (3 floats, default: 1.0 1.0 1.0)\n"; + std::cout + << " --bg-color Background color (4 floats, default: 0.05 0.05 0.05 1.0)\n"; + std::cout + << " --no-bg Set background to black (0 0 0 0)\n"; + std::cout + << " --ambient Ambient radiance (default: 0.25)\n"; + std::cout + << " --ambient-color Ambient color (3 floats, default: 1.0 1.0 1.0)\n"; std::cout << " --dir-light \n"; - std::cout << " Add directional light (direction + color + intensity)\n"; + std::cout + << " Add directional light (direction + color + intensity)\n"; std::cout << " --help Show this help message\n"; std::cout << "\n"; std::cout << "Importer Options:\n"; @@ -88,12 +98,15 @@ static void printUsage(const char *programName) std::cout << " -gltf Import GLTF/GLB files\n"; std::cout << " -obj Import Wavefront OBJ files\n"; std::cout << " -ply Import PLY files\n"; - std::cout << " -volume Import volume data (RAW, NVDB, VTI, etc.)\n"; + std::cout + << " -volume Import volume data (RAW, NVDB, VTI, etc.)\n"; std::cout << " -hdri Set HDRI environment map\n"; std::cout << " -silo Import Silo files\n"; std::cout << " -usd Import USD files\n"; - std::cout << " -l, --layer Specify layer name for following imports\n"; - std::cout << " -assimp Import via ASSIMP (supports many formats)\n"; + std::cout + << " -l, --layer Specify layer name for following imports\n"; + std::cout + << " -assimp Import via ASSIMP (supports many formats)\n"; std::cout << " -axyz Import AXYZ point cloud files\n"; std::cout << " -e57xyz Import E57 point cloud files\n"; std::cout << " -pdb Import PDB molecule files\n"; @@ -106,16 +119,22 @@ static void printUsage(const char *programName) std::cout << " " << programName << " -gltf model.glb -o output.png\n"; std::cout << "\n"; std::cout << " # Render volume data at custom resolution\n"; - std::cout << " " << programName << " -volume density.raw -w 2048 -h 2048 -s 256 -o volume.png\n"; + std::cout << " " << programName + << " -volume density.raw -w 2048 -h 2048 -s 256 -o volume.png\n"; std::cout << "\n"; std::cout << " # Combine multiple formats\n"; - std::cout << " " << programName << " -obj mesh.obj -hdri env.hdr -w 1920 -h 1080 -o render.png\n"; + std::cout << " " << programName + << " -obj mesh.obj -hdri env.hdr -w 1920 -h 1080 -o render.png\n"; std::cout << "\n"; std::cout << " # With custom camera\n"; - std::cout << " " << programName << " -gltf scene.glb --campos 10 10 10 --lookpos 0 0 0 -o out.png\n"; + std::cout + << " " << programName + << " -gltf scene.glb --campos 10 10 10 --lookpos 0 0 0 -o out.png\n"; std::cout << "\n"; - std::cout << "If no importer flags are specified, a default empty scene will be created.\n"; - std::cout << "If camera is not specified, it will be computed from scene bounds.\n"; + std::cout + << "If no importer flags are specified, a default empty scene will be created.\n"; + std::cout + << "If camera is not specified, it will be computed from scene bounds.\n"; } static tsd::math::float3 parseFloat3(const char **argv, int &i) @@ -129,7 +148,8 @@ static tsd::math::float3 parseFloat3(const char **argv, int &i) // Parse rendering-specific options and build a new argv with importer options // Returns the new argc for importer parsing, or -1 on error -static int parseRenderingOptions(int argc, const char *argv[], std::vector &importerArgv) +static int parseRenderingOptions( + int argc, const char *argv[], std::vector &importerArgv) { // First element is always the program name importerArgv.push_back(argv[0]); @@ -243,7 +263,8 @@ static int parseRenderingOptions(int argc, const char *argv[], std::vector= argc) { - std::cerr << "Error: --dir-light requires 7 arguments (dx dy dz r g b intensity)\n"; + std::cerr + << "Error: --dir-light requires 7 arguments (dx dy dz r g b intensity)\n"; return -1; } Config::DirectionalLight light; @@ -300,14 +321,13 @@ static void initTSDRenderIndex() fflush(stdout); g_timer.start(); - g_renderIndex = - std::make_unique(g_core->tsd.scene, g_deviceName, g_device); + g_renderIndex = std::make_unique( + g_core->tsd.scene, g_deviceName, g_device); g_timer.end(); printf("done (%.2f ms)\n", g_timer.milliseconds()); } - static void populateTSDScene() { printf("Importing scene data..."); @@ -343,7 +363,8 @@ static void setupLights() // Add user-specified directional lights for (const auto &lightConfig : g_config.directionalLights) { - auto light = g_core->tsd.scene.createObject("directional"); + auto light = + g_core->tsd.scene.createObject("directional"); light->setParameter("direction", lightConfig.direction); light->setParameter("color", lightConfig.color); light->setParameter("irradiance", lightConfig.irradiance); @@ -379,36 +400,17 @@ static void setupCameraManipulator() if (g_config.autoCamera) { printf("from world bounds..."); fflush(stdout); - - tsd::math::float3 bounds[2] = {{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; - if (!anariGetProperty(g_device, - g_renderIndex->world(), - "bounds", - ANARI_FLOAT32_BOX3, - &bounds[0], - sizeof(bounds), - ANARI_WAIT)) { - printf("(no bounds, using defaults)..."); - fflush(stdout); - } - - auto center = 0.5f * (bounds[0] + bounds[1]); - auto diag = bounds[1] - bounds[0]; - float dist = 1.25f * tsd::math::length(diag); - - // Match tsdViewer resetView(true): azimuth=0, elevation=20, distance=1.25*|diag| - pose.lookat = center; - pose.fixedDist = dist; - pose.azeldist = {0.f, 20.f, dist}; - pose.upAxis = static_cast(tsd::rendering::UpAxis::POS_Y); + pose = g_renderIndex->computeDefaultView(); } else { printf("from command line..."); fflush(stdout); pose.lookat = g_config.cameraLookAt; - pose.fixedDist = tsd::math::length(g_config.cameraPos - g_config.cameraLookAt); + pose.fixedDist = + tsd::math::length(g_config.cameraPos - g_config.cameraLookAt); - auto dir = tsd::math::normalize(g_config.cameraPos - g_config.cameraLookAt); + auto dir = + tsd::math::normalize(g_config.cameraPos - g_config.cameraLookAt); float azimuth = std::atan2(dir.x, dir.z) * 180.f / M_PI; float elevation = std::asin(dir.y) * 180.f / M_PI; pose.azeldist = {azimuth, elevation, pose.fixedDist}; @@ -438,7 +440,8 @@ static void setupRenderPipeline() g_camera = anari::newObject(g_device, "perspective"); anari::setParameter( g_device, g_camera, "aspect", frameWidth / float(frameHeight)); - anari::setParameter(g_device, g_camera, "fovy", anari::radians(g_config.fovy)); + anari::setParameter( + g_device, g_camera, "fovy", anari::radians(g_config.fovy)); anari::setParameter(g_device, g_camera, "apertureRadius", @@ -449,7 +452,8 @@ static void setupRenderPipeline() g_core->offline.camera.focusDistance); anari::commitParameters(g_device, g_camera); - auto r = anari::newObject(g_device, g_config.rendererName.c_str()); + auto r = anari::newObject( + g_device, g_config.rendererName.c_str()); // Set background and ambient lighting anari::setParameter(g_device, r, "background", g_config.background); @@ -538,14 +542,16 @@ int main(int argc, const char *argv[]) // Initialize Core first so it can be used for parsing g_core = std::make_unique(); - // Default library: TSD_ANARI_LIBRARIES[0], then "environment" (reads ANARI_LIBRARY), then "visrtx" + // Default library: TSD_ANARI_LIBRARIES[0], then "environment" (reads + // ANARI_LIBRARY), then "visrtx" std::string defaultLibrary = "visrtx"; if (getenv("ANARI_LIBRARY")) defaultLibrary = "environment"; if (const char *tsdLibs = getenv("TSD_ANARI_LIBRARIES")) { std::string envStr = tsdLibs; auto comma = envStr.find(','); - std::string first = (comma != std::string::npos) ? envStr.substr(0, comma) : envStr; + std::string first = + (comma != std::string::npos) ? envStr.substr(0, comma) : envStr; first.erase(0, first.find_first_not_of(" \t")); first.erase(first.find_last_not_of(" \t") + 1); if (!first.empty()) @@ -556,7 +562,7 @@ int main(int argc, const char *argv[]) // Two-pass command line parsing: // 1. Extract rendering options (width, height, samples, etc.) // 2. Pass remaining args (importer flags and filenames) to Core - std::vector importerArgv; + std::vector importerArgv; int importerArgc = parseRenderingOptions(argc, argv, importerArgv); if (importerArgc < 0) { return 1; @@ -567,7 +573,9 @@ int main(int argc, const char *argv[]) printf("tsdOffline - Headless TSD Renderer\n"); printf("===================================\n"); - printf("Resolution: %ux%u\n", g_core->offline.frame.width, g_core->offline.frame.height); + printf("Resolution: %ux%u\n", + g_core->offline.frame.width, + g_core->offline.frame.height); printf("Samples: %u\n", g_core->offline.frame.samples); printf("Library: %s\n", g_core->offline.renderer.libraryName.c_str()); printf("Renderer: %s\n", g_config.rendererName.c_str()); @@ -576,8 +584,8 @@ int main(int argc, const char *argv[]) // Core already initializes its scene, no separate initialization needed loadANARIDevice(); - populateTSDScene(); // Import data into scene FIRST - setupLights(); // Then add lights + populateTSDScene(); // Import data into scene FIRST + setupLights(); // Then add lights initTSDRenderIndex(); // THEN create render index with populated scene populateRenderIndex(); setupCameraManipulator(); diff --git a/tsd/apps/tools/tsdRender.cpp b/tsd/apps/tools/tsdRender.cpp index f8411f5a3..63d0a9231 100644 --- a/tsd/apps/tools/tsdRender.cpp +++ b/tsd/apps/tools/tsdRender.cpp @@ -190,26 +190,7 @@ static void setupCameraManipulator() } else { printf("from world bounds..."); fflush(stdout); - - tsd::math::float3 bounds[2] = {{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; - anariGetProperty(g_device, - g_renderIndex->world(), - "bounds", - ANARI_FLOAT32_BOX3, - &bounds[0], - sizeof(bounds), - ANARI_WAIT); - - auto center = 0.5f * (bounds[0] + bounds[1]); - auto diag = bounds[1] - bounds[0]; - - tsd::rendering::CameraPose pose; - pose.fixedDist = 2.f * tsd::math::length(diag); - pose.lookat = center; - pose.azeldist = {0.f, 20.f, pose.fixedDist}; - pose.upAxis = static_cast(tsd::rendering::UpAxis::POS_Y); - - g_cameraPoses.push_back(std::move(pose)); + g_cameraPoses.push_back(g_renderIndex->computeDefaultView()); } g_timer.end(); diff --git a/tsd/src/tsd/core/AnariObjectCache.cpp b/tsd/src/tsd/core/AnariObjectCache.cpp index 03664e93c..797e4df06 100644 --- a/tsd/src/tsd/core/AnariObjectCache.cpp +++ b/tsd/src/tsd/core/AnariObjectCache.cpp @@ -103,6 +103,9 @@ void AnariObjectCache::insertEmptyHandle(anari::DataType type) case ANARI_RENDERER: renderer.insert(nullptr); break; + case ANARI_CAMERA: + camera.insert(nullptr); + break; case ANARI_ARRAY: case ANARI_ARRAY1D: case ANARI_ARRAY2D: @@ -156,6 +159,9 @@ void AnariObjectCache::removeHandle(anari::DataType type, size_t index) case ANARI_RENDERER: renderer.erase(index); break; + case ANARI_CAMERA: + camera.erase(index); + break; case ANARI_ARRAY: case ANARI_ARRAY1D: case ANARI_ARRAY2D: @@ -190,6 +196,7 @@ void AnariObjectCache::clear() releaseAllHandles(sampler); releaseAllHandles(light); releaseAllHandles(renderer); + releaseAllHandles(camera); releaseAllHandles(array); // this needs to be last! } @@ -246,6 +253,9 @@ void AnariObjectCache::replaceHandle( case ANARI_RENDERER: renderer[i] = (anari::Renderer)o; break; + case ANARI_CAMERA: + camera[i] = (anari::Camera)o; + break; case ANARI_ARRAY: case ANARI_ARRAY1D: case ANARI_ARRAY2D: @@ -286,6 +296,9 @@ anari::Object AnariObjectCache::readHandle(anari::DataType type, size_t i) const case ANARI_RENDERER: obj = renderer.at(i).value_or(nullptr); break; + case ANARI_CAMERA: + obj = camera.at(i).value_or(nullptr); + break; case ANARI_ARRAY: case ANARI_ARRAY1D: case ANARI_ARRAY2D: diff --git a/tsd/src/tsd/core/AnariObjectCache.hpp b/tsd/src/tsd/core/AnariObjectCache.hpp index 55d55ff5c..4fd2afca7 100644 --- a/tsd/src/tsd/core/AnariObjectCache.hpp +++ b/tsd/src/tsd/core/AnariObjectCache.hpp @@ -39,6 +39,7 @@ struct AnariObjectCache ObjectPool light; ObjectPool array; ObjectPool renderer; + ObjectPool camera; anari::Device device{nullptr}; tsd::core::Token deviceName; diff --git a/tsd/src/tsd/core/Any.hpp b/tsd/src/tsd/core/Any.hpp index 8dd0018fb..471ad46cf 100644 --- a/tsd/src/tsd/core/Any.hpp +++ b/tsd/src/tsd/core/Any.hpp @@ -48,6 +48,8 @@ struct Any T get() const; template T getAs(anari::DataType expectedHeldAnariType = ANARI_UNKNOWN) const; + template + T getValueOr(const T &alt) const; size_t getAsObjectIndex() const; // when storing object indices only @@ -226,6 +228,12 @@ inline T Any::getAs(anari::DataType expectedType) const return storageAs(); } +template +inline T Any::getValueOr(const T &alt) const +{ + return is() ? get() : alt; +} + inline const void *Any::data() const { return type() == ANARI_STRING ? (const void *)m_string.data() diff --git a/tsd/src/tsd/core/DataTree.hpp b/tsd/src/tsd/core/DataTree.hpp index 614906143..963025bd9 100644 --- a/tsd/src/tsd/core/DataTree.hpp +++ b/tsd/src/tsd/core/DataTree.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -92,14 +93,14 @@ struct DataNode size_t numChildren() const; - DataNode *child(const std::string &childName); - const DataNode *child(const std::string &childName) const; + DataNode *child(std::string_view childName); + const DataNode *child(const std::string_view childName) const; DataNode *child(size_t childIdx); const DataNode *child(size_t childIdx) const; - DataNode &operator[](const std::string &childName); + DataNode &operator[](std::string_view childName); - DataNode &append(const std::string &newChildName = ""); - void remove(const std::string &name); + DataNode &append(std::string_view newChildName = ""); + void remove(std::string_view name); void remove(DataNode &childNode); // Algorithms // @@ -296,7 +297,7 @@ inline std::string DataNode::getValueAs() const template inline T DataNode::getValueOr(const T &alt) const { - return getValue().is() ? getValueAs() : alt; + return getValue().getValueOr(alt); } template <> @@ -441,21 +442,21 @@ inline size_t DataNode::numChildren() const return num; } -inline DataNode *DataNode::child(const std::string &childName) +inline DataNode *DataNode::child(std::string_view childName) { auto n = find_first_child( self(), [&](DataNode::Ptr &cn) { return cn->name() == childName; }); return n ? (**n).get() : nullptr; } -inline const DataNode *DataNode::child(const std::string &childName) const +inline const DataNode *DataNode::child(std::string_view childName) const { auto n = find_first_child( self(), [&](DataNode::Ptr &cn) { return cn->name() == childName; }); return n ? (**n).get() : nullptr; } -inline DataNode &DataNode::operator[](const std::string &childName) +inline DataNode &DataNode::operator[](std::string_view childName) { auto *n = child(childName); return n ? *n : append(childName); @@ -477,11 +478,11 @@ inline const DataNode *DataNode::child(size_t childIdx) const return n ? (**n).get() : nullptr; } -inline DataNode &DataNode::append(const std::string &newChildName) +inline DataNode &DataNode::append(std::string_view newChildName) { clearValue(); - std::string name = newChildName; + std::string name(newChildName); if (name.empty()) { #if 1 static int counter = 0; @@ -500,7 +501,7 @@ inline DataNode &DataNode::append(const std::string &newChildName) } } -inline void DataNode::remove(const std::string &name) +inline void DataNode::remove(std::string_view name) { if (auto *c = child(name); c != nullptr) self()->container()->erase(c->self()); diff --git a/tsd/src/tsd/core/ObjectVersion.hpp b/tsd/src/tsd/core/ObjectVersion.hpp new file mode 100644 index 000000000..2b9846455 --- /dev/null +++ b/tsd/src/tsd/core/ObjectVersion.hpp @@ -0,0 +1,27 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// std +#include + +namespace tsd::core { + +using ObjectVersion = uint64_t; + +bool versionChanged(ObjectVersion &lastChecked, const ObjectVersion ¤t); + +// Inlined definitions //////////////////////////////////////////////////////// + +inline bool versionChanged( + ObjectVersion &lastChecked, const ObjectVersion ¤t) +{ + if (lastChecked < current) { + lastChecked = current; + return true; + } + return false; +} + +} // namespace tsd::core diff --git a/tsd/src/tsd/core/scene/Object.cpp b/tsd/src/tsd/core/scene/Object.cpp index e2116b12d..75ffd80f6 100644 --- a/tsd/src/tsd/core/scene/Object.cpp +++ b/tsd/src/tsd/core/scene/Object.cpp @@ -204,7 +204,7 @@ void Object::setName(const std::string &n) m_name = n; } -Any Object::getMetadataValue(const std::string &name) const +Any Object::getMetadataValue(std::string_view name) const { if (!m_metadata) return {}; @@ -214,7 +214,7 @@ Any Object::getMetadataValue(const std::string &name) const return {}; } -void Object::getMetadataArray(const std::string &name, +void Object::getMetadataArray(std::string_view name, anari::DataType *type, const void **ptr, size_t *size) const @@ -228,26 +228,29 @@ void Object::getMetadataArray(const std::string &name, c->getValueAsArray(type, ptr, size); } -void Object::setMetadataValue(const std::string &name, Any v) +void Object::setMetadataValue(std::string_view name, Any v) { initMetadata(); m_metadata->root().append(name) = v; + m_versions.metadata++; } -void Object::setMetadataArray(const std::string &name, +void Object::setMetadataArray(std::string_view name, anari::DataType type, const void *v, size_t numElements) { initMetadata(); m_metadata->root().append(name).setValueAsArray(type, v, numElements); + m_versions.metadata++; } -void Object::removeMetadata(const std::string &name) +void Object::removeMetadata(std::string_view name) { if (!m_metadata) return; m_metadata->root().remove(name); + m_versions.metadata++; } size_t Object::numMetadata() const @@ -270,6 +273,7 @@ const char *Object::getMetadataName(size_t i) const Parameter &Object::addParameter(Token name) { m_parameters.set(name, Parameter(this, name.c_str())); + m_versions.parameter++; return *parameter(name); } @@ -350,6 +354,16 @@ const char *Object::parameterNameAt(size_t i) const return m_parameters.at_index(i).first.c_str(); } +ObjectVersion Object::lastParameterChange() const +{ + return m_versions.parameter; +} + +ObjectVersion Object::lastMetadataChange() const +{ + return m_versions.metadata; +} + anari::Object Object::makeANARIObject(anari::Device) const { return {}; @@ -412,11 +426,13 @@ void Object::parameterChanged(const Parameter *p, const Any &oldValue) incObjectUseCountParameter(p); if (m_updateDelegate) m_updateDelegate->signalParameterUpdated(this, p); + m_versions.parameter++; } void Object::removeParameter(const Parameter *p) { removeParameter(p->name()); + m_versions.parameter++; } BaseUpdateDelegate *Object::updateDelegate() const diff --git a/tsd/src/tsd/core/scene/Object.hpp b/tsd/src/tsd/core/scene/Object.hpp index 24512ea37..6c4a90578 100644 --- a/tsd/src/tsd/core/scene/Object.hpp +++ b/tsd/src/tsd/core/scene/Object.hpp @@ -6,6 +6,7 @@ #include "tsd/core/DataTree.hpp" #include "tsd/core/FlatMap.hpp" #include "tsd/core/ObjectPool.hpp" +#include "tsd/core/ObjectVersion.hpp" #include "tsd/core/Parameter.hpp" #include "tsd/core/TSDMath.hpp" #include "tsd/core/Token.hpp" @@ -14,6 +15,7 @@ #include #include #include +#include #include namespace tsd::core { @@ -85,18 +87,18 @@ struct Object : public ParameterObserver void setName(const char *n); void setName(const std::string &n); - Any getMetadataValue(const std::string &name) const; - void getMetadataArray(const std::string &name, + Any getMetadataValue(std::string_view name) const; + void getMetadataArray(std::string_view name, anari::DataType *type, const void **ptr, size_t *size) const; - void setMetadataValue(const std::string &name, Any v); - void setMetadataArray(const std::string &name, + void setMetadataValue(std::string_view name, Any v); + void setMetadataArray(std::string_view name, anari::DataType type, const void *v, size_t numElements); - void removeMetadata(const std::string &name); + void removeMetadata(std::string_view name); size_t numMetadata() const; const char *getMetadataName(size_t i) const; @@ -128,6 +130,11 @@ struct Object : public ParameterObserver Parameter ¶meterAt(size_t i); const char *parameterNameAt(size_t i) const; + //// Change tracking //// + + ObjectVersion lastParameterChange() const; + ObjectVersion lastMetadataChange() const; + //// ANARI Objects ///// virtual anari::Object makeANARIObject(anari::Device d) const; @@ -176,6 +183,11 @@ struct Object : public ParameterObserver size_t parameter{0}; size_t layer{0}; } m_useCounts; + struct Versions + { + ObjectVersion parameter{0}; + ObjectVersion metadata{0}; + } m_versions; }; void print(const Object &obj, std::ostream &out = std::cout); diff --git a/tsd/src/tsd/core/scene/ObjectUsePtr.hpp b/tsd/src/tsd/core/scene/ObjectUsePtr.hpp index 61baec397..c9dcc8c89 100644 --- a/tsd/src/tsd/core/scene/ObjectUsePtr.hpp +++ b/tsd/src/tsd/core/scene/ObjectUsePtr.hpp @@ -35,6 +35,8 @@ struct ObjectUsePtr T *operator->(); T &operator*(); + ObjectPoolRef ref() const; + operator bool() const; private: @@ -179,6 +181,12 @@ inline T &ObjectUsePtr::operator*() return *get(); } +template +inline ObjectPoolRef ObjectUsePtr::ref() const +{ + return m_object; +} + template inline ObjectUsePtr::operator bool() const { diff --git a/tsd/src/tsd/core/scene/Scene.cpp b/tsd/src/tsd/core/scene/Scene.cpp index 4a0be11ec..4d629ef0f 100644 --- a/tsd/src/tsd/core/scene/Scene.cpp +++ b/tsd/src/tsd/core/scene/Scene.cpp @@ -29,11 +29,15 @@ std::string objectDBInfo(const ObjectDatabase &db) Scene::Scene() { - createObject(tokens::material::matte)->setName("default_material"); + defaultMaterial(); + defaultCamera(); } Scene::~Scene() { + m_defaultObjects.material.reset(); + m_defaultObjects.camera.reset(); + m_updateDelegate = nullptr; m_layers.clear(); m_animations.objects.clear(); @@ -69,9 +73,30 @@ Scene::~Scene() reportObjectUsages(m_db.renderer); } -MaterialRef Scene::defaultMaterial() const +MaterialRef Scene::defaultMaterial() +{ + if (!m_defaultObjects.material) { + if (numberOfObjects(ANARI_MATERIAL) == 0) { + m_defaultObjects.material = + createObject(tokens::material::matte); + m_defaultObjects.material->setName("default"); + } else + m_defaultObjects.material = getObject(0); + } + return m_defaultObjects.material.ref(); +} + +CameraRef Scene::defaultCamera() { - return getObject(0); + if (!m_defaultObjects.camera) { + if (numberOfObjects(ANARI_CAMERA) == 0) { + m_defaultObjects.camera = + createObject(tokens::camera::perspective); + m_defaultObjects.camera->setName("default"); + } else + m_defaultObjects.camera = getObject(0); + } + return m_defaultObjects.camera.ref(); } Layer *Scene::defaultLayer() @@ -232,37 +257,37 @@ size_t Scene::numberOfObjects(anari::DataType type) const switch (type) { case ANARI_SURFACE: - numObjects = m_db.surface.capacity(); + numObjects = m_db.surface.size(); break; case ANARI_GEOMETRY: - numObjects = m_db.geometry.capacity(); + numObjects = m_db.geometry.size(); break; case ANARI_MATERIAL: - numObjects = m_db.material.capacity(); + numObjects = m_db.material.size(); break; case ANARI_SAMPLER: - numObjects = m_db.sampler.capacity(); + numObjects = m_db.sampler.size(); break; case ANARI_VOLUME: - numObjects = m_db.volume.capacity(); + numObjects = m_db.volume.size(); break; case ANARI_SPATIAL_FIELD: - numObjects = m_db.field.capacity(); + numObjects = m_db.field.size(); break; case ANARI_LIGHT: - numObjects = m_db.light.capacity(); + numObjects = m_db.light.size(); break; case ANARI_CAMERA: - numObjects = m_db.camera.capacity(); + numObjects = m_db.camera.size(); break; case ANARI_RENDERER: - numObjects = m_db.renderer.capacity(); + numObjects = m_db.renderer.size(); break; case ANARI_ARRAY: case ANARI_ARRAY1D: case ANARI_ARRAY2D: case ANARI_ARRAY3D: - numObjects = m_db.array.capacity(); + numObjects = m_db.array.size(); break; default: break; // no-op @@ -334,6 +359,9 @@ void Scene::removeAllObjects() if (m_updateDelegate) m_updateDelegate->signalRemoveAllObjects(); + m_defaultObjects.material.reset(); + m_defaultObjects.camera.reset(); + removeAllLayers(); m_db.array.clear(); @@ -714,8 +742,10 @@ void Scene::removeUnusedObjects(bool includeRenderers) { tsd::core::logStatus("Removing unused context objects"); - // Always keep around the default material // - ObjectUsePtr defaultMat = getObject(0).data(); + // Always keep around the default material + default camera // + + ObjectUsePtr defaultMat = defaultMaterial(); + ObjectUsePtr defaultCam = defaultCamera(); auto removeUnused = [&](auto &array) { foreach_item_ref(array, [&](auto ref) { diff --git a/tsd/src/tsd/core/scene/Scene.hpp b/tsd/src/tsd/core/scene/Scene.hpp index a470af37d..ef3898c99 100644 --- a/tsd/src/tsd/core/scene/Scene.hpp +++ b/tsd/src/tsd/core/scene/Scene.hpp @@ -77,7 +77,8 @@ struct Scene Scene(Scene &&) = delete; Scene &operator=(Scene &&) = delete; - MaterialRef defaultMaterial() const; + MaterialRef defaultMaterial(); + CameraRef defaultCamera(); Layer *defaultLayer(); int mpiRank() const; @@ -232,6 +233,13 @@ struct Scene Array::MemoryKind kind); ObjectDatabase m_db; + + struct DefaultObjects + { + ObjectUsePtr material; + ObjectUsePtr camera; + } m_defaultObjects; + BaseUpdateDelegate *m_updateDelegate{nullptr}; LayerMap m_layers; size_t m_numActiveLayers{0}; diff --git a/tsd/src/tsd/core/scene/objects/Camera.cpp b/tsd/src/tsd/core/scene/objects/Camera.cpp index e1274fef3..dc52f3804 100644 --- a/tsd/src/tsd/core/scene/objects/Camera.cpp +++ b/tsd/src/tsd/core/scene/objects/Camera.cpp @@ -78,11 +78,11 @@ Camera::Camera(Token subtype) : Object(ANARI_CAMERA, subtype) if (subtype == tokens::camera::perspective) { // KHR_CAMERA_PERSPECTIVE extension addParameter("fovy") - .setValue(float(M_PI) / 3.0f) + .setValue(math::radians(40.f)) .setDescription( "the field of view (angle in radians) of the frame's height") - .setMin(0.001f) - .setMax(float(M_PI) - 0.001f); + .setMin(math::radians(0.1f)) + .setMax(math::radians(179.9f)); addParameter("near").setDescription("near clip plane distance").setMin(0.f); addParameter("far").setDescription("far clip plane distance").setMin(0.f); diff --git a/tsd/src/tsd/core/scene/objects/Camera.hpp b/tsd/src/tsd/core/scene/objects/Camera.hpp index d6c490a1d..33f77415c 100644 --- a/tsd/src/tsd/core/scene/objects/Camera.hpp +++ b/tsd/src/tsd/core/scene/objects/Camera.hpp @@ -3,7 +3,7 @@ #pragma once -#include "tsd/core/scene/Object.hpp" +#include "tsd/core/scene/ObjectUsePtr.hpp" namespace tsd::core { @@ -20,6 +20,7 @@ struct Camera : public Object }; using CameraRef = ObjectPoolRef; +using CameraAppRef = ObjectUsePtr; namespace tokens::camera { diff --git a/tsd/src/tsd/core/scene/objects/Renderer.cpp b/tsd/src/tsd/core/scene/objects/Renderer.cpp index d31c82949..9ffeb30dd 100644 --- a/tsd/src/tsd/core/scene/objects/Renderer.cpp +++ b/tsd/src/tsd/core/scene/objects/Renderer.cpp @@ -10,7 +10,22 @@ Renderer::Renderer(Token sourceDevice, Token subtype) : Object(ANARI_RENDERER, subtype) { m_rendererDeviceName = sourceDevice; + setCommonParameterDefaults(); +} + +ObjectPoolRef Renderer::self() const +{ + return scene() ? scene()->getObject(index()) + : ObjectPoolRef{}; +} + +anari::Object Renderer::makeANARIObject(anari::Device d) const +{ + return anari::newObject(d, subtype().c_str()); +} +void Renderer::setCommonParameterDefaults() +{ addParameter("background") .setValue(float4(0.05f, 0.05f, 0.05f, 1.f)) .setDescription("background color") @@ -25,15 +40,4 @@ Renderer::Renderer(Token sourceDevice, Token subtype) .setUsage(ParameterUsageHint::COLOR); } -ObjectPoolRef Renderer::self() const -{ - return scene() ? scene()->getObject(index()) - : ObjectPoolRef{}; -} - -anari::Object Renderer::makeANARIObject(anari::Device d) const -{ - return anari::newObject(d, subtype().c_str()); -} - } // namespace tsd::core diff --git a/tsd/src/tsd/core/scene/objects/Renderer.hpp b/tsd/src/tsd/core/scene/objects/Renderer.hpp index 8e6c8fa07..0465a36d8 100644 --- a/tsd/src/tsd/core/scene/objects/Renderer.hpp +++ b/tsd/src/tsd/core/scene/objects/Renderer.hpp @@ -18,6 +18,8 @@ struct Renderer : public Object ObjectPoolRef self() const; anari::Object makeANARIObject(anari::Device d) const override; + + void setCommonParameterDefaults(); }; using RendererRef = ObjectPoolRef; diff --git a/tsd/src/tsd/rendering/CMakeLists.txt b/tsd/src/tsd/rendering/CMakeLists.txt index 8c6ce7d70..206b9e46e 100644 --- a/tsd/src/tsd/rendering/CMakeLists.txt +++ b/tsd/src/tsd/rendering/CMakeLists.txt @@ -25,6 +25,7 @@ PRIVATE pipeline/RenderPipeline.cpp view/CameraUpdateDelegate.cpp view/ManipulatorToAnari.cpp + view/ManipulatorToTSD.cpp ) project_include_directories( diff --git a/tsd/src/tsd/rendering/index/RenderIndex.cpp b/tsd/src/tsd/rendering/index/RenderIndex.cpp index f067e4412..6deff302b 100644 --- a/tsd/src/tsd/rendering/index/RenderIndex.cpp +++ b/tsd/src/tsd/rendering/index/RenderIndex.cpp @@ -38,6 +38,37 @@ anari::Renderer RenderIndex::renderer(size_t i) return (anari::Renderer)m_cache.getHandle(ANARI_RENDERER, i, true); } +anari::Camera RenderIndex::camera(size_t i) +{ + return (anari::Camera)m_cache.getHandle(ANARI_CAMERA, i, true); +} + +CameraPose RenderIndex::computeDefaultView() const +{ + tsd::math::float3 bounds[2] = {{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; + if (!anariGetProperty(device(), + world(), + "bounds", + ANARI_FLOAT32_BOX3, + &bounds[0], + sizeof(bounds), + ANARI_WAIT)) { + tsd::core::logWarning( + "[RenderIndex::computeDefaultView] " + "anari::World returned no bounds!"); + } + + auto center = 0.5f * (bounds[0] + bounds[1]); + auto diag = bounds[1] - bounds[0]; + + CameraPose pose; + pose.fixedDist = 1.25f * tsd::math::length(diag); + pose.lookat = center; + pose.azeldist = {0.f, 20.f, pose.fixedDist}; + pose.upAxis = static_cast(UpAxis::POS_Y); + return pose; +} + void RenderIndex::logCacheInfo() const { logStatus("RENDER INDEX:"); @@ -51,6 +82,7 @@ void RenderIndex::logCacheInfo() const logStatus(" fields: %zu", m_cache.field.size()); logStatus(" lights: %zu", m_cache.light.size()); logStatus(" arrays: %zu", m_cache.array.size()); + logStatus(" cameras: %zu", m_cache.camera.size()); logStatus(" renderers: %zu", m_cache.renderer.size()); } @@ -73,6 +105,7 @@ void RenderIndex::populate(bool setAsUpdateDelegate) createANARICacheObjects(db.field, m_cache.field); createANARICacheObjects(db.volume, m_cache.volume); createANARICacheObjects(db.light, m_cache.light); + createANARICacheObjects(db.camera, m_cache.camera); createANARICacheObjects(db.renderer, m_cache.renderer); if (setAsUpdateDelegate) diff --git a/tsd/src/tsd/rendering/index/RenderIndex.hpp b/tsd/src/tsd/rendering/index/RenderIndex.hpp index 5c23e5717..6245c1074 100644 --- a/tsd/src/tsd/rendering/index/RenderIndex.hpp +++ b/tsd/src/tsd/rendering/index/RenderIndex.hpp @@ -9,6 +9,7 @@ #include "tsd/core/scene/UpdateDelegate.hpp" // tsd_rendering #include "tsd/rendering/index/RenderIndexFilterFcn.hpp" +#include "tsd/rendering/view/Manipulator.hpp" namespace tsd::rendering { @@ -24,6 +25,9 @@ struct RenderIndex : public BaseUpdateDelegate anari::Device device() const; anari::World world() const; anari::Renderer renderer(size_t i); + anari::Camera camera(size_t i); + + CameraPose computeDefaultView() const; void logCacheInfo() const; diff --git a/tsd/src/tsd/rendering/pipeline/passes/AnariAxesRenderPass.cpp b/tsd/src/tsd/rendering/pipeline/passes/AnariAxesRenderPass.cpp index 9f2c5b86e..7f89d5dbe 100644 --- a/tsd/src/tsd/rendering/pipeline/passes/AnariAxesRenderPass.cpp +++ b/tsd/src/tsd/rendering/pipeline/passes/AnariAxesRenderPass.cpp @@ -40,6 +40,7 @@ AnariAxesRenderPass::AnariAxesRenderPass( m_camera = anari::newObject(d, "perspective"); anari::setParameter(d, m_camera, "fovy", tsd::math::radians(5.f)); } + anari::setParameter(d, m_camera, "aspect", 1.f); anari::commitParameters(d, m_camera); anari::setParameter(d, m_frame, "camera", m_camera); diff --git a/tsd/src/tsd/rendering/pipeline/passes/AnariSceneRenderPass.cpp b/tsd/src/tsd/rendering/pipeline/passes/AnariSceneRenderPass.cpp index 1fa34a71c..b10a7848e 100644 --- a/tsd/src/tsd/rendering/pipeline/passes/AnariSceneRenderPass.cpp +++ b/tsd/src/tsd/rendering/pipeline/passes/AnariSceneRenderPass.cpp @@ -96,16 +96,23 @@ AnariSceneRenderPass::~AnariSceneRenderPass() void AnariSceneRenderPass::setCamera(anari::Camera c) { - anari::retain(m_device, c); + if (c) + anari::retain(m_device, c); anari::setParameter(m_device, m_frame, "camera", c); anari::commitParameters(m_device, m_frame); anari::release(m_device, m_camera); m_camera = c; + if (m_camera) { + auto size = getDimensions(); + anari::setParameter(m_device, m_camera, "aspect", size.x / float(size.y)); + anari::commitParameters(m_device, m_camera); + } } void AnariSceneRenderPass::setRenderer(anari::Renderer r) { - anari::retain(m_device, r); + if (r) + anari::retain(m_device, r); anari::setParameter(m_device, m_frame, "renderer", r); anari::commitParameters(m_device, m_frame); anari::release(m_device, m_renderer); @@ -114,7 +121,8 @@ void AnariSceneRenderPass::setRenderer(anari::Renderer r) void AnariSceneRenderPass::setWorld(anari::World w) { - anari::retain(m_device, w); + if (w) + anari::retain(m_device, w); anari::setParameter(m_device, m_frame, "world", w); anari::commitParameters(m_device, m_frame); anari::release(m_device, m_world); @@ -123,6 +131,7 @@ void AnariSceneRenderPass::setWorld(anari::World w) void AnariSceneRenderPass::setColorFormat(anari::DataType t) { + m_format = t; anari::setParameter(m_device, m_frame, "channel.color", t); anari::commitParameters(m_device, m_frame); } @@ -227,7 +236,8 @@ void AnariSceneRenderPass::setEnableAlbedo(bool on) anari::discard(m_device, m_frame); anari::wait(m_device, m_frame); - anari::setParameter(m_device, m_frame, "channel.albedo", ANARI_FLOAT32_VEC3); + anari::setParameter( + m_device, m_frame, "channel.albedo", ANARI_FLOAT32_VEC3); anari::commitParameters(m_device, m_frame); anari::render(m_device, m_frame); @@ -252,7 +262,8 @@ void AnariSceneRenderPass::setEnableNormals(bool on) anari::discard(m_device, m_frame); anari::wait(m_device, m_frame); - anari::setParameter(m_device, m_frame, "channel.normal", ANARI_FLOAT32_VEC3); + anari::setParameter( + m_device, m_frame, "channel.normal", ANARI_FLOAT32_VEC3); anari::commitParameters(m_device, m_frame); anari::render(m_device, m_frame); @@ -264,6 +275,11 @@ void AnariSceneRenderPass::setEnableNormals(bool on) } } +anari::DataType AnariSceneRenderPass::getColorFormat() const +{ + return m_format; +} + void AnariSceneRenderPass::setRunAsync(bool on) { m_runAsync = on; @@ -291,6 +307,11 @@ void AnariSceneRenderPass::updateSize() anari::setParameter(m_device, m_frame, "size", size); anari::commitParameters(m_device, m_frame); + if (m_camera) { + anari::setParameter(m_device, m_camera, "aspect", size.x / float(size.y)); + anari::commitParameters(m_device, m_camera); + } + const size_t totalSize = size_t(size.x) * size_t(size.y); m_buffers.color = detail::allocate(totalSize); m_buffers.depth = detail::allocate(totalSize); diff --git a/tsd/src/tsd/rendering/pipeline/passes/AnariSceneRenderPass.h b/tsd/src/tsd/rendering/pipeline/passes/AnariSceneRenderPass.h index 348a67373..20c7ea492 100644 --- a/tsd/src/tsd/rendering/pipeline/passes/AnariSceneRenderPass.h +++ b/tsd/src/tsd/rendering/pipeline/passes/AnariSceneRenderPass.h @@ -27,6 +27,7 @@ struct AnariSceneRenderPass : public RenderPass // default' true', if 'false', then anari::wait() on each pass void setRunAsync(bool on); + anari::DataType getColorFormat() const; // NOTE(jda): these do not increase ref count, no need to release anari::Device getDevice() const; anari::Frame getFrame() const; @@ -50,6 +51,8 @@ struct AnariSceneRenderPass : public RenderPass bool m_enableNormals{false}; bool m_runAsync{true}; + anari::DataType m_format{ANARI_UFIXED8_RGBA_SRGB}; + anari::Device m_device{nullptr}; anari::Camera m_camera{nullptr}; anari::Renderer m_renderer{nullptr}; diff --git a/tsd/src/tsd/rendering/view/Manipulator.hpp b/tsd/src/tsd/rendering/view/Manipulator.hpp index a6fdb47c8..6d4d85dad 100644 --- a/tsd/src/tsd/rendering/view/Manipulator.hpp +++ b/tsd/src/tsd/rendering/view/Manipulator.hpp @@ -4,21 +4,13 @@ #pragma once #include "tsd/core/TSDMath.hpp" +#include "tsd/core/ObjectVersion.hpp" // std #include namespace tsd::rendering { -using UpdateToken = size_t; - -struct CameraPose -{ - std::string name; - tsd::math::float3 lookat{0.f}; - tsd::math::float3 azeldist{0.f}; - float fixedDist{tsd::math::inf}; - int upAxis{0}; -}; +using UpdateToken = tsd::core::ObjectVersion; enum class UpAxis { @@ -30,6 +22,15 @@ enum class UpAxis NEG_Z }; +struct CameraPose +{ + std::string name; + tsd::math::float3 lookat{0.f}; + tsd::math::float3 azeldist{0.f}; + float fixedDist{tsd::math::inf}; + int upAxis{static_cast(UpAxis::POS_Y)}; +}; + class Manipulator { public: @@ -155,11 +156,7 @@ inline void Manipulator::startNewRotation() inline bool Manipulator::hasChanged(UpdateToken &t) const { - if (t < m_token) { - t = m_token; - return true; - } else - return false; + return tsd::core::versionChanged(t, m_token); } inline void Manipulator::rotate(anari::math::float2 delta) diff --git a/tsd/src/tsd/rendering/view/ManipulatorToTSD.cpp b/tsd/src/tsd/rendering/view/ManipulatorToTSD.cpp new file mode 100644 index 000000000..509ae84d0 --- /dev/null +++ b/tsd/src/tsd/rendering/view/ManipulatorToTSD.cpp @@ -0,0 +1,44 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "tsd/rendering/view/ManipulatorToTSD.hpp" + +namespace tsd::rendering { + +void updateCameraObject( + tsd::core::Camera &c, const Manipulator &m, bool includeManipulatorMetadata) +{ + c.setParameter("direction", m.dir()); + c.setParameter("up", m.up()); + + if (c.subtype() == core::tokens::camera::orthographic) { + c.setParameter("position", m.eye_FixedDistance()); + c.setParameter("height", m.distance() * 0.75f); + } else { + c.setParameter("position", m.eye()); + } + + if (includeManipulatorMetadata) { + c.setMetadataValue("manipulator.at", m.at()); + c.setMetadataValue("manipulator.distance", m.distance()); + c.setMetadataValue("manipulator.fixedDistance", m.fixedDistance()); + c.setMetadataValue("manipulator.azel", m.azel()); + c.setMetadataValue("manipulator.up", int(m.axis())); + } +} + +void updateManipulatorFromCamera(Manipulator &m, const tsd::core::Camera &c) +{ + auto at = c.getMetadataValue("manipulator.at").getValueOr(m.at()); + auto d = c.getMetadataValue("manipulator.distance").getValueOr(m.distance()); + auto azel = c.getMetadataValue("manipulator.azel").getValueOr(m.azel()); + auto up = c.getMetadataValue("manipulator.up").getValueOr(int(m.axis())); + auto fd = c.getMetadataValue("manipulator.fixedDistance") + .getValueOr(m.fixedDistance()); + + m.setConfig(at, d, azel); + m.setAxis(static_cast(up)); + m.setFixedDistance(fd); +} + +} // namespace tsd::rendering diff --git a/tsd/src/tsd/rendering/view/ManipulatorToTSD.hpp b/tsd/src/tsd/rendering/view/ManipulatorToTSD.hpp new file mode 100644 index 000000000..cac9044e1 --- /dev/null +++ b/tsd/src/tsd/rendering/view/ManipulatorToTSD.hpp @@ -0,0 +1,18 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "Manipulator.hpp" +// tsd_core +#include "tsd/core/scene/objects/Camera.hpp" + +namespace tsd::rendering { + +void updateCameraObject(tsd::core::Camera &c, + const Manipulator &m, + bool includeManipulatorMetadata = true); + +void updateManipulatorFromCamera(Manipulator &m, const tsd::core::Camera &c); + +} // namespace tsd::rendering diff --git a/tsd/src/tsd/ui/imgui/Application.cpp b/tsd/src/tsd/ui/imgui/Application.cpp index 00d2ef420..943273d65 100644 --- a/tsd/src/tsd/ui/imgui/Application.cpp +++ b/tsd/src/tsd/ui/imgui/Application.cpp @@ -46,6 +46,7 @@ CommandLineOptions *Application::commandLineOptions() { return &m_commandLine; } + #ifdef TSD_USE_LUA ExtensionManager *Application::extensionManager() const { @@ -205,6 +206,15 @@ void Application::teardown() } void Application::uiMainMenuBar() +{ + uiMainMenuBar_File(); + uiMainMenuBar_Edit(); + uiMainMenuBar_Tools(); + uiMainMenuBar_Lua(); + uiMainMenuBar_View(); +} + +void Application::uiMainMenuBar_File() { if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("Load")) @@ -253,7 +263,10 @@ void Application::uiMainMenuBar() ImGui::EndMenu(); } +} +void Application::uiMainMenuBar_Edit() +{ if (ImGui::BeginMenu("Edit")) { if (ImGui::MenuItem("Settings")) m_appSettingsDialog->show(); @@ -299,7 +312,10 @@ void Application::uiMainMenuBar() ImGui::EndMenu(); } +} +void Application::uiMainMenuBar_Tools() +{ if (ImGui::BeginMenu("Tools")) { if (ImGui::BeginMenu("OpenUSD Device")) { if (usdDeviceIsSetup()) { @@ -337,11 +353,27 @@ void Application::uiMainMenuBar() ImGui::EndMenu(); } - +} +void Application::uiMainMenuBar_Lua() +{ #ifdef TSD_USE_LUA - renderLuaMenu(); + if (ImGui::BeginMenu("Lua")) { + const auto &tree = m_extensionManager->getMenuTree(); + uiActionMenu(tree); + + if (!tree.empty()) + ImGui::Separator(); + + if (ImGui::MenuItem("Reload Script")) + m_extensionManager->refresh(); + + ImGui::EndMenu(); + } #endif +} +void Application::uiMainMenuBar_View() +{ if (ImGui::BeginMenu("View")) { for (auto &w : m_windows) { ImGui::PushID(&w); @@ -353,6 +385,31 @@ void Application::uiMainMenuBar() } } +void Application::uiActionMenu(const std::vector &entries) +{ + for (const auto &entry : entries) { + if (entry.isSeparator) { + ImGui::Separator(); + } else if (entry.isFolder) { + if (ImGui::BeginMenu(entry.name.c_str())) { + uiActionMenu(entry.children); + ImGui::EndMenu(); + } + } else { + if (ImGui::MenuItem(entry.name.c_str())) { + showTaskModal( + [this, actionIndex = entry.actionIndex]() { + m_extensionManager->executeAction(actionIndex); + }, + "Executing Action..."); + } + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", entry.name.c_str()); + } + } +} + void Application::doSave(const std::string &name) { if (!name.empty()) @@ -637,47 +694,4 @@ void Application::updateWindowTitle() SDL_SetWindowTitle(w, title.c_str()); } -#ifdef TSD_USE_LUA -void Application::renderLuaMenu() -{ - if (ImGui::BeginMenu("Lua")) { - const auto &tree = m_extensionManager->getMenuTree(); - renderActionMenu(tree); - - if (!tree.empty()) - ImGui::Separator(); - - if (ImGui::MenuItem("Reload Script")) - m_extensionManager->refresh(); - - ImGui::EndMenu(); - } -} -#endif - -void Application::renderActionMenu(const std::vector &entries) -{ - for (const auto &entry : entries) { - if (entry.isSeparator) { - ImGui::Separator(); - } else if (entry.isFolder) { - if (ImGui::BeginMenu(entry.name.c_str())) { - renderActionMenu(entry.children); - ImGui::EndMenu(); - } - } else { - if (ImGui::MenuItem(entry.name.c_str())) { - showTaskModal( - [this, actionIndex = entry.actionIndex]() { - m_extensionManager->executeAction(actionIndex); - }, - "Executing Action..."); - } - - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", entry.name.c_str()); - } - } -} - } // namespace tsd::ui::imgui diff --git a/tsd/src/tsd/ui/imgui/Application.h b/tsd/src/tsd/ui/imgui/Application.h index fbd938956..3170337af 100644 --- a/tsd/src/tsd/ui/imgui/Application.h +++ b/tsd/src/tsd/ui/imgui/Application.h @@ -81,10 +81,13 @@ class Application : public anari_viewer::Application virtual void uiMainMenuBar(); -#ifdef TSD_USE_LUA - void renderLuaMenu(); -#endif - void renderActionMenu(const std::vector &entries); + void uiMainMenuBar_File(); + void uiMainMenuBar_Edit(); + void uiMainMenuBar_Tools(); + void uiMainMenuBar_Lua(); + void uiMainMenuBar_View(); + + void uiActionMenu(const std::vector &entries); void doSave(const std::string &name = ""); diff --git a/tsd/src/tsd/ui/imgui/tsd_ui_imgui.cpp b/tsd/src/tsd/ui/imgui/tsd_ui_imgui.cpp index 3d2de21eb..48520aea2 100644 --- a/tsd/src/tsd/ui/imgui/tsd_ui_imgui.cpp +++ b/tsd/src/tsd/ui/imgui/tsd_ui_imgui.cpp @@ -639,9 +639,10 @@ bool buildUI_parameter(tsd::core::Object &o, else ImGui::BulletText("%s | [%zu] %s", name, idx, anari::toString(type)); } else { - if (useTable) - ImGui::Text("%s", anari::toString(type)); - else + if (useTable) { + ImGui::TextColored( + ImVec4(0.6f, 0.6f, 0.6f, 1.f), "%s", anari::toString(type)); + } else ImGui::BulletText("%s | %s", name, anari::toString(type)); } break; diff --git a/tsd/src/tsd/ui/imgui/windows/CameraPoses.cpp b/tsd/src/tsd/ui/imgui/windows/CameraPoses.cpp index ca0a2c038..637be44b9 100644 --- a/tsd/src/tsd/ui/imgui/windows/CameraPoses.cpp +++ b/tsd/src/tsd/ui/imgui/windows/CameraPoses.cpp @@ -49,11 +49,13 @@ void CameraPoses::buildUI() if (ImGui::IsItemHovered()) ImGui::SetTooltip("add a series of turntable camera poses"); +#if 0 ImGui::SameLine(); ImGui::BeginDisabled(!m_viewport); if (ImGui::Button("camera")) m_viewport->addCameraObjectFromCurrentView(); ImGui::EndDisabled(); +#endif if (ImGui::IsItemHovered()) ImGui::SetTooltip("add new camera object from current view"); @@ -238,7 +240,8 @@ void CameraPoses::buildUI_interpolationControls() ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f)); ImGui::TextWrapped( "Note: Rendering uses Offline Render Settings. " - "To change output folder, file prefix, and renderer: File / App Settings / Offline Render Settings"); + "To change output folder, file prefix, and renderer: " + "File / App Settings / Offline Render Settings"); ImGui::PopStyleColor(); // Render or Cancel button diff --git a/tsd/src/tsd/ui/imgui/windows/CameraPoses.h b/tsd/src/tsd/ui/imgui/windows/CameraPoses.h index 22073bc73..6e44f112c 100644 --- a/tsd/src/tsd/ui/imgui/windows/CameraPoses.h +++ b/tsd/src/tsd/ui/imgui/windows/CameraPoses.h @@ -32,7 +32,7 @@ struct CameraPoses : public Window tsd::math::float3 m_turntableCenter{0.f, 0.f, 0.f}; tsd::math::float3 m_turntableAzimuths{0.f, 360.f, 20.f}; - tsd::math::float3 m_turntableElevations{0.f, 45.f, 10.f}; + tsd::math::float3 m_turntableElevations{5.f, 45.f, 10.f}; float m_turntableDistance{1.f}; bool m_updateViewport{true}; // Update viewport during rendering diff --git a/tsd/src/tsd/ui/imgui/windows/LayerTree.cpp b/tsd/src/tsd/ui/imgui/windows/LayerTree.cpp index 78fcfb9d9..fc1fc83cc 100644 --- a/tsd/src/tsd/ui/imgui/windows/LayerTree.cpp +++ b/tsd/src/tsd/ui/imgui/windows/LayerTree.cpp @@ -610,7 +610,8 @@ void LayerTree::buildUI_objectSceneMenu() scene.signalLayerChange(&layer); } - ImGui::Separator(); + if (nodeSelected) + ImGui::Separator(); if (nodeSelected && ImGui::BeginMenu("rename")) { ImGui::InputText("##edit_node_name", &(*menuNode)->name()); @@ -714,16 +715,12 @@ void LayerTree::buildUI_objectSceneMenu() OBJECT_UI_MENU_ITEM("light", ANARI_LIGHT); OBJECT_UI_MENU_ITEM("surface", ANARI_SURFACE); OBJECT_UI_MENU_ITEM("volume", ANARI_VOLUME); +#undef OBJECT_UI_MENU_ITEM ImGui::EndMenu(); } ImGui::Separator(); - if (ImGui::MenuItem("import...")) - m_app->showImportFileDialog(); - - ImGui::Separator(); - if (ImGui::BeginMenu("procedural")) { if (ImGui::MenuItem("cylinders")) { tsd::io::generate_cylinders(scene, menuNode); @@ -763,6 +760,11 @@ void LayerTree::buildUI_objectSceneMenu() ImGui::EndMenu(); } + ImGui::Separator(); + + if (ImGui::MenuItem("from file...")) + m_app->showImportFileDialog(); + ImGui::EndMenu(); } diff --git a/tsd/src/tsd/ui/imgui/windows/Viewport.cpp b/tsd/src/tsd/ui/imgui/windows/Viewport.cpp index 1b741e3e6..c9fbfd6c0 100644 --- a/tsd/src/tsd/ui/imgui/windows/Viewport.cpp +++ b/tsd/src/tsd/ui/imgui/windows/Viewport.cpp @@ -10,7 +10,7 @@ #include "tsd/core/Logging.hpp" #include "tsd/core/scene/objects/Camera.hpp" // tsd_rendering -#include "tsd/rendering/view/ManipulatorToAnari.hpp" +#include "tsd/rendering/view/ManipulatorToTSD.hpp" // tsd_io #include "tsd/io/serialization.hpp" @@ -77,9 +77,8 @@ void Viewport::buildUI() bool didPick = ui_picking(); // Needs to happen before ui_menubar // Render the overlay after input handling so it does not interfere. - if (m_showOverlay) { + if (m_showOverlay) ui_overlay(); - } ImGui::EndDisabled(); @@ -107,23 +106,11 @@ void Viewport::resetView(bool resetAzEl) { if (!m_device) return; - - tsd::math::float3 bounds[2] = {{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; - if (!anariGetProperty(m_device, - m_rIdx->world(), - "bounds", - ANARI_FLOAT32_BOX3, - &bounds[0], - sizeof(bounds), - ANARI_WAIT)) { - tsd::core::logWarning("[viewport] ANARIWorld returned no bounds!"); - } - - auto center = 0.5f * (bounds[0] + bounds[1]); - auto diag = bounds[1] - bounds[0]; - + auto axis = m_arcball->axis(); auto azel = resetAzEl ? tsd::math::float2(0.f, 20.f) : m_arcball->azel(); - m_arcball->setConfig(center, 1.25f * linalg::length(diag), azel); + m_arcball->setConfig(m_rIdx->computeDefaultView()); + m_arcball->setAzel(azel); + m_arcball->setAxis(axis); m_cameraToken = 0; } @@ -131,19 +118,15 @@ void Viewport::centerView() { if (!m_device) return; - - tsd::math::float3 bounds[2] = {{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; - if (!anariGetProperty(m_device, - m_rIdx->world(), - "bounds", - ANARI_FLOAT32_BOX3, - &bounds[0], - sizeof(bounds), - ANARI_WAIT)) { - tsd::core::logWarning("[viewport] ANARIWorld returned no bounds!"); - } - - m_arcball->setCenter(0.5f * (bounds[0] + bounds[1])); + auto axis = m_arcball->axis(); + auto azel = m_arcball->azel(); + auto dist = m_arcball->distance(); + auto fixedDist = m_arcball->fixedDistance(); + m_arcball->setConfig(m_rIdx->computeDefaultView()); + m_arcball->setAzel(azel); + m_arcball->setDistance(dist); + m_arcball->setFixedDistance(fixedDist); + m_arcball->setAxis(axis); m_cameraToken = 0; } @@ -171,25 +154,13 @@ void Viewport::setLibrary(const std::string &libName, bool doAsync) m_maxFL = -std::numeric_limits::infinity(); if (d) { - if (auto *exts = adm.loadDeviceExtensions(libName); exts != nullptr) - m_extensions = *exts; - else - m_extensions = {}; - - tsd::core::logStatus("[viewport] getting renderer params..."); + tsd::core::logStatus("[viewport] setting up renderer objects..."); m_rendererObjects = scene.renderersOfDevice(libName); if (m_rendererObjects.empty()) m_rendererObjects = scene.createStandardRenderers(libName, d); m_currentRenderer = m_rendererObjects[0]; - m_perspCamera = anari::newObject(d, "perspective"); - m_currentCamera = m_perspCamera; - if (m_extensions.ANARI_KHR_CAMERA_ORTHOGRAPHIC) - m_orthoCamera = anari::newObject(d, "orthographic"); - if (m_extensions.ANARI_KHR_CAMERA_OMNIDIRECTIONAL) - m_omniCamera = anari::newObject(d, "omnidirectional"); - tsd::core::logStatus("[viewport] populating render index..."); m_rIdx = adm.acquireRenderIndex(scene, libName, d); @@ -207,6 +178,11 @@ void Viewport::setLibrary(const std::string &libName, bool doAsync) if (firstFrame && appCore()->commandLine.loadedFromStateFile) firstFrame = false; + if (!m_currentCamera) + m_currentCamera = scene.defaultCamera(); + + rendering::updateManipulatorFromCamera(*m_arcball, *m_currentCamera); + if (firstFrame || m_arcball->distance() == tsd::math::inf) { resetView(true); if (appCore()->view.poses.empty()) { @@ -274,9 +250,10 @@ void Viewport::saveSettings(tsd::core::DataNode &root) { root.reset(); // clear all previous values, if they exist + root["anariLibrary"] = m_libName; + // Viewport settings // - root["echoCameraConfig"] = m_echoCameraConfig; root["showOverlay"] = m_showOverlay; root["showCameraInfo"] = m_showCameraInfo; root["showOnlySelected"] = m_showOnlySelected; @@ -287,7 +264,6 @@ void Viewport::saveSettings(tsd::core::DataNode &root) root["depthVisualMaximum"] = m_depthVisualMaximum; root["edgeThreshold"] = m_edgeThreshold; root["edgeInvert"] = m_edgeInvert; - root["fov"] = m_fov; root["resolutionScale"] = m_resolutionScale; root["showAxes"] = m_showAxes; @@ -297,23 +273,10 @@ void Viewport::saveSettings(tsd::core::DataNode &root) root["gizmoOperation"] = static_cast(m_gizmoOperation); root["gizmoMode"] = static_cast(m_gizmoMode); - root["anariLibrary"] = m_libName; - - // Camera // - - auto &camera = root["camera"]; - camera["at"] = m_arcball->at(); - camera["distance"] = m_arcball->distance(); - camera["azel"] = m_arcball->azel(); - camera["up"] = int(m_arcball->axis()); - camera["apertureRadius"] = m_apertureRadius; - camera["focusDistance"] = m_focusDistance; - // Database Camera // - if (m_selectedCamera) { - root["selectedCamera"] = static_cast(m_selectedCamera.index()); - } + if (m_currentCamera) + root["currentCamera"] = static_cast(m_currentCamera->index()); // Base window settings // @@ -326,7 +289,6 @@ void Viewport::loadSettings(tsd::core::DataNode &root) // Viewport settings // - root["echoCameraConfig"].getValue(ANARI_BOOL, &m_echoCameraConfig); root["showOverlay"].getValue(ANARI_BOOL, &m_showOverlay); root["showCameraInfo"].getValue(ANARI_BOOL, &m_showCameraInfo); root["showOnlySelected"].getValue(ANARI_BOOL, &m_showOnlySelected); @@ -339,7 +301,6 @@ void Viewport::loadSettings(tsd::core::DataNode &root) root["depthVisualMaximum"].getValue(ANARI_FLOAT32, &m_depthVisualMaximum); root["edgeThreshold"].getValue(ANARI_FLOAT32, &m_edgeThreshold); root["edgeInvert"].getValue(ANARI_BOOL, &m_edgeInvert); - root["fov"].getValue(ANARI_FLOAT32, &m_fov); root["resolutionScale"].getValue(ANARI_FLOAT32, &m_resolutionScale); root["showAxes"].getValue(ANARI_BOOL, &m_showAxes); @@ -353,33 +314,12 @@ void Viewport::loadSettings(tsd::core::DataNode &root) root["gizmoMode"].getValue(ANARI_INT32, &gizmoMode); m_gizmoMode = static_cast(gizmoMode); - // Camera // - - if (auto *c = root.child("camera"); c != nullptr) { - tsd::math::float3 at(0.f); - float distance = 0.f; - tsd::math::float2 azel(0.f); - int axis = 0; - - auto &camera = *c; - camera["at"].getValue(ANARI_FLOAT32_VEC3, &at); - camera["distance"].getValue(ANARI_FLOAT32, &distance); - camera["azel"].getValue(ANARI_FLOAT32_VEC2, &azel); - camera["up"].getValue(ANARI_INT32, &axis); - - camera["apertureRadius"].getValue(ANARI_FLOAT32, &m_apertureRadius); - camera["focusDistance"].getValue(ANARI_FLOAT32, &m_focusDistance); - - m_arcball->setAxis(tsd::rendering::UpAxis(axis)); - m_arcball->setConfig(at, distance, azel); - } - // Database Camera // - if (auto *c = root.child("selectedCamera"); c) { + if (auto *c = root.child("currentCamera"); c) { uint64_t idx = 0; c->getValue(ANARI_UINT64, &idx); - m_selectedCamera = appCore()->tsd.scene.getObject(idx); + m_currentCamera = appCore()->tsd.scene.getObject(idx); } // Setup library // @@ -431,7 +371,10 @@ void Viewport::setupRenderPipeline() const float aspect = m_viewportSize.x / float(m_viewportSize.y); anari::math::float2 imgPlaneSize; - imgPlaneSize.y = 2.f * tanf(0.5f * anari::radians(m_fov)); + + auto fov = m_currentCamera->parameterValueAs("fovy").value_or( + math::radians(40.f)); + imgPlaneSize.y = 2.f * tanf(0.5f * fov); imgPlaneSize.x = imgPlaneSize.y * aspect; const auto d = m_arcball->dir(); @@ -499,8 +442,13 @@ void Viewport::setupRenderPipeline() m_outlinePass = m_pipeline.emplace_back(); + anari::Extensions extensions{}; + auto &adm = appCore()->anari; + if (auto *exts = adm.loadDeviceExtensions(m_libName); exts != nullptr) + extensions = *exts; + m_axesPass = m_pipeline.emplace_back( - m_device, m_extensions); + m_device, extensions); m_axesPass->setEnabled(m_showAxes); m_outputPass = m_pipeline.emplace_back( @@ -527,14 +475,8 @@ void Viewport::teardownDevice() m_rIdx = nullptr; m_libName.clear(); - anari::release(m_device, m_perspCamera); - anari::release(m_device, m_orthoCamera); - anari::release(m_device, m_omniCamera); anari::release(m_device, m_device); - m_perspCamera = nullptr; - m_orthoCamera = nullptr; - m_omniCamera = nullptr; m_rendererObjects.clear(); m_device = nullptr; @@ -584,8 +526,12 @@ void Viewport::updateFrame() if (!m_anariPass) return; - m_anariPass->setCamera(m_currentCamera); + if (!m_currentCamera) + m_currentCamera = appCore()->tsd.scene.defaultCamera(); + m_anariPass->setWorld(m_rIdx->world()); + if (m_currentCamera) + m_anariPass->setCamera(m_rIdx->camera(m_currentCamera->index())); if (m_currentRenderer) m_anariPass->setRenderer(m_rIdx->renderer(m_currentRenderer->index())); } @@ -595,74 +541,21 @@ void Viewport::updateCamera(bool force) if (!m_anariPass) return; - // Check if camera changed, might it be database camera or manipulator one. - bool isDbCamera = m_selectedCamera && m_cameraDelegate; + if (!m_currentCamera) + m_currentCamera = appCore()->tsd.scene.defaultCamera(); - // Before proceeding, check if the camera still does exist - if (isDbCamera && !m_selectedCamera->self()) { - tsd::core::logWarning( - "[viewport] selected camera no longer exists, reverting to manipulator camera"); - clearDatabaseCamera(); - isDbCamera = false; - } - - if (!force - && !(isDbCamera ? m_cameraDelegate->hasChanged(m_cameraToken) - : m_arcball->hasChanged(m_cameraToken))) + if (!force && !m_arcball->hasChanged(m_cameraToken)) return; // Get compass information - tsd::math::float3 axesDir; - tsd::math::float3 axesUp; - if (isDbCamera) { - applyCameraParameters(&*m_selectedCamera); - axesDir = m_selectedCamera->parameterValueAs("direction") - .value_or(tsd::math::float3(0.0f, 0.0f, -1.0f)); - axesUp = - m_selectedCamera->parameterValueAs("up").value_or( - tsd::math::float3(0.0f, 1.0f, 0.0f)); - } else { - // perspective camera // - tsd::rendering::updateCameraParametersPerspective( - m_device, m_perspCamera, *m_arcball); - anari::setParameter(m_device, - m_perspCamera, - "aspect", - m_viewportSize.x / float(m_viewportSize.y)); - anari::setParameter( - m_device, m_perspCamera, "apertureRadius", m_apertureRadius); - anari::setParameter( - m_device, m_perspCamera, "focusDistance", m_focusDistance); - - anari::setParameter(m_device, m_perspCamera, "fovy", anari::radians(m_fov)); - anari::commitParameters(m_device, m_perspCamera); - - // orthographic camera // - - if (m_orthoCamera) { - tsd::rendering::updateCameraParametersOrthographic( - m_device, m_orthoCamera, *m_arcball); - anari::setParameter(m_device, - m_orthoCamera, - "aspect", - m_viewportSize.x / float(m_viewportSize.y)); - anari::commitParameters(m_device, m_orthoCamera); - } + tsd::rendering::updateCameraObject(*m_currentCamera, *m_arcball); - // omnidirectional camera // - - if (m_omniCamera) { - tsd::rendering::updateCameraParametersPerspective( // also works for omni - m_device, - m_omniCamera, - *m_arcball); - anari::commitParameters(m_device, m_omniCamera); - } - if (m_echoCameraConfig) - echoCameraConfig(); - axesUp = m_arcball->up(); - axesDir = m_arcball->dir(); - } + auto axesDir = + m_currentCamera->parameterValueAs("direction") + .value_or(tsd::math::float3(0.0f, 0.0f, -1.0f)); + auto axesUp = + m_currentCamera->parameterValueAs("up").value_or( + tsd::math::float3(0.0f, 1.0f, 0.0f)); m_axesPass->setView(axesDir, axesUp); } @@ -704,446 +597,271 @@ void Viewport::updateImage() m_maxFL = std::max(m_maxFL, m_latestAnariFL); } -void Viewport::applyCameraParameters(tsd::core::Camera *cam) +void Viewport::ui_menubar() { - if (!cam || !m_device || !m_currentCamera) - return; - - auto d = m_device; - auto c = m_currentCamera; // ANARI camera - anari::setParameter( - d, m_currentCamera, "aspect", m_viewportSize.x / float(m_viewportSize.y)); - cam->updateAllANARIParameters(d, m_currentCamera); - anari::commitParameters(d, c); + if (ImGui::BeginMenuBar()) { + ui_menubar_Device(); + ImGui::BeginDisabled(!m_device); + ui_menubar_Renderer(); + ui_menubar_Camera(); + ui_menubar_TransformManipulator(); + ui_menubar_Viewport(); + ui_menubar_World(); + ImGui::EndDisabled(); + ImGui::EndMenuBar(); + } } -void Viewport::setDatabaseCamera(tsd::core::CameraRef cam) +void Viewport::ui_menubar_Device() { - // Detach previous delegate if any - if (m_cameraDelegate) - m_cameraDelegate->detach(); - m_cameraDelegate.reset(); - - m_selectedCamera = cam; - m_cameraToken = 0; - // Wire new delegate - if (m_selectedCamera) { - m_cameraDelegate = std::make_unique( - m_selectedCamera.data()); + if (ImGui::BeginMenu("Device")) { + const auto &libraryList = appCore()->anari.libraryList(); + for (auto &libName : libraryList) { + const bool isThisLibrary = m_libName == libName; + if (ImGui::RadioButton(libName.c_str(), isThisLibrary)) + setLibrary(libName); + } + ImGui::Separator(); + if (ImGui::MenuItem("Reload Current Device")) { + auto lib = m_libName; // setLibrary() clears m_libName + setLibrary(lib); + } + ImGui::EndMenu(); } - updateCamera(true); - tsd::core::logStatus( - "Viewport using database camera '%s'", cam->name().c_str()); } -void Viewport::clearDatabaseCamera() +void Viewport::ui_menubar_Renderer() { - // Detach delegate if any - if (m_cameraDelegate) - m_cameraDelegate->detach(); - m_cameraDelegate.reset(); - m_selectedCamera = {}; - m_cameraToken = 0; - updateCamera(true); - tsd::core::logStatus("Viewport using manipulator"); -} + if (ImGui::BeginMenu("Renderer")) { + if (m_rendererObjects.size() > 1) { + ImGui::Text("Subtype:"); + ImGui::Indent(INDENT_AMOUNT); + for (int i = 0; i < m_rendererObjects.size(); i++) { + auto ro = m_rendererObjects[i]; + const char *rName = ro->subtype().c_str(); + if (ImGui::RadioButton(rName, m_currentRenderer == ro)) { + m_currentRenderer = ro; + updateFrame(); + } + } + ImGui::Unindent(INDENT_AMOUNT); + } -void Viewport::createCameraFromCurrentView() -{ - auto &scene = appCore()->tsd.scene; + ImGui::Separator(); - tsd::core::CameraRef cam; + if (!m_rendererObjects.empty()) { + ImGui::Text("Parameters:"); + ImGui::Indent(INDENT_AMOUNT); - if (m_selectedCamera) { - // If a database camera is selected, copy it - auto sourceCam = m_selectedCamera; + tsd::ui::buildUI_object(*m_currentRenderer, appCore()->tsd.scene, true); - // Create new camera with same subtype - cam = scene.createObject(sourceCam->subtype()); + ImGui::Unindent(INDENT_AMOUNT); + ImGui::Separator(); + ImGui::Separator(); + ImGui::Indent(INDENT_AMOUNT); - // Copy all parameters - for (size_t i = 0; i < sourceCam->numParameters(); i++) { - const auto &srcParam = sourceCam->parameterAt(i); - const char *paramName = sourceCam->parameterNameAt(i); - cam->parameter(paramName)->setValue(srcParam.value()); - } - } else { - // No database camera selected, create from manipulator state - - // Determine camera type from current ANARI camera - tsd::core::Token subtype = tsd::core::tokens::camera::perspective; - if (m_currentCamera == m_orthoCamera) - subtype = tsd::core::tokens::camera::orthographic; - else if (m_currentCamera == m_omniCamera) - subtype = tsd::core::tokens::camera::omnidirectional; - - // Create camera object - cam = scene.createObject(subtype); - - // Set parameters from manipulator - auto eye = m_arcball->eye(); - auto dir = tsd::math::normalize(m_arcball->at() - eye); - auto up = m_arcball->up(); - - cam->parameter("position")->setValue(eye); - cam->parameter("direction")->setValue(dir); - cam->parameter("up")->setValue(up); - - // Set type-specific params - if (subtype == tsd::core::tokens::camera::perspective) { - cam->parameter("fovy")->setValue(tsd::math::radians(m_fov)); - cam->parameter("apertureRadius")->setValue(m_apertureRadius); - cam->parameter("focusDistance")->setValue(m_focusDistance); - } else if (subtype == tsd::core::tokens::camera::orthographic) { - // Nothing to set here - } - } + if (ImGui::BeginMenu("Reset to Defaults?")) { + if (ImGui::MenuItem("Yes")) { + m_currentRenderer->removeAllParameters(); + m_currentRenderer->setCommonParameterDefaults(); + tsd::core::parseANARIObjectInfo(*m_currentRenderer, + m_device, + ANARI_RENDERER, + m_currentRenderer->subtype().c_str()); + } + ImGui::EndMenu(); + } - // Set name based on interpolation algorithm - const auto &pathSettings = appCore()->view.pathSettings; - std::string interpName; - switch (pathSettings.type) { - case tsd::rendering::CameraPathInterpolationType::LINEAR: - interpName = "Linear"; - break; - case tsd::rendering::CameraPathInterpolationType::SMOOTH: - interpName = "Smooth"; - break; - default: - interpName = "Unknown"; - break; + ImGui::Unindent(INDENT_AMOUNT); + } + ImGui::EndMenu(); } - - std::string name = "Camera_" + interpName + "_" + std::to_string(cam.index()); - cam->setName(name.c_str()); - - // Auto-select it - setDatabaseCamera(cam); - - tsd::core::logStatus("Created camera '%s' from current view", name.c_str()); } -void Viewport::addCameraObjectFromCurrentView() +void Viewport::ui_menubar_Camera() { - createCameraFromCurrentView(); - - auto cam = m_selectedCamera; - if (!cam) { - tsd::core::logWarning("No camera available to add to scene"); - return; - } + if (ImGui::BeginMenu("Camera")) { + auto &scene = appCore()->tsd.scene; - auto &scene = appCore()->tsd.scene; - auto selectedNode = appCore()->getFirstSelected(); - auto *layer = - selectedNode.valid() ? selectedNode->container() : scene.defaultLayer(); - if (!layer) { - tsd::core::logWarning("No layer available to add camera object"); - return; - } + ImGui::Text("Manipulator:"); + { + ImGui::Indent(INDENT_AMOUNT); - // Always add camera to the root of the layer, not as a child of selection - auto cameraNode = - scene.insertChildObjectNode(layer->root(), cam, cam->name().c_str()); - appCore()->setSelected(cameraNode); - appCore()->view.cameraPathCameraIndex = cam.index(); - if (appCore()->offline.camera.cameraIndex == TSD_INVALID_INDEX) { - appCore()->offline.camera.cameraIndex = cam.index(); - tsd::core::logStatus( - "Offline render camera set to '%s'", cam->name().c_str()); - } - appCore()->updateCameraPathAnimation(); + if (ImGui::Combo("Up", &m_arcballUp, "+x\0+y\0+z\0-x\0-y\0-z\0\0")) { + m_arcball->setAxis(static_cast(m_arcballUp)); + resetView(); + } - tsd::core::logStatus("Added camera '%s' to scene", cam->name().c_str()); -} + auto at = m_arcball->at(); + auto azel = m_arcball->azel(); + auto dist = m_arcball->distance(); + auto fixedDist = m_arcball->fixedDistance(); -void Viewport::echoCameraConfig() -{ - const auto p = m_arcball->eye(); - const auto d = m_arcball->dir(); - const auto u = m_arcball->up(); - - tsd::core::logStatus("Camera:"); - tsd::core::logStatus(" pos: %f, %f, %f", p.x, p.y, p.z); - tsd::core::logStatus(" dir: %f, %f, %f", d.x, d.y, d.z); - tsd::core::logStatus(" up: %f, %f, %f", u.x, u.y, u.z); -} + bool update = ImGui::SliderFloat("Azimuth", &azel.x, 0.f, 360.f); + update |= ImGui::SliderFloat("Elevation", &azel.y, 0.f, 360.f); + update |= ImGui::DragFloat("Distance", &dist); + update |= ImGui::DragFloat3("At", &at.x); + ImGui::BeginDisabled( + m_currentCamera->subtype() != core::tokens::camera::orthographic); + update |= ImGui::DragFloat("Near", &fixedDist); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("near plane distance for orthographic camera"); + ImGui::EndDisabled(); -void Viewport::ui_menubar() -{ - if (ImGui::BeginMenuBar()) { - // Device // - - if (ImGui::BeginMenu("Device")) { - const auto &libraryList = appCore()->anari.libraryList(); - for (auto &libName : libraryList) { - const bool isThisLibrary = m_libName == libName; - if (ImGui::RadioButton(libName.c_str(), isThisLibrary)) - setLibrary(libName); - } - ImGui::Separator(); - if (ImGui::MenuItem("reload device")) { - auto lib = m_libName; // setLibrary() clears m_libName - setLibrary(lib); + if (update) { + m_arcball->setConfig(at, dist, azel); + m_arcball->setFixedDistance(fixedDist); } - ImGui::EndMenu(); - } - ImGui::BeginDisabled(!m_device); + ImGui::Separator(); - // Renderer // - - if (ImGui::BeginMenu("Renderer")) { - if (m_rendererObjects.size() > 1) { - ImGui::Text("Subtype:"); - ImGui::Indent(INDENT_AMOUNT); - for (int i = 0; i < m_rendererObjects.size(); i++) { - auto ro = m_rendererObjects[i]; - const char *rName = ro->subtype().c_str(); - if (ImGui::RadioButton(rName, m_currentRenderer == ro)) { - m_currentRenderer = ro; - updateFrame(); - } - } - ImGui::Unindent(INDENT_AMOUNT); + if (ImGui::BeginMenu("Reset View")) { + if (ImGui::MenuItem("Center")) + centerView(); + if (ImGui::MenuItem("Distance")) + resetView(false); + if (ImGui::MenuItem("Angle + Distance + Center")) + resetView(true); + ImGui::EndMenu(); } - ImGui::Separator(); + ImGui::Unindent(INDENT_AMOUNT); + } - if (!m_rendererObjects.empty()) { - ImGui::Text("Parameters:"); - ImGui::Indent(INDENT_AMOUNT); + ImGui::Separator(); - tsd::ui::buildUI_object(*m_currentRenderer, appCore()->tsd.scene, true); + ImGui::Text("Current Camera:"); + { + ImGui::Indent(INDENT_AMOUNT); - ImGui::Unindent(INDENT_AMOUNT); - ImGui::Separator(); - ImGui::Separator(); - ImGui::Indent(INDENT_AMOUNT); + if (ImGui::BeginMenu("Select Camera")) { + if (ImGui::BeginMenu("New")) { + tsd::core::CameraRef newCam; + if (ImGui::MenuItem("Perspective")) { + newCam = scene.createObject( + tsd::core::tokens::camera::perspective); + } + if (ImGui::MenuItem("Orthographic")) { + newCam = scene.createObject( + tsd::core::tokens::camera::orthographic); + } + if (ImGui::MenuItem("Omnidirectional")) { + newCam = scene.createObject( + tsd::core::tokens::camera::omnidirectional); + } -#if 0 - if (ImGui::BeginMenu("reset to defaults?")) { - if (ImGui::MenuItem("yes")) { - loadANARIRendererParameters(m_device); - updateAllRendererParameters(m_device); + if (newCam) { + m_currentCamera = newCam; + newCam->setName("camera" + std::to_string(newCam->index())); + updateCamera(true); updateFrame(); } - ImGui::EndMenu(); - } -#endif - - ImGui::Unindent(INDENT_AMOUNT); - } - ImGui::EndMenu(); - } - - // Camera // - if (ImGui::BeginMenu("Camera")) { - { - ImGui::Text("Subtype:"); - ImGui::Indent(INDENT_AMOUNT); - - bool changeType = false; - if (ImGui::RadioButton( - "perspective", m_currentCamera == m_perspCamera)) { - m_currentCamera = m_perspCamera; - changeType = true; - } - - ImGui::BeginDisabled(!m_orthoCamera); - if (ImGui::RadioButton("orthographic", - m_orthoCamera && m_currentCamera == m_orthoCamera)) { - m_currentCamera = m_orthoCamera; - changeType = true; + ImGui::EndMenu(); } - ImGui::EndDisabled(); - ImGui::BeginDisabled(!m_omniCamera); - if (ImGui::RadioButton("omnidirectional", - m_omniCamera && m_currentCamera == m_omniCamera)) { - m_currentCamera = m_omniCamera; - changeType = true; - } - ImGui::EndDisabled(); + ImGui::Separator(); - if (changeType) + auto t = ANARI_CAMERA; + if (auto i = tsd::ui::buildUI_objects_menulist(scene, t); + i != TSD_INVALID_INDEX) { + m_currentCamera = scene.getObject(i); + tsd::rendering::updateManipulatorFromCamera( + *m_arcball, *m_currentCamera); updateFrame(); + } - ImGui::Unindent(INDENT_AMOUNT); - } - - if (ImGui::Combo("up", &m_arcballUp, "+x\0+y\0+z\0-x\0-y\0-z\0\0")) { - m_arcball->setAxis(static_cast(m_arcballUp)); - resetView(); + ImGui::EndMenu(); } ImGui::Separator(); - - ImGui::BeginDisabled(m_currentCamera != m_perspCamera); - - ImGui::Text("Perspective Parameters:"); - - ImGui::Indent(INDENT_AMOUNT); - if (ImGui::SliderFloat("fov", &m_fov, 0.1f, 180.f)) - updateCamera(true); - - { - ImGui::Text("Depth of Field:"); - ImGui::Indent(INDENT_AMOUNT); - if (ImGui::DragFloat("aperture", &m_apertureRadius, 0.01f, 0.f, 1.f)) - updateCamera(true); - - if (ImGui::DragFloat( - "focus distance", &m_focusDistance, 0.1f, 0.f, 1e20f)) - updateCamera(true); - - ImGui::Unindent(INDENT_AMOUNT); - } + tsd::ui::buildUI_object(*m_currentCamera, scene, true); ImGui::Unindent(INDENT_AMOUNT); - ImGui::EndDisabled(); + } - ImGui::Separator(); + ImGui::EndMenu(); + } +} - ImGui::Text("Reset View:"); - ImGui::Indent(INDENT_AMOUNT); - if (ImGui::MenuItem("center")) - centerView(); - if (ImGui::MenuItem("dist")) - resetView(false); - if (ImGui::MenuItem("angle + dist + center")) - resetView(true); - ImGui::Unindent(INDENT_AMOUNT); +void Viewport::ui_menubar_TransformManipulator() +{ + if (ImGui::BeginMenu("Transform Manipulator")) { + ImGui::Checkbox("Enabled", &m_enableGizmo); - ImGui::Separator(); + ImGui::Separator(); + ImGui::Text("Operation:"); + ImGui::Indent(INDENT_AMOUNT); + const auto &gOp = m_gizmoOperation; + if (ImGui::RadioButton("(w) Translate", gOp == ImGuizmo::TRANSLATE)) + m_gizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::RadioButton("(e) Scale", gOp == ImGuizmo::SCALE)) + m_gizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("(r) Rotate", gOp == ImGuizmo::ROTATE)) + m_gizmoOperation = ImGuizmo::ROTATE; + ImGui::Unindent(INDENT_AMOUNT); - if (ImGui::Checkbox("echo config", &m_echoCameraConfig) - && m_echoCameraConfig) - echoCameraConfig(); + ImGui::Separator(); + ImGui::Text("Mode:"); + ImGui::Indent(INDENT_AMOUNT); + if (ImGui::RadioButton("Local", m_gizmoMode == ImGuizmo::LOCAL)) + m_gizmoMode = ImGuizmo::LOCAL; + if (ImGui::RadioButton("World", m_gizmoMode == ImGuizmo::WORLD)) + m_gizmoMode = ImGuizmo::WORLD; + ImGui::Unindent(INDENT_AMOUNT); - ImGui::Separator(); + ImGui::EndMenu(); + } +} - // Database Camera Selection - ImGui::Text("Database Camera:"); +void Viewport::ui_menubar_Viewport() +{ + if (ImGui::BeginMenu("Viewport")) { + { + ImGui::Text("Format:"); ImGui::Indent(INDENT_AMOUNT); - - // Build camera list - std::vector cameraNames = {""}; - m_menuCameraRefs.resize(1); - m_menuCameraRefs[0] = {}; - int currentSelection = 0; - - const auto &cameraDB = appCore()->tsd.scene.objectDB().camera; - tsd::core::foreach_item_const(cameraDB, [&](const auto *cam) { - if (cam) { - cameraNames.push_back(cam->name()); - m_menuCameraRefs.push_back(cam->self()); - if (m_selectedCamera == cam->self()) { - currentSelection = static_cast(cameraNames.size() - 1); - } - } - }); - - if (ImGui::Combo( - "Select", - ¤tSelection, - [](void *data, int idx, const char **out) { - auto *names = (std::vector *)data; - *out = (*names)[idx].c_str(); - return true; - }, - &cameraNames, - static_cast(cameraNames.size()))) { - if (currentSelection == 0) { - clearDatabaseCamera(); - } else { - setDatabaseCamera(m_menuCameraRefs[currentSelection]); - } - } - - if (ImGui::Button("Create from Current View")) { - createCameraFromCurrentView(); - } - + anari::DataType format = m_anariPass->getColorFormat(); + if (ImGui::RadioButton( + "UFIXED8_RGBA_SRGB", format == ANARI_UFIXED8_RGBA_SRGB)) + format = ANARI_UFIXED8_RGBA_SRGB; + if (ImGui::RadioButton("UFIXED8_VEC4", format == ANARI_UFIXED8_VEC4)) + format = ANARI_UFIXED8_VEC4; + if (ImGui::RadioButton("FLOAT32_VEC4", format == ANARI_FLOAT32_VEC4)) + format = ANARI_FLOAT32_VEC4; + + if (format != m_anariPass->getColorFormat()) + m_anariPass->setColorFormat(format); ImGui::Unindent(INDENT_AMOUNT); - - ImGui::EndMenu(); } - // Gizmo // - - if (ImGui::BeginMenu("Transform Manipulator")) { - ImGui::Checkbox("Enable Manipulator", &m_enableGizmo); + ImGui::Separator(); - ImGui::Separator(); - ImGui::Text("Operation:"); + { + ImGui::Text("Render Resolution:"); ImGui::Indent(INDENT_AMOUNT); - const auto &gOp = m_gizmoOperation; - if (ImGui::RadioButton("(w) Translate", gOp == ImGuizmo::TRANSLATE)) - m_gizmoOperation = ImGuizmo::TRANSLATE; - if (ImGui::RadioButton("(e) Scale", gOp == ImGuizmo::SCALE)) - m_gizmoOperation = ImGuizmo::SCALE; - if (ImGui::RadioButton("(r) Rotate", gOp == ImGuizmo::ROTATE)) - m_gizmoOperation = ImGuizmo::ROTATE; - ImGui::Unindent(INDENT_AMOUNT); - ImGui::Separator(); - ImGui::Text("Mode:"); - ImGui::Indent(INDENT_AMOUNT); - if (ImGui::RadioButton("Local", m_gizmoMode == ImGuizmo::LOCAL)) - m_gizmoMode = ImGuizmo::LOCAL; - if (ImGui::RadioButton("World", m_gizmoMode == ImGuizmo::WORLD)) - m_gizmoMode = ImGuizmo::WORLD; - ImGui::Unindent(INDENT_AMOUNT); + const float current = m_resolutionScale; + if (ImGui::RadioButton("100%", current == 1.f)) + m_resolutionScale = 1.f; + if (ImGui::RadioButton("75%", current == 0.75f)) + m_resolutionScale = 0.75f; + if (ImGui::RadioButton("50%", current == 0.5f)) + m_resolutionScale = 0.5f; + if (ImGui::RadioButton("25%", current == 0.25f)) + m_resolutionScale = 0.25f; + if (ImGui::RadioButton("12.5%", current == 0.125f)) + m_resolutionScale = 0.125f; + + if (current != m_resolutionScale) + reshape(m_viewportSize); - ImGui::EndMenu(); + ImGui::Unindent(INDENT_AMOUNT); } - // Viewport // - - if (ImGui::BeginMenu("Viewport")) { - { - ImGui::Text("Format:"); - ImGui::Indent(INDENT_AMOUNT); - const anari::DataType format = m_format; - if (ImGui::RadioButton( - "UFIXED8_RGBA_SRGB", m_format == ANARI_UFIXED8_RGBA_SRGB)) - m_format = ANARI_UFIXED8_RGBA_SRGB; - if (ImGui::RadioButton("UFIXED8_VEC4", m_format == ANARI_UFIXED8_VEC4)) - m_format = ANARI_UFIXED8_VEC4; - if (ImGui::RadioButton("FLOAT32_VEC4", m_format == ANARI_FLOAT32_VEC4)) - m_format = ANARI_FLOAT32_VEC4; - - if (format != m_format) - m_anariPass->setColorFormat(m_format); - ImGui::Unindent(INDENT_AMOUNT); - } - - ImGui::Separator(); - - { - ImGui::Text("Render Resolution:"); - ImGui::Indent(INDENT_AMOUNT); - - const float current = m_resolutionScale; - if (ImGui::RadioButton("100%", current == 1.f)) - m_resolutionScale = 1.f; - if (ImGui::RadioButton("75%", current == 0.75f)) - m_resolutionScale = 0.75f; - if (ImGui::RadioButton("50%", current == 0.5f)) - m_resolutionScale = 0.5f; - if (ImGui::RadioButton("25%", current == 0.25f)) - m_resolutionScale = 0.25f; - if (ImGui::RadioButton("12.5%", current == 0.125f)) - m_resolutionScale = 0.125f; - - if (current != m_resolutionScale) - reshape(m_viewportSize); - - ImGui::Unindent(INDENT_AMOUNT); - } + ImGui::Separator(); - ImGui::Separator(); + { + ImGui::Text("AOV Visualization:"); + ImGui::Indent(INDENT_AMOUNT); const char *aovItems[] = {"default", "depth", @@ -1153,8 +871,8 @@ void Viewport::ui_menubar() "object ID", "primitive ID", "instance ID"}; - if (int aov = int(m_visualizeAOV); ImGui::Combo( - "visualize AOV", &aov, aovItems, IM_ARRAYSIZE(aovItems))) { + if (int aov = int(m_visualizeAOV); + ImGui::Combo("AOV", &aov, aovItems, IM_ARRAYSIZE(aovItems))) { if (aov != int(m_visualizeAOV)) { m_visualizeAOV = static_cast(aov); m_visualizeAOVPass->setAOVType(m_visualizeAOV); @@ -1174,12 +892,12 @@ void Viewport::ui_menubar() ImGui::BeginDisabled(m_visualizeAOV != tsd::rendering::AOVType::DEPTH); bool depthRangeChanged = false; - depthRangeChanged |= ImGui::DragFloat("depth minimum", + depthRangeChanged |= ImGui::DragFloat("Depth Minimum", &m_depthVisualMinimum, 0.1f, 0.f, m_depthVisualMaximum); - depthRangeChanged |= ImGui::DragFloat("depth maximum", + depthRangeChanged |= ImGui::DragFloat("Depth Maximum", &m_depthVisualMaximum, 0.1f, m_depthVisualMinimum, @@ -1192,92 +910,102 @@ void Viewport::ui_menubar() ImGui::BeginDisabled(m_visualizeAOV != tsd::rendering::AOVType::EDGES); bool edgeSettingsChanged = false; edgeSettingsChanged |= - ImGui::DragFloat("edge threshold", &m_edgeThreshold, 0.01f, 0.f, 1.f); - edgeSettingsChanged |= ImGui::Checkbox("invert edges", &m_edgeInvert); + ImGui::DragFloat("Edge Threshold", &m_edgeThreshold, 0.01f, 0.f, 1.f); + edgeSettingsChanged |= ImGui::Checkbox("Invert Edges", &m_edgeInvert); if (edgeSettingsChanged) { m_visualizeAOVPass->setEdgeThreshold(m_edgeThreshold); m_visualizeAOVPass->setEdgeInvert(m_edgeInvert); } ImGui::EndDisabled(); - ImGui::Separator(); + ImGui::Unindent(INDENT_AMOUNT); + } + + ImGui::Separator(); + + { + ImGui::Text("Display:"); + ImGui::Indent(INDENT_AMOUNT); ImGui::BeginDisabled(m_showOnlySelected); - ImGui::Checkbox("highlight selected", &m_highlightSelection); + ImGui::Checkbox("Highlight Selected", &m_highlightSelection); ImGui::EndDisabled(); - if (ImGui::Checkbox("only show selected", &m_showOnlySelected)) + if (ImGui::Checkbox("Only Show Selected", &m_showOnlySelected)) setSelectionVisibilityFilterEnabled(m_showOnlySelected); - ImGui::Separator(); + ImGui::Unindent(INDENT_AMOUNT); + } - if (ImGui::Checkbox("show axes", &m_showAxes)) - m_axesPass->setEnabled(m_showAxes); + ImGui::Separator(); - ImGui::Separator(); + { + ImGui::Text("Overlay:"); + ImGui::Indent(INDENT_AMOUNT); + + if (ImGui::Checkbox("Axes", &m_showAxes)) + m_axesPass->setEnabled(m_showAxes); - ImGui::Checkbox("show info overlay", &m_showOverlay); - if (ImGui::MenuItem("reset stats")) { + ImGui::Checkbox("Info Window", &m_showOverlay); + if (ImGui::MenuItem("Reset Timing Stats")) { m_minFL = m_latestFL; m_maxFL = m_latestFL; } + ImGui::Unindent(INDENT_AMOUNT); + } - ImGui::Separator(); - - if (ImGui::MenuItem("take screenshot")) { - // Generate timestamped filename - auto now = std::chrono::system_clock::now(); - auto time_t = std::chrono::system_clock::to_time_t(now); - auto ms = std::chrono::duration_cast( - now.time_since_epoch()) - % 1000; - - std::stringstream ss; - ss << "screenshot_" - << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S") << "_" - << std::setfill('0') << std::setw(3) << ms.count() << ".png"; - - // Ensure the screenshot is saved in the current working directory - std::filesystem::path workingDir = std::filesystem::current_path(); - std::filesystem::path filename = workingDir / ss.str(); - - m_saveToFilePass->setFilename(filename.string()); - m_saveToFilePass->setEnabled(true); - } + ImGui::Separator(); - ImGui::EndMenu(); + if (ImGui::MenuItem("Take Screenshot")) { + // Generate timestamped filename + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + auto ms = std::chrono::duration_cast( + now.time_since_epoch()) + % 1000; + + std::stringstream ss; + ss << "screenshot_" + << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S") << "_" + << std::setfill('0') << std::setw(3) << ms.count() << ".png"; + + // Ensure the screenshot is saved in the current working directory + std::filesystem::path workingDir = std::filesystem::current_path(); + std::filesystem::path filename = workingDir / ss.str(); + + m_saveToFilePass->setFilename(filename.string()); + m_saveToFilePass->setEnabled(true); } - // World // - - if (ImGui::BeginMenu("World")) { - if (ImGui::MenuItem("print bounds")) { - tsd::math::float3 bounds[2]; - - anariGetProperty(m_device, - m_rIdx->world(), - "bounds", - ANARI_FLOAT32_BOX3, - &bounds[0], - sizeof(bounds), - ANARI_WAIT); - - tsd::core::logStatus( - "[viewport] current world bounds {%f, %f, %f} x {%f, %f, %f}", - bounds[0].x, - bounds[0].y, - bounds[0].z, - bounds[1].x, - bounds[1].y, - bounds[1].z); - } + ImGui::EndMenu(); + } +} - ImGui::EndMenu(); - } +void Viewport::ui_menubar_World() +{ + if (ImGui::BeginMenu("World")) { + if (ImGui::MenuItem("Print Bounds")) { + tsd::math::float3 bounds[2]; - ImGui::EndDisabled(); + anariGetProperty(m_device, + m_rIdx->world(), + "bounds", + ANARI_FLOAT32_BOX3, + &bounds[0], + sizeof(bounds), + ANARI_WAIT); - ImGui::EndMenuBar(); + tsd::core::logStatus( + "[viewport] current world bounds {%f, %f, %f} x {%f, %f, %f}", + bounds[0].x, + bounds[0].y, + bounds[0].z, + bounds[1].x, + bounds[1].y, + bounds[1].z); + } + + ImGui::EndMenu(); } } @@ -1338,10 +1066,6 @@ void Viewport::ui_handleInput() if (!ImGui::IsWindowHovered() && !m_manipulating) return; - // Block arcball input when a database camera is selected - if (m_selectedCamera) - return; - ImGuiIO &io = ImGui::GetIO(); const bool dolly = ImGui::IsMouseDown(ImGuiMouseButton_Right) @@ -1403,9 +1127,13 @@ bool Viewport::ui_picking() { const ImGuiIO &io = ImGui::GetIO(); + if (!m_currentCamera) + return false; + // Pick view center // - const bool shouldPickCenter = m_currentCamera == m_perspCamera + const bool shouldPickCenter = + m_currentCamera->subtype() == core::tokens::camera::perspective && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && ImGui::IsKeyDown(ImGuiKey_LeftShift); if (shouldPickCenter && ImGui::IsWindowHovered()) { @@ -1453,48 +1181,27 @@ void Viewport::ui_overlay() if (ImGui::BeginChild( "##viewportOverlay", ImVec2(0, 0), childFlags, childWindowFlags)) { ImGui::Text(" device: %s", m_libName.c_str()); + ImGui::TextColored(ImVec4(0.0f, 1.0f, 1.0f, 1.f), + "renderer: %s", + m_currentRenderer ? m_currentRenderer->subtype().c_str() : "---"); // Camera indicator - if (m_selectedCamera) { - ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), - " camera: %s", - m_selectedCamera->name().c_str()); - } else { - ImGui::Text(" camera: "); - } + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), + " camera: %s", + m_currentCamera ? m_currentCamera->name().c_str() : "---"); + + ImGui::Separator(); - ImGui::Text("Viewport: %i x %i", m_viewportSize.x, m_viewportSize.y); + ImGui::Text("viewport: %i x %i", m_viewportSize.x, m_viewportSize.y); ImGui::Text(" render: %i x %i", m_renderSize.x, m_renderSize.y); - ImGui::Text(" samples: %i", m_frameSamples); + ImGui::Separator(); + + ImGui::Text(" samples: %i", m_frameSamples); ImGui::Text(" display: %.2fms", m_latestFL); ImGui::Text(" ANARI: %.2fms", m_latestAnariFL); ImGui::Text(" (min): %.2fms", m_minFL); ImGui::Text(" (max): %.2fms", m_maxFL); - - ImGui::Separator(); - ImGui::Checkbox("camera config", &m_showCameraInfo); - if (m_showCameraInfo) { - auto at = m_arcball->at(); - auto azel = m_arcball->azel(); - auto dist = m_arcball->distance(); - auto fixedDist = m_arcball->fixedDistance(); - - bool update = ImGui::SliderFloat("az", &azel.x, 0.f, 360.f); - update |= ImGui::SliderFloat("el", &azel.y, 0.f, 360.f); - update |= ImGui::DragFloat("dist", &dist); - update |= ImGui::DragFloat3("at", &at.x); - ImGui::BeginDisabled(m_currentCamera != m_orthoCamera); - update |= ImGui::DragFloat("near", &fixedDist); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("near plane distance for orthographic camera"); - ImGui::EndDisabled(); - - if (update) { - m_arcball->setConfig(at, dist, azel); - m_arcball->setFixedDistance(fixedDist); - } - } } ImGui::EndChild(); @@ -1505,8 +1212,6 @@ bool Viewport::canShowGizmo() const { if (!m_enableGizmo || !m_deviceReadyToUse) return false; - if (m_selectedCamera) - return false; // No gizmo with database camera // Check if we have a selected node with a transform auto selectedNode = appCore()->getFirstSelected(); @@ -1537,7 +1242,8 @@ void Viewport::ui_gizmo() auto parentWorldTransform = computeWorldTransform(parentNodeRef); auto worldTransform = mul(parentWorldTransform, localTransform); - ImGuizmo::SetOrthographic(m_currentCamera == m_orthoCamera); + ImGuizmo::SetOrthographic( + m_currentCamera->subtype() == core::tokens::camera::orthographic); ImGuizmo::BeginFrame(); // Setup ImGuizmo with window and relative viewport information @@ -1556,7 +1262,6 @@ void Viewport::ui_gizmo() const auto view = linalg::lookat_matrix(eye, at, up); const float aspect = m_viewportSize.x / float(m_viewportSize.y); - const float fovRadians = math::radians(m_fov); math::mat4 proj; // Try and get some legroom for ImGuizmo get precision on depth. @@ -1572,7 +1277,10 @@ void Viewport::ui_gizmo() float near = std::max(1e-8f, distanceToSelectedObject * 1e-2f); float far = std::max(1e-6f, distanceToSelectedObject * 1e2f); - if (m_currentCamera == m_perspCamera) { + if (m_currentCamera->subtype() == core::tokens::camera::perspective) { + const float fovRadians = + m_currentCamera->parameterValueAs("fovy").value_or( + math::radians(40.f)); float oneOverTanFov = 1.0f / tan(fovRadians / 2.0f); proj = math::mat4{ {oneOverTanFov / aspect, 0.0f, 0.0f, 0.0f}, @@ -1580,7 +1288,7 @@ void Viewport::ui_gizmo() {0.0f, 0.0f, -(far + near) / (far - near), -1.0f}, {0.0f, 0.0f, -2.0f * far * near / (far - near), 0.0f}, }; - } else if (m_currentCamera == m_orthoCamera) { + } else if (m_currentCamera->subtype() == core::tokens::camera::orthographic) { // The 0.75 factor is to match updateCameraParametersOrthographic const float height = m_arcball->distance() * 0.75f; const float halfHeight = height * 0.5f; diff --git a/tsd/src/tsd/ui/imgui/windows/Viewport.h b/tsd/src/tsd/ui/imgui/windows/Viewport.h index 82367abbd..6de3ca7e8 100644 --- a/tsd/src/tsd/ui/imgui/windows/Viewport.h +++ b/tsd/src/tsd/ui/imgui/windows/Viewport.h @@ -48,11 +48,6 @@ struct Viewport : public Window const anari::Instance *instances = nullptr, size_t count = 0); void setCustomFrameParameter(const char *name, const tsd::core::Any &value); - void setDatabaseCamera(tsd::core::CameraRef cam); - void clearDatabaseCamera(); - void createCameraFromCurrentView(); - void addCameraObjectFromCurrentView(); - private: void saveSettings(tsd::core::DataNode &thisWindowRoot) override; void loadSettings(tsd::core::DataNode &thisWindowRoot) override; @@ -67,14 +62,19 @@ struct Viewport : public Window void updateCamera(bool force = false); void updateImage(); - void applyCameraParameters(tsd::core::Camera *cam); - - void echoCameraConfig(); void ui_menubar(); + void ui_menubar_Device(); + void ui_menubar_Renderer(); + void ui_menubar_Camera(); + void ui_menubar_TransformManipulator(); + void ui_menubar_Viewport(); + void ui_menubar_World(); + void ui_handleInput(); bool ui_picking(); void ui_overlay(); void ui_gizmo(); + bool canShowGizmo() const; int windowFlags() const override; // anari_viewer::Window @@ -92,7 +92,6 @@ struct Viewport : public Window bool m_mouseRotating{false}; bool m_manipulating{false}; bool m_frameCancelled{false}; - bool m_echoCameraConfig{false}; bool m_showOverlay{true}; bool m_showCameraInfo{false}; @@ -107,8 +106,6 @@ struct Viewport : public Window float m_edgeThreshold{0.5f}; bool m_edgeInvert{false}; - float m_fov{40.f}; - // Gizmo state // bool m_enableGizmo{true}; @@ -123,32 +120,18 @@ struct Viewport : public Window // ANARI objects // - anari::DataType m_format{ANARI_UFIXED8_RGBA_SRGB}; - - anari::Extensions m_extensions{}; anari::Device m_device{nullptr}; - anari::Camera m_currentCamera{nullptr}; - anari::Camera m_perspCamera{nullptr}; - anari::Camera m_orthoCamera{nullptr}; - anari::Camera m_omniCamera{nullptr}; - std::vector m_rendererObjects; tsd::core::RendererAppRef m_currentRenderer; // Camera manipulator // + tsd::core::CameraAppRef m_currentCamera; + int m_arcballUp{1}; tsd::rendering::Manipulator m_localArcball; tsd::rendering::Manipulator *m_arcball{nullptr}; tsd::rendering::UpdateToken m_cameraToken{0}; - float m_apertureRadius{0.f}; - float m_focusDistance{1.f}; - - // Database camera state // - - tsd::core::CameraRef m_selectedCamera; - std::unique_ptr m_cameraDelegate; - std::vector m_menuCameraRefs; // Display //