diff --git a/Readme.md b/Readme.md index 9611550..e45692d 100644 --- a/Readme.md +++ b/Readme.md @@ -16,9 +16,9 @@ Current structure: - SSR - FXAA - Bloom +- Floating origin with double precision coordinate system Work-In-Progress -- [ ] Floating origin with double precision coordinate system - [ ] Planet Rendering ## Build prequsites diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 629893f..21de98b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ add_executable (vulkan_engine main.cpp # core root core/types.h + core/world.h core/config.h core/context.h core/context.cpp diff --git a/src/core/assets/async_loader.cpp b/src/core/assets/async_loader.cpp index 6bebb0a..d770544 100644 --- a/src/core/assets/async_loader.cpp +++ b/src/core/assets/async_loader.cpp @@ -104,6 +104,32 @@ AsyncAssetLoader::JobID AsyncAssetLoader::load_gltf_async(const std::string &sce return id; } +AsyncAssetLoader::JobID AsyncAssetLoader::load_gltf_async(const std::string &scene_name, + const std::string &model_relative_path, + const WorldVec3 &translation_world, + const glm::quat &rotation, + const glm::vec3 &scale, + bool preload_textures) +{ + JobID id = load_gltf_async(scene_name, model_relative_path, glm::mat4(1.0f), preload_textures); + if (id == 0) + { + return 0; + } + + std::lock_guard lock(_jobs_mutex); + auto it = _jobs.find(id); + if (it != _jobs.end() && it->second) + { + Job &job = *it->second; + job.has_world_trs = true; + job.translation_world = translation_world; + job.rotation = rotation; + job.scale = scale; + } + return id; +} + bool AsyncAssetLoader::get_job_status(JobID id, JobState &out_state, float &out_progress, std::string *out_error) { std::lock_guard lock(_jobs_mutex); @@ -234,6 +260,13 @@ void AsyncAssetLoader::pump_main_thread(SceneManager &scene) job->scene->debugName = job->model_relative_path; } scene.addGLTFInstance(job->scene_name, job->scene, job->transform); + if (job->has_world_trs) + { + scene.setGLTFInstanceTRSWorld(job->scene_name, + job->translation_world, + job->rotation, + job->scale); + } // Optionally preload textures (same logic as addGLTFInstance) if (job->preload_textures && _textures && _engine && _engine->_resourceManager) diff --git a/src/core/assets/async_loader.h b/src/core/assets/async_loader.h index 46fd879..04dca8d 100644 --- a/src/core/assets/async_loader.h +++ b/src/core/assets/async_loader.h @@ -11,8 +11,11 @@ #include #include +#include +#include #include "scene/vk_loader.h" +#include "core/world.h" #include "core/assets/texture_cache.h" class VulkanEngine; @@ -41,6 +44,13 @@ public: const glm::mat4 &transform, bool preload_textures = false); + JobID load_gltf_async(const std::string &scene_name, + const std::string &model_relative_path, + const WorldVec3 &translation_world, + const glm::quat &rotation, + const glm::vec3 &scale, + bool preload_textures = false); + bool get_job_status(JobID id, JobState &out_state, float &out_progress, std::string *out_error = nullptr); // Main-thread integration: commit completed jobs into the SceneManager. @@ -66,6 +76,10 @@ private: std::string scene_name; std::string model_relative_path; glm::mat4 transform{1.0f}; + bool has_world_trs{false}; + WorldVec3 translation_world{0.0, 0.0, 0.0}; + glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f}; + glm::vec3 scale{1.0f}; bool preload_textures{false}; std::shared_ptr scene; diff --git a/src/core/assets/texture_cache.cpp b/src/core/assets/texture_cache.cpp index ee8d922..2e195db 100644 --- a/src/core/assets/texture_cache.cpp +++ b/src/core/assets/texture_cache.cpp @@ -533,7 +533,7 @@ void TextureCache::worker_loop() { ktx_size_t off = 0, len = 0; ktxTexture_GetImageOffset(ktxTexture(ktex), mip, 0, 0, &off); - ktxTexture_GetImageSize(ktxTexture(ktex), mip, &len); + len = ktxTexture_GetImageSize(ktxTexture(ktex), mip); uint32_t w = std::max(1u, baseW >> mip); uint32_t h = std::max(1u, baseH >> mip); out.ktx.levels.push_back({ static_cast(off), static_cast(len), w, h }); diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 997abab..8732058 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -587,6 +587,36 @@ uint32_t VulkanEngine::loadGLTFAsync(const std::string &sceneName, return _asyncLoader->load_gltf_async(sceneName, resolvedPath, transform, preloadTextures); } +uint32_t VulkanEngine::loadGLTFAsync(const std::string &sceneName, + const std::string &modelRelativePath, + const WorldVec3 &translationWorld, + const glm::quat &rotation, + const glm::vec3 &scale, + bool preloadTextures) +{ + if (!_asyncLoader || !_assetManager || !_sceneManager) + { + return 0; + } + + const std::string resolvedPath = _assetManager->modelPath(modelRelativePath); + if (!file_exists_nothrow(resolvedPath)) + { + fmt::println("[Engine] Failed to enqueue async glTF load for scene '{}' – model file not found (requested='{}', resolved='{}')", + sceneName, + modelRelativePath, + resolvedPath); + return 0; + } + + return _asyncLoader->load_gltf_async(sceneName, + resolvedPath, + translationWorld, + rotation, + scale, + preloadTextures); +} + void VulkanEngine::preloadInstanceTextures(const std::string &instanceName) { if (!_textureCache || !_sceneManager) @@ -746,16 +776,16 @@ void VulkanEngine::draw() // Update IBL based on camera position and user-defined reflection volumes. if (_iblManager && _sceneManager) { - glm::vec3 camPos = _sceneManager->getMainCamera().position; + WorldVec3 camPosWorld = _sceneManager->getMainCamera().position_world; int newVolume = -1; for (size_t i = 0; i < _iblVolumes.size(); ++i) { const IBLVolume &v = _iblVolumes[i]; if (!v.enabled) continue; - glm::vec3 local = camPos - v.center; - if (std::abs(local.x) <= v.halfExtents.x && - std::abs(local.y) <= v.halfExtents.y && - std::abs(local.z) <= v.halfExtents.z) + WorldVec3 local = camPosWorld - v.center_world; + if (std::abs(local.x) <= static_cast(v.halfExtents.x) && + std::abs(local.y) <= static_cast(v.halfExtents.y) && + std::abs(local.z) <= static_cast(v.halfExtents.z)) { newVolume = static_cast(i); break; @@ -800,7 +830,7 @@ void VulkanEngine::draw() if (_sceneManager && _mousePosPixels.x >= 0.0f && _mousePosPixels.y >= 0.0f) { RenderObject hoverObj{}; - glm::vec3 hoverPos{}; + WorldVec3 hoverPos{}; if (_sceneManager->pick(_mousePosPixels, hoverObj, hoverPos)) { _hoverPick.mesh = hoverObj.sourceMesh; @@ -1225,7 +1255,7 @@ void VulkanEngine::run() if (_sceneManager) { RenderObject hitObject{}; - glm::vec3 hitPos{}; + WorldVec3 hitPos{}; if (_sceneManager->pick(releasePos, hitObject, hitPos)) { _lastPick.mesh = hitObject.sourceMesh; @@ -1269,8 +1299,8 @@ void VulkanEngine::run() info.ownerType = obj.ownerType; info.ownerName = obj.ownerName; // Use bounds origin transformed to world as a representative point. - glm::vec3 centerWorld = glm::vec3(obj.transform * glm::vec4(obj.bounds.origin, 1.0f)); - info.worldPos = centerWorld; + glm::vec3 centerLocal = glm::vec3(obj.transform * glm::vec4(obj.bounds.origin, 1.0f)); + info.worldPos = local_to_world(centerLocal, _sceneManager->get_world_origin()); info.worldTransform = obj.transform; info.firstIndex = obj.firstIndex; info.indexCount = obj.indexCount; @@ -1365,7 +1395,8 @@ void VulkanEngine::run() if (_sceneManager->resolveObjectID(pickedID, picked)) { // Fallback hit position: object origin in world space (can refine later) - glm::vec3 fallbackPos = glm::vec3(picked.transform[3]); + glm::vec3 fallbackLocal = glm::vec3(picked.transform[3]); + WorldVec3 fallbackPos = local_to_world(fallbackLocal, _sceneManager->get_world_origin()); _lastPick.mesh = picked.sourceMesh; _lastPick.scene = picked.sourceScene; _lastPick.node = picked.sourceNode; diff --git a/src/core/engine.h b/src/core/engine.h index ed3adcc..08734d7 100644 --- a/src/core/engine.h +++ b/src/core/engine.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -123,7 +124,7 @@ public: // Simple world-space IBL reflection volumes (axis-aligned boxes). struct IBLVolume { - glm::vec3 center{0.0f, 0.0f, 0.0f}; + WorldVec3 center_world{0.0, 0.0, 0.0}; glm::vec3 halfExtents{10.0f, 10.0f, 10.0f}; IBLPaths paths{}; // HDRI paths for this volume bool enabled{true}; @@ -149,7 +150,7 @@ public: Node *node = nullptr; RenderObject::OwnerType ownerType = RenderObject::OwnerType::None; std::string ownerName; - glm::vec3 worldPos{0.0f}; + WorldVec3 worldPos{0.0, 0.0, 0.0}; glm::mat4 worldTransform{1.0f}; uint32_t indexCount = 0; uint32_t firstIndex = 0; @@ -242,6 +243,13 @@ public: const glm::mat4 &transform = glm::mat4(1.f), bool preloadTextures = false); + uint32_t loadGLTFAsync(const std::string &sceneName, + const std::string &modelRelativePath, + const WorldVec3 &translationWorld, + const glm::quat &rotation, + const glm::vec3 &scale, + bool preloadTextures = false); + // Preload textures for an already-loaded scene instance so they are // available before the object becomes visible (visibility-driven loading). void preloadInstanceTextures(const std::string &instanceName); diff --git a/src/core/engine_ui.cpp b/src/core/engine_ui.cpp index 2b76d7d..181694c 100644 --- a/src/core/engine_ui.cpp +++ b/src/core/engine_ui.cpp @@ -245,7 +245,7 @@ namespace VulkanEngine::IBLVolume vol{}; if (eng->_sceneManager) { - vol.center = eng->_sceneManager->getMainCamera().position; + vol.center_world = eng->_sceneManager->getMainCamera().position_world; } vol.halfExtents = glm::vec3(10.0f, 10.0f, 10.0f); vol.paths = eng->_globalIBLPaths; @@ -259,7 +259,13 @@ namespace ImGui::Separator(); ImGui::Text("Volume %zu", i); ImGui::Checkbox("Enabled", &vol.enabled); - ImGui::InputFloat3("Center", &vol.center.x); + { + double c[3] = {vol.center_world.x, vol.center_world.y, vol.center_world.z}; + if (ImGui::InputScalarN("Center (world)", ImGuiDataType_Double, c, 3, nullptr, nullptr, "%.3f")) + { + vol.center_world = WorldVec3(c[0], c[1], c[2]); + } + } ImGui::InputFloat3("Half Extents", &vol.halfExtents.x); // Simple path editors; store absolute or engine-local paths. @@ -337,6 +343,17 @@ namespace ImGui::Text("Swapchain: %ux%u", scExt.width, scExt.height); ImGui::Text("Draw fmt: %s", string_VkFormat(eng->_swapchainManager->drawImage().imageFormat)); ImGui::Text("Swap fmt: %s", string_VkFormat(eng->_swapchainManager->swapchainImageFormat())); + + if (eng->_sceneManager) + { + ImGui::Separator(); + WorldVec3 origin = eng->_sceneManager->get_world_origin(); + WorldVec3 camWorld = eng->_sceneManager->getMainCamera().position_world; + glm::vec3 camLocal = eng->_sceneManager->get_camera_local_position(); + ImGui::Text("Origin (world): (%.3f, %.3f, %.3f)", origin.x, origin.y, origin.z); + ImGui::Text("Camera (world): (%.3f, %.3f, %.3f)", camWorld.x, camWorld.y, camWorld.z); + ImGui::Text("Camera (local): (%.3f, %.3f, %.3f)", camLocal.x, camLocal.y, camLocal.z); + } } // Texture streaming + budget UI @@ -956,18 +973,18 @@ namespace SceneManager::PointLight pl{}; if (sceneMgr->getPointLight(static_cast(selectedLight), pl)) { - float pos[3] = {pl.position.x, pl.position.y, pl.position.z}; + double pos[3] = {pl.position_world.x, pl.position_world.y, pl.position_world.z}; float col[3] = {pl.color.r, pl.color.g, pl.color.b}; bool changed = false; - changed |= ImGui::InputFloat3("Position", pos); + changed |= ImGui::InputScalarN("Position (world)", ImGuiDataType_Double, pos, 3, nullptr, nullptr, "%.3f"); changed |= ImGui::SliderFloat("Radius", &pl.radius, 0.1f, 1000.0f); changed |= ImGui::ColorEdit3("Color", col); changed |= ImGui::SliderFloat("Intensity", &pl.intensity, 0.0f, 100.0f); if (changed) { - pl.position = glm::vec3(pos[0], pos[1], pos[2]); + pl.position_world = WorldVec3(pos[0], pos[1], pos[2]); pl.color = glm::vec3(col[0], col[1], col[2]); sceneMgr->setPointLight(static_cast(selectedLight), pl); } @@ -983,12 +1000,12 @@ namespace // Controls for adding a new light ImGui::Separator(); ImGui::TextUnformatted("Add point light"); - static float newPos[3] = {0.0f, 1.0f, 0.0f}; + static double newPos[3] = {0.0, 1.0, 0.0}; static float newRadius = 10.0f; static float newColor[3] = {1.0f, 1.0f, 1.0f}; static float newIntensity = 5.0f; - ImGui::InputFloat3("New position", newPos); + ImGui::InputScalarN("New position (world)", ImGuiDataType_Double, newPos, 3, nullptr, nullptr, "%.3f"); ImGui::SliderFloat("New radius", &newRadius, 0.1f, 1000.0f); ImGui::ColorEdit3("New color", newColor); ImGui::SliderFloat("New intensity", &newIntensity, 0.0f, 100.0f); @@ -996,7 +1013,7 @@ namespace if (ImGui::Button("Add point light")) { SceneManager::PointLight pl{}; - pl.position = glm::vec3(newPos[0], newPos[1], newPos[2]); + pl.position_world = WorldVec3(newPos[0], newPos[1], newPos[2]); pl.radius = newRadius; pl.color = glm::vec3(newColor[0], newColor[1], newColor[2]); pl.intensity = newIntensity; @@ -1214,7 +1231,7 @@ namespace if (pick->ownerType == RenderObject::OwnerType::MeshInstance) { - if (sceneMgr->getMeshInstanceTransform(pick->ownerName, targetTransform)) + if (sceneMgr->getMeshInstanceTransformLocal(pick->ownerName, targetTransform)) { target = GizmoTarget::MeshInstance; ImGui::Text("Editing mesh instance: %s", pick->ownerName.c_str()); @@ -1222,7 +1239,7 @@ namespace } else if (pick->ownerType == RenderObject::OwnerType::GLTFInstance) { - if (sceneMgr->getGLTFInstanceTransform(pick->ownerName, targetTransform)) + if (sceneMgr->getGLTFInstanceTransformLocal(pick->ownerName, targetTransform)) { target = GizmoTarget::GLTFInstance; ImGui::Text("Editing glTF instance: %s", pick->ownerName.c_str()); @@ -1270,8 +1287,8 @@ namespace : 1.0f; // Distance from camera to object; clamp to avoid degenerate planes. - glm::vec3 camPos = cam.position; - glm::vec3 objPos = pick->worldPos; + glm::vec3 camPos = sceneMgr->get_camera_local_position(); + glm::vec3 objPos = glm::vec3(targetTransform[3]); float dist = glm::length(objPos - camPos); if (!std::isfinite(dist) || dist <= 0.0f) { @@ -1282,7 +1299,7 @@ namespace float nearPlane = glm::max(0.05f, dist * 0.05f); float farPlane = glm::max(nearPlane * 50.0f, dist * 2.0f); - glm::mat4 view = cam.getViewMatrix(); + glm::mat4 view = cam.getViewMatrix(sceneMgr->get_camera_local_position()); glm::mat4 proj = glm::perspective(fovRad, aspect, nearPlane, farPlane); glm::mat4 before = targetTransform; @@ -1313,10 +1330,10 @@ namespace switch (target) { case GizmoTarget::MeshInstance: - sceneMgr->setMeshInstanceTransform(pick->ownerName, targetTransform); + sceneMgr->setMeshInstanceTransformLocal(pick->ownerName, targetTransform); break; case GizmoTarget::GLTFInstance: - sceneMgr->setGLTFInstanceTransform(pick->ownerName, targetTransform); + sceneMgr->setGLTFInstanceTransformLocal(pick->ownerName, targetTransform); break; default: break; @@ -1324,7 +1341,7 @@ namespace // Keep pick debug info roughly in sync. pick->worldTransform = targetTransform; - pick->worldPos = glm::vec3(targetTransform[3]); + pick->worldPos = local_to_world(glm::vec3(targetTransform[3]), sceneMgr->get_world_origin()); } } } // namespace diff --git a/src/core/game_api.cpp b/src/core/game_api.cpp index 5a24ceb..175911d 100644 --- a/src/core/game_api.cpp +++ b/src/core/game_api.cpp @@ -37,6 +37,25 @@ Transform Transform::from_matrix(const glm::mat4& m) return t; } +glm::mat4 TransformD::to_matrix() const +{ + glm::mat4 T = glm::translate(glm::mat4(1.0f), glm::vec3(position)); + glm::mat4 R = glm::mat4_cast(rotation); + glm::mat4 S = glm::scale(glm::mat4(1.0f), scale); + return T * R * S; +} + +TransformD TransformD::from_matrix(const glm::mat4& m) +{ + TransformD t; + glm::vec3 skew; + glm::vec4 perspective; + glm::vec3 pos{}; + glm::decompose(m, t.scale, t.rotation, pos, skew, perspective); + t.position = glm::dvec3(pos); + return t; +} + // ============================================================================ // Engine Implementation // ============================================================================ @@ -254,7 +273,19 @@ void Engine::set_global_ibl_paths(const IBLPaths& paths) size_t Engine::add_ibl_volume(const IBLVolume& volume) { VulkanEngine::IBLVolume v; - v.center = volume.center; + v.center_world = WorldVec3(volume.center); + v.halfExtents = volume.halfExtents; + v.paths = to_internal_ibl_paths(volume.paths); + v.enabled = volume.enabled; + + _engine->_iblVolumes.push_back(v); + return _engine->_iblVolumes.size() - 1; +} + +size_t Engine::add_ibl_volume(const IBLVolumeD& volume) +{ + VulkanEngine::IBLVolume v; + v.center_world = WorldVec3(volume.center); v.halfExtents = volume.halfExtents; v.paths = to_internal_ibl_paths(volume.paths); v.enabled = volume.enabled; @@ -285,7 +316,19 @@ bool Engine::get_ibl_volume(size_t index, IBLVolume& out) const if (index >= _engine->_iblVolumes.size()) return false; const auto& v = _engine->_iblVolumes[index]; - out.center = v.center; + out.center = glm::vec3(v.center_world); + out.halfExtents = v.halfExtents; + out.paths = from_internal_ibl_paths(v.paths); + out.enabled = v.enabled; + return true; +} + +bool Engine::get_ibl_volume(size_t index, IBLVolumeD& out) const +{ + if (index >= _engine->_iblVolumes.size()) return false; + + const auto& v = _engine->_iblVolumes[index]; + out.center = v.center_world; out.halfExtents = v.halfExtents; out.paths = from_internal_ibl_paths(v.paths); out.enabled = v.enabled; @@ -297,7 +340,19 @@ bool Engine::set_ibl_volume(size_t index, const IBLVolume& volume) if (index >= _engine->_iblVolumes.size()) return false; auto& v = _engine->_iblVolumes[index]; - v.center = volume.center; + v.center_world = WorldVec3(volume.center); + v.halfExtents = volume.halfExtents; + v.paths = to_internal_ibl_paths(volume.paths); + v.enabled = volume.enabled; + return true; +} + +bool Engine::set_ibl_volume(size_t index, const IBLVolumeD& volume) +{ + if (index >= _engine->_iblVolumes.size()) return false; + + auto& v = _engine->_iblVolumes[index]; + v.center_world = WorldVec3(volume.center); v.halfExtents = volume.halfExtents; v.paths = to_internal_ibl_paths(volume.paths); v.enabled = volume.enabled; @@ -332,6 +387,28 @@ bool Engine::add_gltf_instance(const std::string& name, return _engine->addGLTFInstance(name, modelPath, transform.to_matrix(), preloadTextures); } +bool Engine::add_gltf_instance(const std::string& name, + const std::string& modelPath, + const TransformD& transform, + bool preloadTextures) +{ + if (!_engine || !_engine->_sceneManager) + { + return false; + } + + // Add the instance first (GPU resources), then apply the authoritative world transform in double. + if (!_engine->addGLTFInstance(name, modelPath, glm::mat4(1.0f), preloadTextures)) + { + return false; + } + + return _engine->_sceneManager->setGLTFInstanceTRSWorld(name, + WorldVec3(transform.position), + transform.rotation, + transform.scale); +} + uint32_t Engine::add_gltf_instance_async(const std::string& name, const std::string& modelPath, const Transform& transform, @@ -340,6 +417,19 @@ uint32_t Engine::add_gltf_instance_async(const std::string& name, return _engine->loadGLTFAsync(name, modelPath, transform.to_matrix(), preloadTextures); } +uint32_t Engine::add_gltf_instance_async(const std::string& name, + const std::string& modelPath, + const TransformD& transform, + bool preloadTextures) +{ + return _engine->loadGLTFAsync(name, + modelPath, + WorldVec3(transform.position), + transform.rotation, + transform.scale, + preloadTextures); +} + bool Engine::remove_gltf_instance(const std::string& name) { return _engine->_sceneManager ? _engine->_sceneManager->removeGLTFInstance(name) : false; @@ -358,6 +448,23 @@ bool Engine::get_gltf_instance_transform(const std::string& name, Transform& out return false; } +bool Engine::get_gltf_instance_transform(const std::string& name, TransformD& out) const +{ + if (!_engine->_sceneManager) return false; + + WorldVec3 t{}; + glm::quat r{}; + glm::vec3 s{}; + if (_engine->_sceneManager->getGLTFInstanceTRSWorld(name, t, r, s)) + { + out.position = glm::dvec3(t); + out.rotation = r; + out.scale = s; + return true; + } + return false; +} + bool Engine::set_gltf_instance_transform(const std::string& name, const Transform& transform) { return _engine->_sceneManager @@ -365,6 +472,16 @@ bool Engine::set_gltf_instance_transform(const std::string& name, const Transfor : false; } +bool Engine::set_gltf_instance_transform(const std::string& name, const TransformD& transform) +{ + return _engine->_sceneManager + ? _engine->_sceneManager->setGLTFInstanceTRSWorld(name, + WorldVec3(transform.position), + transform.rotation, + transform.scale) + : false; +} + bool Engine::add_primitive_instance(const std::string& name, PrimitiveType type, const Transform& transform) @@ -382,6 +499,32 @@ bool Engine::add_primitive_instance(const std::string& name, return _engine->addPrimitiveInstance(name, geomType, transform.to_matrix()); } +bool Engine::add_primitive_instance(const std::string& name, + PrimitiveType type, + const TransformD& transform) +{ + AssetManager::MeshGeometryDesc::Type geomType; + switch (type) + { + case PrimitiveType::Cube: geomType = AssetManager::MeshGeometryDesc::Type::Cube; break; + case PrimitiveType::Sphere: geomType = AssetManager::MeshGeometryDesc::Type::Sphere; break; + case PrimitiveType::Plane: geomType = AssetManager::MeshGeometryDesc::Type::Plane; break; + case PrimitiveType::Capsule: geomType = AssetManager::MeshGeometryDesc::Type::Capsule; break; + default: return false; + } + + if (!_engine->addPrimitiveInstance(name, geomType, glm::mat4(1.0f))) + { + return false; + } + return _engine->_sceneManager + ? _engine->_sceneManager->setMeshInstanceTRSWorld(name, + WorldVec3(transform.position), + transform.rotation, + transform.scale) + : false; +} + bool Engine::remove_mesh_instance(const std::string& name) { return _engine->_sceneManager ? _engine->_sceneManager->removeMeshInstance(name) : false; @@ -400,6 +543,23 @@ bool Engine::get_mesh_instance_transform(const std::string& name, Transform& out return false; } +bool Engine::get_mesh_instance_transform(const std::string& name, TransformD& out) const +{ + if (!_engine->_sceneManager) return false; + + WorldVec3 t{}; + glm::quat r{}; + glm::vec3 s{}; + if (_engine->_sceneManager->getMeshInstanceTRSWorld(name, t, r, s)) + { + out.position = glm::dvec3(t); + out.rotation = r; + out.scale = s; + return true; + } + return false; +} + bool Engine::set_mesh_instance_transform(const std::string& name, const Transform& transform) { return _engine->_sceneManager @@ -407,6 +567,16 @@ bool Engine::set_mesh_instance_transform(const std::string& name, const Transfor : false; } +bool Engine::set_mesh_instance_transform(const std::string& name, const TransformD& transform) +{ + return _engine->_sceneManager + ? _engine->_sceneManager->setMeshInstanceTRSWorld(name, + WorldVec3(transform.position), + transform.rotation, + transform.scale) + : false; +} + void Engine::preload_instance_textures(const std::string& name) { _engine->preloadInstanceTextures(name); @@ -477,7 +647,22 @@ size_t Engine::add_point_light(const PointLight& light) if (!_engine->_sceneManager) return 0; SceneManager::PointLight pl; - pl.position = light.position; + pl.position_world = WorldVec3(light.position); + pl.radius = light.radius; + pl.color = light.color; + pl.intensity = light.intensity; + + size_t idx = _engine->_sceneManager->getPointLightCount(); + _engine->_sceneManager->addPointLight(pl); + return idx; +} + +size_t Engine::add_point_light(const PointLightD& light) +{ + if (!_engine->_sceneManager) return 0; + + SceneManager::PointLight pl; + pl.position_world = WorldVec3(light.position); pl.radius = light.radius; pl.color = light.color; pl.intensity = light.intensity; @@ -499,7 +684,23 @@ bool Engine::get_point_light(size_t index, PointLight& out) const SceneManager::PointLight pl; if (_engine->_sceneManager->getPointLight(index, pl)) { - out.position = pl.position; + out.position = glm::vec3(pl.position_world); + out.radius = pl.radius; + out.color = pl.color; + out.intensity = pl.intensity; + return true; + } + return false; +} + +bool Engine::get_point_light(size_t index, PointLightD& out) const +{ + if (!_engine->_sceneManager) return false; + + SceneManager::PointLight pl; + if (_engine->_sceneManager->getPointLight(index, pl)) + { + out.position = pl.position_world; out.radius = pl.radius; out.color = pl.color; out.intensity = pl.intensity; @@ -513,7 +714,20 @@ bool Engine::set_point_light(size_t index, const PointLight& light) if (!_engine->_sceneManager) return false; SceneManager::PointLight pl; - pl.position = light.position; + pl.position_world = WorldVec3(light.position); + pl.radius = light.radius; + pl.color = light.color; + pl.intensity = light.intensity; + + return _engine->_sceneManager->setPointLight(index, pl); +} + +bool Engine::set_point_light(size_t index, const PointLightD& light) +{ + if (!_engine->_sceneManager) return false; + + SceneManager::PointLight pl; + pl.position_world = WorldVec3(light.position); pl.radius = light.radius; pl.color = light.color; pl.intensity = light.intensity; @@ -748,7 +962,7 @@ void Engine::set_camera_position(const glm::vec3& position) { if (_engine->_sceneManager) { - _engine->_sceneManager->getMainCamera().position = position; + _engine->_sceneManager->getMainCamera().position_world = WorldVec3(position); } } @@ -756,11 +970,28 @@ glm::vec3 Engine::get_camera_position() const { if (_engine->_sceneManager) { - return _engine->_sceneManager->getMainCamera().position; + return glm::vec3(_engine->_sceneManager->getMainCamera().position_world); } return glm::vec3(0.0f); } +void Engine::set_camera_position(const glm::dvec3& position) +{ + if (_engine->_sceneManager) + { + _engine->_sceneManager->getMainCamera().position_world = position; + } +} + +glm::dvec3 Engine::get_camera_position_d() const +{ + if (_engine->_sceneManager) + { + return _engine->_sceneManager->getMainCamera().position_world; + } + return glm::dvec3(0.0); +} + void Engine::set_camera_rotation(float pitch, float yaw) { if (_engine->_sceneManager) @@ -821,7 +1052,7 @@ void Engine::camera_look_at(const glm::vec3& target) if (!_engine->_sceneManager) return; Camera& cam = _engine->_sceneManager->getMainCamera(); - glm::vec3 dir = glm::normalize(target - cam.position); + glm::vec3 dir = glm::normalize(target - glm::vec3(cam.position_world)); // For a -Z forward convention, build a quaternion that rotates -Z into dir. // Use glm's lookAt-style helper via matrices, then convert to a quaternion. @@ -843,6 +1074,33 @@ void Engine::camera_look_at(const glm::vec3& target) cam.orientation = glm::quat_cast(rot); } +void Engine::camera_look_at(const glm::dvec3& target) +{ + if (!_engine->_sceneManager) return; + + Camera& cam = _engine->_sceneManager->getMainCamera(); + glm::dvec3 dirD = glm::normalize(target - cam.position_world); + glm::vec3 dir = glm::normalize(glm::vec3(dirD)); + + // For a -Z forward convention, build a quaternion that rotates -Z into dir. + glm::vec3 up(0.0f, 1.0f, 0.0f); + if (glm::length2(glm::cross(dir, up)) < 1e-6f) + { + up = glm::vec3(0.0f, 0.0f, 1.0f); + } + + glm::vec3 f = dir; + glm::vec3 r = glm::normalize(glm::cross(up, f)); + glm::vec3 u = glm::cross(f, r); + + glm::mat3 rot; + rot[0] = r; + rot[1] = u; + rot[2] = -f; // -Z forward + + cam.orientation = glm::quat_cast(rot); +} + // ---------------------------------------------------------------------------- // Rendering // ---------------------------------------------------------------------------- @@ -904,6 +1162,15 @@ Engine::PickResult Engine::get_last_pick() const PickResult r; r.valid = _engine->_lastPick.valid; r.ownerName = _engine->_lastPick.ownerName; + r.worldPosition = glm::vec3(_engine->_lastPick.worldPos); + return r; +} + +Engine::PickResultD Engine::get_last_pick_d() const +{ + PickResultD r; + r.valid = _engine->_lastPick.valid; + r.ownerName = _engine->_lastPick.ownerName; r.worldPosition = _engine->_lastPick.worldPos; return r; } diff --git a/src/core/game_api.h b/src/core/game_api.h index c117c22..307b6c2 100644 --- a/src/core/game_api.h +++ b/src/core/game_api.h @@ -60,6 +60,15 @@ struct PointLight float intensity{1.0f}; }; +// Double-precision world-space point light data (position only). +struct PointLightD +{ + glm::dvec3 position{0.0}; + float radius{10.0f}; + glm::vec3 color{1.0f}; + float intensity{1.0f}; +}; + // IBL (Image-Based Lighting) paths struct IBLPaths { @@ -78,6 +87,15 @@ struct IBLVolume bool enabled{true}; }; +// Double-precision world-space IBL volume (center only). +struct IBLVolumeD +{ + glm::dvec3 center{0.0}; + glm::vec3 halfExtents{10.0f}; + IBLPaths paths; + bool enabled{true}; +}; + // Transform decomposition struct Transform { @@ -89,6 +107,17 @@ struct Transform static Transform from_matrix(const glm::mat4& m); }; +// Double-precision world-space transform (position only). +struct TransformD +{ + glm::dvec3 position{0.0}; + glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f}; + glm::vec3 scale{1.0f}; + + glm::mat4 to_matrix() const; + static TransformD from_matrix(const glm::mat4& m); +}; + // Engine statistics (read-only) struct Stats { @@ -174,6 +203,7 @@ public: // Add a local IBL volume (returns volume index) size_t add_ibl_volume(const IBLVolume& volume); + size_t add_ibl_volume(const IBLVolumeD& volume); // Remove IBL volume by index bool remove_ibl_volume(size_t index); @@ -181,6 +211,8 @@ public: // Get/set IBL volume properties bool get_ibl_volume(size_t index, IBLVolume& out) const; bool set_ibl_volume(size_t index, const IBLVolume& volume); + bool get_ibl_volume(size_t index, IBLVolumeD& out) const; + bool set_ibl_volume(size_t index, const IBLVolumeD& volume); // Get current active IBL volume index (-1 = global) int get_active_ibl_volume() const; @@ -200,12 +232,20 @@ public: const std::string& modelPath, const Transform& transform = {}, bool preloadTextures = true); + bool add_gltf_instance(const std::string& name, + const std::string& modelPath, + const TransformD& transform, + bool preloadTextures = true); // Add glTF model asynchronously (returns job ID, 0 on failure) uint32_t add_gltf_instance_async(const std::string& name, const std::string& modelPath, const Transform& transform = {}, bool preloadTextures = true); + uint32_t add_gltf_instance_async(const std::string& name, + const std::string& modelPath, + const TransformD& transform, + bool preloadTextures = true); // Remove glTF instance bool remove_gltf_instance(const std::string& name); @@ -213,11 +253,16 @@ public: // Get/set glTF instance transform bool get_gltf_instance_transform(const std::string& name, Transform& out) const; bool set_gltf_instance_transform(const std::string& name, const Transform& transform); + bool get_gltf_instance_transform(const std::string& name, TransformD& out) const; + bool set_gltf_instance_transform(const std::string& name, const TransformD& transform); // Add primitive mesh instance bool add_primitive_instance(const std::string& name, PrimitiveType type, const Transform& transform = {}); + bool add_primitive_instance(const std::string& name, + PrimitiveType type, + const TransformD& transform); // Remove mesh instance (primitives or custom meshes) bool remove_mesh_instance(const std::string& name); @@ -225,6 +270,8 @@ public: // Get/set mesh instance transform bool get_mesh_instance_transform(const std::string& name, Transform& out) const; bool set_mesh_instance_transform(const std::string& name, const Transform& transform); + bool get_mesh_instance_transform(const std::string& name, TransformD& out) const; + bool set_mesh_instance_transform(const std::string& name, const TransformD& transform); // Preload textures for an instance (useful before it becomes visible) void preload_instance_textures(const std::string& name); @@ -256,6 +303,7 @@ public: // Add point light (returns index) size_t add_point_light(const PointLight& light); + size_t add_point_light(const PointLightD& light); // Remove point light by index bool remove_point_light(size_t index); @@ -263,6 +311,8 @@ public: // Get/set point light properties bool get_point_light(size_t index, PointLight& out) const; bool set_point_light(size_t index, const PointLight& light); + bool get_point_light(size_t index, PointLightD& out) const; + bool set_point_light(size_t index, const PointLightD& light); // Get point light count size_t get_point_light_count() const; @@ -322,6 +372,8 @@ public: void set_camera_position(const glm::vec3& position); glm::vec3 get_camera_position() const; + void set_camera_position(const glm::dvec3& position); + glm::dvec3 get_camera_position_d() const; void set_camera_rotation(float pitch, float yaw); void get_camera_rotation(float& pitch, float& yaw) const; @@ -331,6 +383,7 @@ public: // Look at a target position void camera_look_at(const glm::vec3& target); + void camera_look_at(const glm::dvec3& target); // ------------------------------------------------------------------------ // Rendering @@ -363,8 +416,16 @@ public: glm::vec3 worldPosition{0.0f}; }; + struct PickResultD + { + bool valid{false}; + std::string ownerName; + glm::dvec3 worldPosition{0.0}; + }; + // Get last click selection result PickResult get_last_pick() const; + PickResultD get_last_pick_d() const; // Enable/disable ID buffer picking (vs CPU raycast) void set_use_id_buffer_picking(bool use); diff --git a/src/core/types.h b/src/core/types.h index 081cc19..6425b34 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -12,7 +12,16 @@ #include #include +#if __has_include() #include +#elif __has_include() +#include +#else +// Fallback for environments without Vulkan-Hpp's enum string helpers. +// This keeps the build working; enum names may not be available. +inline const char *string_VkResult(VkResult) { return "VkResult"; } +inline const char *string_VkFormat(VkFormat) { return "VkFormat"; } +#endif #include diff --git a/src/core/world.h b/src/core/world.h new file mode 100644 index 0000000..cc37865 --- /dev/null +++ b/src/core/world.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include + +// Authoritative world-space coordinates are stored as double precision. +using WorldVec3 = glm::dvec3; + +inline glm::vec3 world_to_local(const WorldVec3 &world, const WorldVec3 &origin_world) +{ + const WorldVec3 local_d = world - origin_world; + return glm::vec3(static_cast(local_d.x), + static_cast(local_d.y), + static_cast(local_d.z)); +} + +inline WorldVec3 local_to_world(const glm::vec3 &local, const WorldVec3 &origin_world) +{ + return origin_world + WorldVec3(local); +} + +inline WorldVec3 snap_world(const WorldVec3 &p, double grid_size) +{ + if (grid_size <= 0.0) + { + return p; + } + + auto snap = [grid_size](double v) { + return std::round(v / grid_size) * grid_size; + }; + + return WorldVec3(snap(p.x), snap(p.y), snap(p.z)); +} + diff --git a/src/render/graph/graph.h b/src/render/graph/graph.h index 932ec6f..44ed42e 100644 --- a/src/render/graph/graph.h +++ b/src/render/graph/graph.h @@ -36,7 +36,6 @@ public: RGBufferHandle create_buffer(const RGBufferDesc& desc); // Pass builder API -struct Pass; // fwd using RecordCallback = std::function; using BuildCallback = std::function; diff --git a/src/scene/camera.cpp b/src/scene/camera.cpp index a801b4d..8a30eac 100644 --- a/src/scene/camera.cpp +++ b/src/scene/camera.cpp @@ -8,7 +8,8 @@ void Camera::update() { glm::mat4 cameraRotation = getRotationMatrix(); - position += glm::vec3(cameraRotation * glm::vec4(velocity * moveSpeed, 0.f)); + glm::vec3 delta = glm::vec3(cameraRotation * glm::vec4(velocity * moveSpeed, 0.f)); + position_world += glm::dvec3(delta); } void Camera::processSDLEvent(SDL_Event& e) @@ -71,17 +72,17 @@ void Camera::processSDLEvent(SDL_Event& e) } } -glm::mat4 Camera::getViewMatrix() +glm::mat4 Camera::getViewMatrix(const glm::vec3 &position_local) const { // to create a correct model view, we need to move the world in opposite // direction to the camera // so we will create the camera model matrix and invert - glm::mat4 cameraTranslation = glm::translate(glm::mat4(1.f), position); + glm::mat4 cameraTranslation = glm::translate(glm::mat4(1.f), position_local); glm::mat4 cameraRotation = getRotationMatrix(); return glm::inverse(cameraTranslation * cameraRotation); } -glm::mat4 Camera::getRotationMatrix() +glm::mat4 Camera::getRotationMatrix() const { // Use the stored quaternion orientation directly. return glm::toMat4(orientation); diff --git a/src/scene/camera.h b/src/scene/camera.h index dabed92..0b90583 100644 --- a/src/scene/camera.h +++ b/src/scene/camera.h @@ -7,8 +7,8 @@ class Camera { public: - glm::vec3 velocity; - glm::vec3 position; + glm::vec3 velocity{0.0f, 0.0f, 0.0f}; + glm::dvec3 position_world{0.0, 0.0, 0.0}; // Orientation stored as a quaternion (local -> world). glm::quat orientation { 1.0f, 0.0f, 0.0f, 0.0f }; @@ -20,8 +20,8 @@ public: // Field of view in degrees for projection float fovDegrees { 50.f }; - glm::mat4 getViewMatrix(); - glm::mat4 getRotationMatrix(); + glm::mat4 getViewMatrix(const glm::vec3 &position_local) const; + glm::mat4 getRotationMatrix() const; void processSDLEvent(SDL_Event& e); diff --git a/src/scene/vk_scene.cpp b/src/scene/vk_scene.cpp index e25c37c..336c764 100644 --- a/src/scene/vk_scene.cpp +++ b/src/scene/vk_scene.cpp @@ -72,7 +72,7 @@ void SceneManager::init(EngineContext *context) _context = context; mainCamera.velocity = glm::vec3(0.f); - mainCamera.position = glm::vec3(30.f, -00.f, 85.f); + mainCamera.position_world = WorldVec3(30.0, 0.0, 85.0); mainCamera.orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); sceneData.ambientColor = glm::vec4(0.1f, 0.1f, 0.1f, 1.0f); @@ -81,18 +81,20 @@ void SceneManager::init(EngineContext *context) // Seed a couple of default point lights for quick testing. PointLight warmKey{}; - warmKey.position = glm::vec3(0.0f, 0.0f, 0.0f); + warmKey.position_world = WorldVec3(0.0, 0.0, 0.0); warmKey.radius = 25.0f; warmKey.color = glm::vec3(1.0f, 0.95f, 0.8f); warmKey.intensity = 15.0f; addPointLight(warmKey); PointLight coolFill{}; - coolFill.position = glm::vec3(-10.0f, 4.0f, 10.0f); + coolFill.position_world = WorldVec3(-10.0, 4.0, 10.0); coolFill.radius = 20.0f; coolFill.color = glm::vec3(0.6f, 0.7f, 1.0f); coolFill.intensity = 10.0f; addPointLight(coolFill); + + _camera_position_local = world_to_local(mainCamera.position_world, _origin_world); } void SceneManager::update_scene() @@ -126,6 +128,24 @@ void SceneManager::update_scene() mainCamera.update(); + // Floating origin: keep render-local coordinates near (0,0,0) by shifting the origin + // when the camera drifts too far in world space. + if (_floating_origin_recenter_threshold > 0.0) + { + const WorldVec3 d = mainCamera.position_world - _origin_world; + const double threshold2 = _floating_origin_recenter_threshold * _floating_origin_recenter_threshold; + if (glm::length2(d) > threshold2) + { + const WorldVec3 newOrigin = + (_floating_origin_snap_size > 0.0) + ? snap_world(mainCamera.position_world, _floating_origin_snap_size) + : mainCamera.position_world; + _origin_world = newOrigin; + } + } + + _camera_position_local = world_to_local(mainCamera.position_world, _origin_world); + // Simple per-frame dt (seconds) for animations static auto lastFrameTime = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now(); @@ -155,6 +175,11 @@ void SceneManager::update_scene() } }; + // Root transform to shift "world space" float scenes into render-local space. + // Any object that is authored in world coordinates (float) should be offset by -origin. + const glm::mat4 world_to_local_root = + glm::translate(glm::mat4{1.f}, world_to_local(WorldVec3(0.0, 0.0, 0.0), _origin_world)); + // Draw all loaded GLTF scenes (static world), advancing their independent animation states. for (auto &[name, scene] : loadedScenes) { @@ -173,7 +198,7 @@ void SceneManager::update_scene() const size_t opaqueStart = mainDrawContext.OpaqueSurfaces.size(); const size_t transpStart = mainDrawContext.TransparentSurfaces.size(); mainDrawContext.gltfNodeLocalOverrides = nullptr; - scene->Draw(glm::mat4{1.f}, mainDrawContext); + scene->Draw(world_to_local_root, mainDrawContext); mainDrawContext.gltfNodeLocalOverrides = nullptr; tagOwner(RenderObject::OwnerType::StaticGLTF, name, opaqueStart, transpStart); } @@ -203,7 +228,8 @@ void SceneManager::update_scene() { mainDrawContext.gltfNodeLocalOverrides = nullptr; } - glm::mat4 instanceTransform = make_trs_matrix(inst.translation, inst.rotation, inst.scale); + glm::vec3 tLocal = world_to_local(inst.translation_world, _origin_world); + glm::mat4 instanceTransform = make_trs_matrix(tLocal, inst.rotation, inst.scale); inst.scene->Draw(instanceTransform, mainDrawContext); mainDrawContext.gltfNodeLocalOverrides = nullptr; tagOwner(RenderObject::OwnerType::GLTFInstance, kv.first, opaqueStart, transpStart); @@ -216,7 +242,8 @@ void SceneManager::update_scene() { const MeshInstance &inst = kv.second; if (!inst.mesh || inst.mesh->surfaces.empty()) continue; - glm::mat4 instanceTransform = make_trs_matrix(inst.translation, inst.rotation, inst.scale); + glm::vec3 tLocal = world_to_local(inst.translation_world, _origin_world); + glm::mat4 instanceTransform = make_trs_matrix(tLocal, inst.rotation, inst.scale); uint32_t surfaceIndex = 0; for (const auto &surf: inst.mesh->surfaces) { @@ -249,7 +276,7 @@ void SceneManager::update_scene() } } - glm::mat4 view = mainCamera.getViewMatrix(); + glm::mat4 view = mainCamera.getViewMatrix(_camera_position_local); // Use reversed infinite-Z projection (right-handed, -Z forward) to avoid far-plane clipping // on very large scenes. Vulkan clip space is 0..1 (GLM_FORCE_DEPTH_ZERO_TO_ONE) and requires Y flip. auto makeReversedInfinitePerspective = [](float fovyRadians, float aspect, float zNear) { @@ -374,7 +401,8 @@ void SceneManager::update_scene() for (uint32_t i = 0; i < lightCount; ++i) { const PointLight &pl = pointLights[i]; - sceneData.punctualLights[i].position_radius = glm::vec4(pl.position, pl.radius); + glm::vec3 posLocal = world_to_local(pl.position_world, _origin_world); + sceneData.punctualLights[i].position_radius = glm::vec4(posLocal, pl.radius); sceneData.punctualLights[i].color_intensity = glm::vec4(pl.color, pl.intensity); } for (uint32_t i = lightCount; i < kMaxPunctualLights; ++i) @@ -448,7 +476,9 @@ void SceneManager::addMeshInstance(const std::string &name, std::shared_ptrsecond; - outTransform = make_trs_matrix(inst.translation, inst.rotation, inst.scale); + outTransform = make_trs_matrix(glm::vec3(inst.translation_world), inst.rotation, inst.scale); return true; } @@ -473,7 +503,74 @@ bool SceneManager::setMeshInstanceTransform(const std::string &name, const glm:: return false; } MeshInstance &inst = it->second; - decompose_trs_matrix(transform, inst.translation, inst.rotation, inst.scale); + glm::vec3 t{}; + decompose_trs_matrix(transform, t, inst.rotation, inst.scale); + inst.translation_world = WorldVec3(t); + return true; +} + +bool SceneManager::getMeshInstanceTransformLocal(const std::string &name, glm::mat4 &outTransformLocal) const +{ + auto it = dynamicMeshInstances.find(name); + if (it == dynamicMeshInstances.end()) + { + return false; + } + + const MeshInstance &inst = it->second; + glm::vec3 tLocal = world_to_local(inst.translation_world, _origin_world); + outTransformLocal = make_trs_matrix(tLocal, inst.rotation, inst.scale); + return true; +} + +bool SceneManager::setMeshInstanceTransformLocal(const std::string &name, const glm::mat4 &transformLocal) +{ + auto it = dynamicMeshInstances.find(name); + if (it == dynamicMeshInstances.end()) + { + return false; + } + + MeshInstance &inst = it->second; + glm::vec3 tLocal{}; + decompose_trs_matrix(transformLocal, tLocal, inst.rotation, inst.scale); + inst.translation_world = local_to_world(tLocal, _origin_world); + return true; +} + +bool SceneManager::getMeshInstanceTRSWorld(const std::string &name, + WorldVec3 &outTranslationWorld, + glm::quat &outRotation, + glm::vec3 &outScale) const +{ + auto it = dynamicMeshInstances.find(name); + if (it == dynamicMeshInstances.end()) + { + return false; + } + + const MeshInstance &inst = it->second; + outTranslationWorld = inst.translation_world; + outRotation = inst.rotation; + outScale = inst.scale; + return true; +} + +bool SceneManager::setMeshInstanceTRSWorld(const std::string &name, + const WorldVec3 &translationWorld, + const glm::quat &rotation, + const glm::vec3 &scale) +{ + auto it = dynamicMeshInstances.find(name); + if (it == dynamicMeshInstances.end()) + { + return false; + } + + MeshInstance &inst = it->second; + inst.translation_world = translationWorld; + inst.rotation = rotation; + inst.scale = scale; return true; } @@ -496,7 +593,9 @@ void SceneManager::addGLTFInstance(const std::string &name, std::shared_ptrdebugName.empty() ? "" : scene->debugName.c_str()); GLTFInstance inst{}; inst.scene = std::move(scene); - decompose_trs_matrix(transform, inst.translation, inst.rotation, inst.scale); + glm::vec3 t{}; + decompose_trs_matrix(transform, t, inst.rotation, inst.scale); + inst.translation_world = WorldVec3(t); if (inst.scene && !inst.scene->animations.empty()) { inst.animation.activeAnimation = 0; @@ -551,7 +650,7 @@ bool SceneManager::getGLTFInstanceTransform(const std::string &name, glm::mat4 & return false; } const GLTFInstance &inst = it->second; - outTransform = make_trs_matrix(inst.translation, inst.rotation, inst.scale); + outTransform = make_trs_matrix(glm::vec3(inst.translation_world), inst.rotation, inst.scale); return true; } @@ -560,7 +659,74 @@ bool SceneManager::setGLTFInstanceTransform(const std::string &name, const glm:: auto it = dynamicGLTFInstances.find(name); if (it == dynamicGLTFInstances.end()) return false; GLTFInstance &inst = it->second; - decompose_trs_matrix(transform, inst.translation, inst.rotation, inst.scale); + glm::vec3 t{}; + decompose_trs_matrix(transform, t, inst.rotation, inst.scale); + inst.translation_world = WorldVec3(t); + return true; +} + +bool SceneManager::getGLTFInstanceTransformLocal(const std::string &name, glm::mat4 &outTransformLocal) const +{ + auto it = dynamicGLTFInstances.find(name); + if (it == dynamicGLTFInstances.end()) + { + return false; + } + + const GLTFInstance &inst = it->second; + glm::vec3 tLocal = world_to_local(inst.translation_world, _origin_world); + outTransformLocal = make_trs_matrix(tLocal, inst.rotation, inst.scale); + return true; +} + +bool SceneManager::setGLTFInstanceTransformLocal(const std::string &name, const glm::mat4 &transformLocal) +{ + auto it = dynamicGLTFInstances.find(name); + if (it == dynamicGLTFInstances.end()) + { + return false; + } + + GLTFInstance &inst = it->second; + glm::vec3 tLocal{}; + decompose_trs_matrix(transformLocal, tLocal, inst.rotation, inst.scale); + inst.translation_world = local_to_world(tLocal, _origin_world); + return true; +} + +bool SceneManager::getGLTFInstanceTRSWorld(const std::string &name, + WorldVec3 &outTranslationWorld, + glm::quat &outRotation, + glm::vec3 &outScale) const +{ + auto it = dynamicGLTFInstances.find(name); + if (it == dynamicGLTFInstances.end()) + { + return false; + } + + const GLTFInstance &inst = it->second; + outTranslationWorld = inst.translation_world; + outRotation = inst.rotation; + outScale = inst.scale; + return true; +} + +bool SceneManager::setGLTFInstanceTRSWorld(const std::string &name, + const WorldVec3 &translationWorld, + const glm::quat &rotation, + const glm::vec3 &scale) +{ + auto it = dynamicGLTFInstances.find(name); + if (it == dynamicGLTFInstances.end()) + { + return false; + } + + GLTFInstance &inst = it->second; + inst.translation_world = translationWorld; + inst.rotation = rotation; + inst.scale = scale; return true; } diff --git a/src/scene/vk_scene.h b/src/scene/vk_scene.h index 10a47d0..a0f3631 100644 --- a/src/scene/vk_scene.h +++ b/src/scene/vk_scene.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -67,10 +68,13 @@ public: Camera &getMainCamera() { return mainCamera; } + WorldVec3 get_world_origin() const { return _origin_world; } + glm::vec3 get_camera_local_position() const { return _camera_position_local; } + // Ray-pick against current DrawContext using per-surface Bounds. // mousePosPixels is in window coordinates (SDL), origin at top-left. // Returns true if any object was hit, filling outObject and outWorldPos. - bool pick(const glm::vec2 &mousePosPixels, RenderObject &outObject, glm::vec3 &outWorldPos); + bool pick(const glm::vec2 &mousePosPixels, RenderObject &outObject, WorldVec3 &outWorldPos); // Resolve an object ID (from ID buffer) back to the RenderObject for // the most recently built DrawContext. Returns false if not found or id==0. @@ -91,7 +95,7 @@ public: struct MeshInstance { std::shared_ptr mesh; - glm::vec3 translation{0.0f, 0.0f, 0.0f}; + WorldVec3 translation_world{0.0, 0.0, 0.0}; glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f}; glm::vec3 scale{1.0f, 1.0f, 1.0f}; std::optional boundsTypeOverride; @@ -102,6 +106,10 @@ public: std::optional boundsType = {}); bool getMeshInstanceTransform(const std::string &name, glm::mat4 &outTransform); bool setMeshInstanceTransform(const std::string &name, const glm::mat4 &transform); + bool getMeshInstanceTransformLocal(const std::string &name, glm::mat4 &outTransformLocal) const; + bool setMeshInstanceTransformLocal(const std::string &name, const glm::mat4 &transformLocal); + bool getMeshInstanceTRSWorld(const std::string &name, WorldVec3 &outTranslationWorld, glm::quat &outRotation, glm::vec3 &outScale) const; + bool setMeshInstanceTRSWorld(const std::string &name, const WorldVec3 &translationWorld, const glm::quat &rotation, const glm::vec3 &scale); bool removeMeshInstance(const std::string &name); void clearMeshInstances(); @@ -109,7 +117,7 @@ public: struct GLTFInstance { std::shared_ptr scene; - glm::vec3 translation{0.0f, 0.0f, 0.0f}; + WorldVec3 translation_world{0.0, 0.0, 0.0}; glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f}; glm::vec3 scale{1.0f, 1.0f, 1.0f}; LoadedGLTF::AnimationState animation; @@ -123,6 +131,10 @@ public: bool removeGLTFInstance(const std::string &name); bool getGLTFInstanceTransform(const std::string &name, glm::mat4 &outTransform); bool setGLTFInstanceTransform(const std::string &name, const glm::mat4 &transform); + bool getGLTFInstanceTransformLocal(const std::string &name, glm::mat4 &outTransformLocal) const; + bool setGLTFInstanceTransformLocal(const std::string &name, const glm::mat4 &transformLocal); + bool getGLTFInstanceTRSWorld(const std::string &name, WorldVec3 &outTranslationWorld, glm::quat &outRotation, glm::vec3 &outScale) const; + bool setGLTFInstanceTRSWorld(const std::string &name, const WorldVec3 &translationWorld, const glm::quat &rotation, const glm::vec3 &scale); void clearGLTFInstances(); // Per-instance glTF node pose overrides (local-space, layered on top of animation/base TRS). // 'offset' is post-multiplied onto the node's localTransform for this instance only. @@ -147,7 +159,7 @@ public: struct PointLight { - glm::vec3 position; + WorldVec3 position_world; float radius; glm::vec3 color; float intensity; @@ -187,6 +199,10 @@ private: GPUSceneData sceneData = {}; DrawContext mainDrawContext; std::vector pointLights; + WorldVec3 _origin_world{0.0, 0.0, 0.0}; + glm::vec3 _camera_position_local{0.0f, 0.0f, 0.0f}; + double _floating_origin_recenter_threshold = 1000.0; + double _floating_origin_snap_size = 100.0; std::unordered_map > loadedScenes; // Per-named static glTF scene animation state (independent of instances). diff --git a/src/scene/vk_scene_picking.cpp b/src/scene/vk_scene_picking.cpp index 19bd96d..5990f66 100644 --- a/src/scene/vk_scene_picking.cpp +++ b/src/scene/vk_scene_picking.cpp @@ -421,7 +421,7 @@ namespace } } // namespace -bool SceneManager::pick(const glm::vec2 &mousePosPixels, RenderObject &outObject, glm::vec3 &outWorldPos) +bool SceneManager::pick(const glm::vec2 &mousePosPixels, RenderObject &outObject, WorldVec3 &outWorldPos) { if (_context == nullptr) { @@ -473,7 +473,7 @@ bool SceneManager::pick(const glm::vec2 &mousePosPixels, RenderObject &outObject -1.0f); dirCamera = glm::normalize(dirCamera); - glm::vec3 rayOrigin = mainCamera.position; + glm::vec3 rayOrigin = world_to_local(mainCamera.position_world, _origin_world); glm::mat4 camRotation = mainCamera.getRotationMatrix(); glm::vec3 rayDir = glm::normalize(glm::vec3(camRotation * glm::vec4(dirCamera, 0.0f))); @@ -539,7 +539,7 @@ bool SceneManager::pick(const glm::vec2 &mousePosPixels, RenderObject &outObject if (anyHit) { - outWorldPos = bestHitPos; + outWorldPos = local_to_world(bestHitPos, _origin_world); } return anyHit;