ADD: Floating origin system

This commit is contained in:
2025-12-14 21:37:49 +09:00
parent eb5a04e95a
commit d6ad2bf252
18 changed files with 728 additions and 69 deletions

View File

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

View File

@@ -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<std::mutex> 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<std::mutex> 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)

View File

@@ -11,8 +11,11 @@
#include <thread>
#include <glm/mat4x4.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/vec3.hpp>
#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<LoadedGLTF> scene;

View File

@@ -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<uint64_t>(off), static_cast<uint64_t>(len), w, h });

View File

@@ -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<double>(v.halfExtents.x) &&
std::abs(local.y) <= static_cast<double>(v.halfExtents.y) &&
std::abs(local.z) <= static_cast<double>(v.halfExtents.z))
{
newVolume = static_cast<int>(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;

View File

@@ -4,6 +4,7 @@
#pragma once
#include <core/types.h>
#include <core/world.h>
#include <vector>
#include <string>
#include <unordered_map>
@@ -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);

View File

@@ -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<size_t>(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<size_t>(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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -12,7 +12,16 @@
#include <deque>
#include <vulkan/vulkan.h>
#if __has_include(<vulkan/vk_enum_string_helper.h>)
#include <vulkan/vk_enum_string_helper.h>
#elif __has_include(<vk_enum_string_helper.h>)
#include <vk_enum_string_helper.h>
#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 <vk_mem_alloc.h>

36
src/core/world.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include <glm/vec3.hpp>
#include <cmath>
// 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<float>(local_d.x),
static_cast<float>(local_d.y),
static_cast<float>(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));
}

View File

@@ -36,7 +36,6 @@ public:
RGBufferHandle create_buffer(const RGBufferDesc& desc);
// Pass builder API
struct Pass; // fwd
using RecordCallback = std::function<void(VkCommandBuffer cmd, const class RGPassResources& res, EngineContext* ctx)>;
using BuildCallback = std::function<void(class RGPassBuilder& b, EngineContext* ctx)>;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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_ptr<Mesh
if (!mesh) return;
MeshInstance inst{};
inst.mesh = std::move(mesh);
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);
inst.boundsTypeOverride = boundsType;
dynamicMeshInstances[name] = std::move(inst);
}
@@ -461,7 +491,7 @@ bool SceneManager::getMeshInstanceTransform(const std::string &name, glm::mat4 &
return false;
}
const MeshInstance &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;
}
@@ -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_ptr<Load
scene->debugName.empty() ? "<unnamed>" : 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;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <core/types.h>
#include <core/world.h>
#include <scene/camera.h>
#include <unordered_map>
#include <memory>
@@ -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<MeshAsset> 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<BoundsType> boundsTypeOverride;
@@ -102,6 +106,10 @@ public:
std::optional<BoundsType> 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<LoadedGLTF> 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<PointLight> 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<std::string, std::shared_ptr<LoadedGLTF> > loadedScenes;
// Per-named static glTF scene animation state (independent of instances).

View File

@@ -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;