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

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