Files
QuaternionEngine/docs/FloatingOrigin.md
2025-12-17 01:43:13 +09:00

6.9 KiB

Floating Origin & Double-Precision Coordinates

Precision-safe coordinate system that stores authoritative world positions in double precision and dynamically shifts the rendering origin to keep local float coordinates near zero.

Problem

Single-precision floats (32-bit) have ~7 significant digits. At large world positions (e.g., 100km from origin), sub-meter precision is lost, causing:

  • Vertex jitter / z-fighting
  • Camera stutter during movement
  • Picking inaccuracy
  • Physics instability

Solution

  1. Double-precision world coordinates: Store authoritative positions as glm::dvec3 (WorldVec3).
  2. Floating origin: Maintain a world-space origin point that follows the camera.
  3. Local-space rendering: Convert world positions to local float coordinates relative to the origin.

This keeps all render-local coordinates within a few hundred meters of (0,0,0), preserving full float precision.

Core Types (src/core/world.h)

// Authoritative world-space coordinates (double precision)
using WorldVec3 = glm::dvec3;

// Convert world position to local float (for rendering)
glm::vec3 world_to_local(const WorldVec3 &world, const WorldVec3 &origin_world);

// Convert local float back to world position
WorldVec3 local_to_world(const glm::vec3 &local, const WorldVec3 &origin_world);

// Snap a world position to a grid (for stable origin placement)
WorldVec3 snap_world(const WorldVec3 &p, double grid_size);

Origin Management (SceneManager)

The SceneManager automatically recenters the origin when the camera drifts too far:

// Private members in SceneManager
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;  // meters
double _floating_origin_snap_size = 100.0;            // grid snap size

Recentering Logic (in update_scene()):

  1. Compute distance from camera to current origin.
  2. If distance exceeds threshold, snap camera position to grid and use as new origin.
  3. Recompute all local positions relative to new origin.
if (_floating_origin_recenter_threshold > 0.0) {
    const WorldVec3 d = mainCamera.position_world - _origin_world;
    if (glm::length2(d) > threshold2) {
        _origin_world = snap_world(mainCamera.position_world, _floating_origin_snap_size);
    }
}
_camera_position_local = world_to_local(mainCamera.position_world, _origin_world);

Coordinate Storage

Component Type Description
Camera::position_world WorldVec3 Camera world position (double)
MeshInstance::translation_world WorldVec3 Instance world position
GLTFInstance::translation_world WorldVec3 glTF instance world position
PointLight::position_world WorldVec3 Light world position
SceneManager::_origin_world WorldVec3 Current floating origin
SceneManager::_camera_position_local glm::vec3 Camera in local/render space

Rendering Pipeline

During update_scene(), all world-space objects are converted to local coordinates:

  1. Static glTF scenes: Apply world_to_local_root transform that offsets by -origin.
  2. Dynamic instances: Convert translation_world to local position, build TRS matrix.
  3. Point lights: Convert position_world to local for GPU upload.
  4. Camera: Store both world and local positions; view matrix uses local.
// Root transform for static world content
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));

// Dynamic instance positioning
glm::vec3 tLocal = world_to_local(inst.translation_world, _origin_world);
glm::mat4 instanceTransform = make_trs_matrix(tLocal, inst.rotation, inst.scale);

GameAPI Double-Precision Variants

The GameAPI exposes both float and double-precision transform types:

// Float-precision (relative/local usage)
struct Transform {
    glm::vec3 position{0.0f};
    glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f};
    glm::vec3 scale{1.0f};
};

// Double-precision (world-space usage)
struct TransformD {
    glm::dvec3 position{0.0};
    glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f};
    glm::vec3 scale{1.0f};
};

// API accepts both variants
bool add_gltf_instance(const std::string& name, 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);

Similarly for PointLight / PointLightD and IBLVolume / IBLVolumeD.

Picking Integration

Picking operates in local coordinates but returns world positions:

// Ray origin in local space
glm::vec3 rayOrigin = world_to_local(mainCamera.position_world, _origin_world);

// ... perform ray-object intersection in local space ...

// Convert hit position back to world
outWorldPos = local_to_world(bestHitPos, _origin_world);

Query Functions

// Get current floating origin (world coordinates)
WorldVec3 SceneManager::get_world_origin() const;

// Get camera position in local/render coordinates
glm::vec3 SceneManager::get_camera_local_position() const;

Configuration Parameters

Parameter Default Description
_floating_origin_recenter_threshold 1000.0 Distance (m) before recentering
_floating_origin_snap_size 100.0 Grid snap size (m) for new origin

Setting _floating_origin_recenter_threshold to 0 or negative disables floating origin.

Best Practices

  1. Store world positions in double: Use WorldVec3 / TransformD for anything that persists or needs absolute positioning.

  2. Work in local space for rendering: All GPU-visible transforms should be in local coordinates.

  3. Convert at boundaries: Convert world↔local at the scene update boundary, not per-frame in shaders.

  4. Use snap size: Grid-aligned origins prevent micro-shifts that could cause subtle jitter.

  5. Consider threshold tuning:

    • Smaller threshold (500m): More frequent recenters, tighter precision
    • Larger threshold (2000m): Fewer recenters, slightly reduced precision at edges

Debugging

The engine UI displays origin and camera positions:

Origin (world): (1200.000, 0.000, 3400.000)
Camera (local): (23.456, 1.234, -12.789)

If you observe jitter at large world coordinates, verify:

  • Positions are stored as WorldVec3, not glm::vec3
  • world_to_local() is applied before GPU upload
  • Origin recentering is enabled (threshold > 0)
  • src/core/world.hWorldVec3 type and conversion functions
  • src/scene/vk_scene.hSceneManager origin management
  • src/scene/vk_scene.cpp — Recentering logic in update_scene()
  • src/scene/camera.hCamera::position_world double-precision position
  • src/core/game_api.hTransform / TransformD API types