You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
World: large-coordinate support — camera-relative rendering / floating-origin to kill far-from-origin jitter #429
All transforms are single-precision glm::vec3 (Scene/Components.hTransformComponent). There is no double-precision path and no floating-origin / origin-rebasing. Far from the origin you get vertex jitter, shadow swim, Z-fighting on distant geometry, and physics instability.
Why it matters (Tier 1)
A Far Cry 2-scale (~50 km²) map visibly degrades toward the edges. This is hard to retrofit late, so the strategy should be chosen early.
Proposed approach
Camera-relative rendering (recommended for OpenGL/PC): keep world positions in f32 but subtract the camera origin before upload so the GPU always works near 0. Cheapest, no ECS change to f64. Usually combined with:
Floating origin / origin rebasing: periodically re-base the world (shift all entities + physics bodies) when the player moves far from origin. Integrates naturally with the existing Scene/Streaming/SceneStreamer region model (rebase on region transition).
Double-precision (dvec3) transforms are the heavyweight fallback — likely unnecessary for PC/FC scale if camera-relative + rebasing is done well.
Acceptance criteria
No visible vertex jitter or shadow swim at the far edge of a 50 km² test map.
Physics stable far from origin.
Streaming + rebasing interoperate without hitches.
Tier 1 — architectural. From the open-world FPS gap analysis.
Landed the camera-relative rendering technique from the proposed approach (branch feature/camera-relative-rendering). Positions stay f32 world-space in the ECS — nothing about TransformComponent or the data format changes. Only the upload path changes: a per-frame render origin is subtracted from every world position/matrix before it reaches the GPU, so the GPU always works with coordinates near 0 and the catastrophic cancellation that causes vertex jitter / shadow swim far from origin disappears.
This issue stays open — it's the umbrella for large-coordinate support. The second independent technique, floating-origin / origin rebasing (periodically re-basing the world as the player travels far, integrating with SceneStreamer), is a separate follow-up slice and is not done here. Double-precision (dvec3) transforms remain explicitly out of scope.
Design
Render origin = camera position snapped to a coarse grid (1024 units). Two payoffs: (1) within the first grid cell the origin is exactly (0,0,0), so the whole feature is a byte-identical no-op near origin — every existing scene and the full test suite are unaffected; (2) the origin only changes on cell crossings, so relative coordinates are frame-stable (no per-frame micro-jitter, CSM texel snapping stays aligned).
One small tested abstraction (Renderer/CameraRelative.h): ComputeRenderOrigin + MakeModelRelative / MakeViewRelative / MakeViewProjectionRelative / MakePositionRelative.
Lighting/fog/shadow math is unchanged because it's all differences (camPos − worldPos, lightPos − worldPos) — subtracting a common origin from both operands is invariant.
Sites migrated (the full audit list + the done/remaining split lives in docs/agent-rules/camera-relative-rendering.md)
Camera UBO — every upload path (primary, the shared UploadCameraUBO, the terrain/voxel inline uploads now routed through it, and the planar-reflection mirror camera). InverseViewProjection inverts the relative VP.
Per-object transforms — shifted at the InstanceBuffer choke points, covering main/depth/quad/terrain/voxel/decal-cube meshes, the instanced loop, all shadow-caster paths, and the GPU frustum culler + legacy occlusion-culler proxies.
Shadows — CSM/spot/point sampling matrices + point positions (ShadowMap::UploadUBO) and the matching render-pass matrices + casters, shifted by the same origin so sampled and rendered depth stay consistent.
Lights — MultiLightUBO + the Forward+ point/spot/sphere-area SSBOs.
Difference-based peripherals — decals, light-probe volumes, fog volumes, underwater fog. Motion vectors preserved (prev-VP made relative to the current origin, matching prev-model).
Verification
Contract tests (CameraRelativeTest.cpp) pin the grid-snap, the NDC-invariance identity, fine-detail preservation at 45 km (the naive world path corrupts nearby-vertex separation; the relative path preserves it), and motion-vector consistency.
Visual before/after (CameraRelativeVisualEvidenceTest.cpp): the same sphere-on-plane-with-shadow scene at 45 km renders identically to the near-origin ground truth with the fix ON (RMSE 2.7) and visibly degraded with it OFF (RMSE 6.9). PNGs inspected directly.
Full suite 4204/4204 pass (no near-origin regression). Real terrain+foliage editor scene renders clean at origin, log error-free.
Debug/bisect lever: Renderer3D::SetCameraRelativeEnabled(false) reverts to exact pre-change behaviour.
Remaining (follow-up slices, documented in the agent-rules doc)
Absolute-world pattern shaders — terrain triplanar tiling / brush / snow-clipmap, water wave-phase, procedural noise, foliage wind. Their geometry already renders stable, but sampling an absolute world position for a pattern (not a difference) offsets far from origin; the fix is a u_RenderOrigin uniform + an + origin add-back per shader. Many are auto-saved by the 1024-multiple snap (e.g. the infinite grid renders clean far from origin already).
2D sprites (Renderer2D) — detail is lost at CPU bake time (transform * local at 45 km), so it needs its own snapped origin applied to the VP + baked vertices.
Floating-origin rebasing — the umbrella's other half.
Follow-up: absolute-world pattern shaders now covered too
Extended this slice to close the "category-2" gap flagged in the first comment — the shaders that use an absolute world position for a pattern (not a lighting difference), which the plain origin-shift would offset far from origin.
Mechanism
Added u_RenderOrigin as the trailing member of the shared CameraMatrices UBO (binding 0, now 288 B — append-only, so shaders that stop earlier are unaffected; the cross-shader std140 layout check still passes). Most pattern shaders declare their own inline camera block, so each got the field added.
Two coordinate spaces (the clean split)
Geometry pattern shaders receive a render-relative interpolated v_WorldPos, so they rebuild absWorldPos = v_WorldPos + u_RenderOrigin for the pattern (never interpolate the absolute position — a 45 km varying re-introduces jitter). Migrated: terrain (5 shaders — triplanar tiling, snow clipmap, brush, snow height), water (Gerstner phase / FFT / foam / normal-map / noise across VS/TCS/TES/FS), foliage (world-anchored wind), infinite grid (world-origin axes + lines).
Depth-reconstruction passes (fog, decals, underwater caustics, motion blur) instead invert the world view-projection: since inverse(VP_world) · (VP_rel · worldPos_rel) == worldPos_absolute, they reconstruct absolute world for free — no per-fragment add-back, and their world-space UBO inputs (WorldToLocal, decal inverse-transform) stay world-space. The only add-back is lifting the relative camera-UBO position for difference terms (volumetric fog).
The full enumerated audit + the "which space is this consumer in" rule are in docs/agent-rules/camera-relative-rendering.md.
Verification
All production shaders compile under the strict Vulkan/SPIR-V target; the cross-shader UBO-layout checks pass; full suite 4206/4206, no near-origin regression.
Still open (documented as follow-ups; #429 stays open)
Terrain geometry far from origin — the terrain quadtree LOD tessellation factors (CPU-side, TerrainUBO::TessFactors, from camera↔patch distance) lose precision at ~45 km, so terrain patches stop drawing. This is independent of the now-fixed terrain pattern shaders and of camera-relative rendering — a separate large-coordinate slice for the terrain LOD system. (Foliage / sphere / plane / grid all draw fine at 45 km; terrain is the outlier.)
2D sprites (Renderer2D) — a clean coordinate shift (not a pattern shader): the CPU-baked world vertices lose detail at bake time, so it needs its own snapped origin on the VP + baked vertices.
Floating-origin / origin rebasing — the umbrella's other half.
Camera-relative rendering shipped in #587 (first slice of this umbrella).
Every world-space GPU upload is now rendered relative to a per-frame render origin (camera position snapped to a 1024 grid), so the GPU works near 0 far from the world origin — vertex jitter and shadow swim are gone. World positions stay f32 in the ECS; the shift is upload-only, and within the first grid cell the origin is exactly (0,0,0) so it's a byte-identical no-op near origin.
Covers the full audit: core matrices/camera/transforms, shadows, multi-light + Forward+ SSBOs, GPU frustum/occlusion cull, light probes; the absolute-world pattern shaders (terrain triplanar/snow, water Gerstner/FFT/foam, foliage wind, infinite grid); and the depth-reconstruction passes (decals, volumetric fog, underwater, motion blur, deferred lighting). A /code-review pass on the initial slice caught and fixed several far-origin-only regressions (deferred lighting space mismatch, foliage identity-model displacement, occlusion-cull VP mismatch, shadow-pass origin, fog light-shaft shadows). Full suite 4206 pass; near-origin render byte-identical. Design + audit list in docs/agent-rules/camera-relative-rendering.md.
Keeping this issue OPEN — remaining large-coordinate work:
Terrain geometry far from origin — the terrain quadtree LOD (TessFactors, CPU camera↔patch distance) loses precision at ~45 km so patches degenerate/cull; independent of the now-fixed terrain pattern shaders. (Foliage / sphere / plane / grid all render fine at 45 km.)
2D sprites (Renderer2D) — done in feat(renderer2d): camera-relative CPU-bake coordinate shift for 2D sprites (#429) #601 (branch feature/large-coord-2d-sprites-429). Unlike the GPU-upload sites above, Renderer2D bakes world vertices on the CPU at Draw* time, so detail was lost at bake time. Fix: Renderer2D keeps its own grid-snapped render origin (per BeginScene, from the camera world position), bakes every vertex relative to it (MakeModelRelative for the transform-matrix Draw*, MakePositionRelative for the explicit-position line/polygon/quad-vertex paths) and uploads a relative VP. No shader change (pure coordinate shift). Own debug lever Renderer2D::SetCameraRelativeEnabled. Pinned by Renderer2DCameraRelativeTest (CPU) + Renderer2DCameraRelativeVisualEvidenceTest (far-origin ON reproduces the origin frame pixel-for-pixel, RMSE 0.000; OFF degrades, RMSE 15.3). UIRenderer / ParticleRenderer inherit the shift for free (they draw through Renderer2D).
Floating-origin / origin rebasing — re-base the world as the player travels far, integrating with SceneStreamer.
(nice-to-have) dedicated far-origin deferred-lighting visual-evidence test; minor non-blocking cleanup noted in the PR (alloc churn at origin, redundant RenderOrigin storage, shared camera-block duplication across inline shader blocks).
Appears resolved by #587 (merged 2026-07-07): camera-relative rendering + absolute-world pattern shaders. The core floating-origin/camera-relative path shipped there (OloEngine/src/OloEngine/Renderer/CameraRelative.h now exists) plus the two-phase GPU-cull transform shift + unified CommandDispatch render-origin source in commit f7584a7. Flagging for closure — verify the far-from-origin jitter is gone in the editor if any residual scope remains.
Correction to my earlier comment: this should stay open.#587 shipped only the camera-relative render path (3D geometry uploaded relative to the camera origin). The umbrella still has open follow-ups:
Terrain geometry precision far from origin (quadtree LOD).
2D sprite (Renderer2D) coordinate shift.
Floating-origin / origin rebasing (periodic world re-base incl. physics bodies, interoperating with SceneStreamer region transitions) — plus the acceptance criteria "physics stable far from origin" and "streaming + rebasing interoperate without hitches."
So #587 closes the first technique, not the issue. Disregard the "flagging for closure" line above.
Advanced by #605 (feature/terrain-camera-relative-429). This delivers the terrain geometry LOD / cull far from origin follow-up.
Root cause (two bugs at once). The terrain quadtree keeps node bounds in terrain-local coordinates (minX·worldSize, no entity transform baked in) and the entity transform places them in the world at draw time — but LOD selection and chunk frustum culling were fed the world camera / view-projection / frustum. Far from origin that failed two ways: (1) it ignored the terrain's world transform, testing the world camera against local bounds, and (2) the world camera↔node subtraction cancelled catastrophically in f32. Symptom: patches degenerating to the coarsest LOD or frustum-culling entirely at ~45 km (TerrainGenerationEvidenceTest even documents the caveat: "a translated transform would cull the terrain out of view"). Note this is not a family-A/B shader issue — the rendered vertices were already correct (terrain transform goes through UploadModelInstance's relative shift; the pattern shaders add u_RenderOrigin back). Only the CPU LOD/cull inputs were in the wrong space.
Fix. Run terrain LOD/cull in terrain-local space, evaluated through the grid-snapped render origin so the large-coordinate subtraction stays precise:
Renderer/CameraRelative.h: new MakeObjectLocalViewProjection / MakeObjectLocalCameraPos helpers (compose the existing relative-VP / relative-model helpers so the origin cancels analytically, not numerically).
Scene.cpp (ProcessScene3DSharedLogic, runtime and editor path): a per-terrain-entity MakeTerrainLocalCullInputs feeds both SelectVisibleChunks (tessellated quadtree) and GetVisibleChunks (non-tess cull). Same IsCameraRelativeEnabled() lever as the rest of World: large-coordinate support — camera-relative rendering / floating-origin to kill far-from-origin jitter #429; a byte-identical no-op near origin. Also corrects a latent bug for any off-origin terrain (e.g. ScriptedTerrainDemo's terrain at x=±150), not just at 45 km.
Tests (55 terrain + camera-relative tests green).TerrainCameraRelativeLODTest.cpp (L1, CPU): local camera/VP reproduce the near-origin reference at 45 km; the quadtree screen-space-error decision is reproduced (same LOD) while the naive world eval collapses it; GPU-gated end-to-end TerrainQuadtree::SelectLOD picks the identical node set near vs far (diverges under the old naive path). TerrainCameraRelativeVisualEvidenceTest.cpp (L8): real tessellated editor pipeline near-origin vs +45 km — far terrain renders identically to origin (RMSE ~5 vs a culled ≈40+), PNGs inspected.
Umbrella stays OPEN. Remaining follow-ups: terrain streaming far from origin (TerrainStreamer builds tiles in tile-local coords but renders with the entity transform only, and never calls SelectVisibleChunks per tile — entangled with the streaming/floating-origin work) and floating-origin / origin rebasing (re-base the world as the player travels far, SceneStreamer interop).
Floating-origin / origin-rebasing — first slice shipped in #612
The third proposed approach on this umbrella (floating-origin / rebasing) now has a first shippable mechanism. The other two-thirds — camera-relative rendering — were already merged (#587 3D, #601 2D sprites, #605 terrain LOD/cull).
Scene::RebaseOrigin(shift) shifts the whole world back toward the origin: root entity transforms (children telescope), Jolt rigid bodies + static terrain + character controllers (JoltScene::ShiftOrigin, velocities/rotations/sleep preserved), and Box2D bodies — atomically on the game thread, then re-propagates world matrices and accumulates the offset (absolute = rebased + GetWorldOrigin()).
Scene::MaybeRebaseOrigin — grid-snapped auto-trigger in UpdateStreaming (physics idle, never races the parallel gameplay scheduler), gated by opt-in WorldOriginSettings (default off → every existing scene byte-identical).
Composes with camera-relative rendering as an orthogonal layer: rebasing moves stored state, camera-relative is a fixed per-frame GPU transform on top; after a rebase the render origin snaps back to (0,0,0).
Verified: unit contract math, Functional physics (velocity preserved across a rebase; resting body stays on a shifted static floor), and an L8 visual-evidence test proving no visible pop across a 45 km→origin rebase (before/after frames RMSE ~1.3).
Rebase the world-space state left outside the first slice: cloth soft-bodies, vehicle/constraint fixed-world anchors, NavMesh, TerrainStreamer per-tile WorldOrigin.
Full 50 km² end-to-end scale validation (no jitter/shadow-swim, physics stable across many rebases, streaming interoperates) + auto-trigger tuning under real traversal.
Umbrella complete. The three-part strategy from this issue's proposed approach shipped:
Camera-relative rendering — shipped earlier (OloEngine/src/OloEngine/Renderer/CameraRelative.h): world positions stay f32 but the camera origin is subtracted before GPU upload, so the GPU always works near 0.
Double precision (dvec3) — deliberately not pursued; camera-relative + rebasing cover PC/Far-Cry scale without an ECS-wide f64 migration, exactly as this issue anticipated.
Acceptance criteria met:
No visible vertex jitter / shadow swim at the far edge of a 50 km² map — camera-relative rendering + rebasing; the visual-evidence test shows RMSE 1.6 before/after a rebase at 50 km (no visible pop).
Physics stable far from origin — rebasing keeps stored coordinates small; Functional physics tests pass across single and many sequential rebases (velocity/rest/anchor continuity).
Streaming + rebasing interoperate without hitches — the terrain streamer is now entity-relative (rebase-free), and RebaseOrigin runs in UpdateStreaming on the game thread with physics idle, before the streamer update.
Closing as completed (tracked continuation #613 also closed; delivery in #612 + #663).
Activity
coderabbitai commentedon Jun 28, 2026
🔗 Related PRs
#400 - fix(net,task): isfinite-guard untrusted float ingestion (network snap… [merged]
🧪 Issue enrichment is currently in open beta.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
drsnuggles8 commentedon Jul 7, 2026
First slice shipped: camera-relative rendering
Landed the camera-relative rendering technique from the proposed approach (branch
feature/camera-relative-rendering). Positions stay f32 world-space in the ECS — nothing aboutTransformComponentor the data format changes. Only the upload path changes: a per-frame render origin is subtracted from every world position/matrix before it reaches the GPU, so the GPU always works with coordinates near 0 and the catastrophic cancellation that causes vertex jitter / shadow swim far from origin disappears.This issue stays open — it's the umbrella for large-coordinate support. The second independent technique, floating-origin / origin rebasing (periodically re-basing the world as the player travels far, integrating with
SceneStreamer), is a separate follow-up slice and is not done here. Double-precision (dvec3) transforms remain explicitly out of scope.Design
(0,0,0), so the whole feature is a byte-identical no-op near origin — every existing scene and the full test suite are unaffected; (2) the origin only changes on cell crossings, so relative coordinates are frame-stable (no per-frame micro-jitter, CSM texel snapping stays aligned).Renderer/CameraRelative.h):ComputeRenderOrigin+MakeModelRelative/MakeViewRelative/MakeViewProjectionRelative/MakePositionRelative.camPos − worldPos,lightPos − worldPos) — subtracting a common origin from both operands is invariant.Sites migrated (the full audit list + the done/remaining split lives in
docs/agent-rules/camera-relative-rendering.md)UploadCameraUBO, the terrain/voxel inline uploads now routed through it, and the planar-reflection mirror camera).InverseViewProjectioninverts the relative VP.InstanceBufferchoke points, covering main/depth/quad/terrain/voxel/decal-cube meshes, the instanced loop, all shadow-caster paths, and the GPU frustum culler + legacy occlusion-culler proxies.ShadowMap::UploadUBO) and the matching render-pass matrices + casters, shifted by the same origin so sampled and rendered depth stay consistent.MultiLightUBO+ the Forward+ point/spot/sphere-area SSBOs.Verification
CameraRelativeTest.cpp) pin the grid-snap, the NDC-invariance identity, fine-detail preservation at 45 km (the naive world path corrupts nearby-vertex separation; the relative path preserves it), and motion-vector consistency.CameraRelativeVisualEvidenceTest.cpp): the same sphere-on-plane-with-shadow scene at 45 km renders identically to the near-origin ground truth with the fix ON (RMSE 2.7) and visibly degraded with it OFF (RMSE 6.9). PNGs inspected directly.Renderer3D::SetCameraRelativeEnabled(false)reverts to exact pre-change behaviour.Remaining (follow-up slices, documented in the agent-rules doc)
u_RenderOriginuniform + an+ originadd-back per shader. Many are auto-saved by the 1024-multiple snap (e.g. the infinite grid renders clean far from origin already).Renderer2D) — detail is lost at CPU bake time (transform * localat 45 km), so it needs its own snapped origin applied to the VP + baked vertices.drsnuggles8 commentedon Jul 7, 2026
Follow-up: absolute-world pattern shaders now covered too
Extended this slice to close the "category-2" gap flagged in the first comment — the shaders that use an absolute world position for a pattern (not a lighting difference), which the plain origin-shift would offset far from origin.
Mechanism
Added
u_RenderOriginas the trailing member of the sharedCameraMatricesUBO (binding 0, now 288 B — append-only, so shaders that stop earlier are unaffected; the cross-shader std140 layout check still passes). Most pattern shaders declare their own inline camera block, so each got the field added.Two coordinate spaces (the clean split)
v_WorldPos, so they rebuildabsWorldPos = v_WorldPos + u_RenderOriginfor the pattern (never interpolate the absolute position — a 45 km varying re-introduces jitter). Migrated: terrain (5 shaders — triplanar tiling, snow clipmap, brush, snow height), water (Gerstner phase / FFT / foam / normal-map / noise across VS/TCS/TES/FS), foliage (world-anchored wind), infinite grid (world-origin axes + lines).inverse(VP_world) · (VP_rel · worldPos_rel) == worldPos_absolute, they reconstruct absolute world for free — no per-fragment add-back, and their world-space UBO inputs (WorldToLocal, decal inverse-transform) stay world-space. The only add-back is lifting the relative camera-UBO position for difference terms (volumetric fog).The full enumerated audit + the "which space is this consumer in" rule are in
docs/agent-rules/camera-relative-rendering.md.Verification
v_WorldPos + O== the pre-World: large-coordinate support — camera-relative rendering / floating-origin to kill far-from-origin jitter #429 absolute world position).Still open (documented as follow-ups; #429 stays open)
TerrainUBO::TessFactors, from camera↔patch distance) lose precision at ~45 km, so terrain patches stop drawing. This is independent of the now-fixed terrain pattern shaders and of camera-relative rendering — a separate large-coordinate slice for the terrain LOD system. (Foliage / sphere / plane / grid all draw fine at 45 km; terrain is the outlier.)Renderer2D) — a clean coordinate shift (not a pattern shader): the CPU-baked world vertices lose detail at bake time, so it needs its own snapped origin on the VP + baked vertices.drsnuggles8 commentedon Jul 7, 2026
Camera-relative rendering shipped in #587 (first slice of this umbrella).
Every world-space GPU upload is now rendered relative to a per-frame render origin (camera position snapped to a 1024 grid), so the GPU works near 0 far from the world origin — vertex jitter and shadow swim are gone. World positions stay
f32in the ECS; the shift is upload-only, and within the first grid cell the origin is exactly (0,0,0) so it's a byte-identical no-op near origin.Covers the full audit: core matrices/camera/transforms, shadows, multi-light + Forward+ SSBOs, GPU frustum/occlusion cull, light probes; the absolute-world pattern shaders (terrain triplanar/snow, water Gerstner/FFT/foam, foliage wind, infinite grid); and the depth-reconstruction passes (decals, volumetric fog, underwater, motion blur, deferred lighting). A
/code-reviewpass on the initial slice caught and fixed several far-origin-only regressions (deferred lighting space mismatch, foliage identity-model displacement, occlusion-cull VP mismatch, shadow-pass origin, fog light-shaft shadows). Full suite 4206 pass; near-origin render byte-identical. Design + audit list indocs/agent-rules/camera-relative-rendering.md.Keeping this issue OPEN — remaining large-coordinate work:
TessFactors, CPU camera↔patch distance) loses precision at ~45 km so patches degenerate/cull; independent of the now-fixed terrain pattern shaders. (Foliage / sphere / plane / grid all render fine at 45 km.)Renderer2D) — done in feat(renderer2d): camera-relative CPU-bake coordinate shift for 2D sprites (#429) #601 (branchfeature/large-coord-2d-sprites-429). Unlike the GPU-upload sites above,Renderer2Dbakes world vertices on the CPU atDraw*time, so detail was lost at bake time. Fix:Renderer2Dkeeps its own grid-snapped render origin (perBeginScene, from the camera world position), bakes every vertex relative to it (MakeModelRelativefor the transform-matrixDraw*,MakePositionRelativefor the explicit-position line/polygon/quad-vertex paths) and uploads a relative VP. No shader change (pure coordinate shift). Own debug leverRenderer2D::SetCameraRelativeEnabled. Pinned byRenderer2DCameraRelativeTest(CPU) +Renderer2DCameraRelativeVisualEvidenceTest(far-origin ON reproduces the origin frame pixel-for-pixel, RMSE 0.000; OFF degrades, RMSE 15.3).UIRenderer/ParticleRendererinherit the shift for free (they draw throughRenderer2D).SceneStreamer.RenderOriginstorage, shared camera-block duplication across inline shader blocks).fix(renderer): shift two-phase GPU cull transforms + unify CommandDis…
Merge pull request #587 from drsnuggles8/feature/camera-relative-rend…
drsnuggles8 commentedon Jul 9, 2026
Advanced by #587 (merged 2026-07-09); worktree removed. This delivered the first camera-relative rendering slice (per-frame grid-snapped render origin; families A/B/B′ pattern + depth-reconstruction shaders shifted). Umbrella stays open — remaining follow-ups: terrain geometry precision far from origin (quadtree LOD), 2D sprites (
Renderer2D) coordinate shift, and floating-origin / origin rebasing.drsnuggles8 commentedon Jul 9, 2026
Appears resolved by #587 (merged 2026-07-07): camera-relative rendering + absolute-world pattern shaders. The core floating-origin/camera-relative path shipped there (
OloEngine/src/OloEngine/Renderer/CameraRelative.hnow exists) plus the two-phase GPU-cull transform shift + unified CommandDispatch render-origin source in commit f7584a7. Flagging for closure — verify the far-from-origin jitter is gone in the editor if any residual scope remains.drsnuggles8 commentedon Jul 9, 2026
Correction to my earlier comment: this should stay open. #587 shipped only the camera-relative render path (3D geometry uploaded relative to the camera origin). The umbrella still has open follow-ups:
So #587 closes the first technique, not the issue. Disregard the "flagging for closure" line above.
Merge pull request #601 from drsnuggles8/feature/large-coord-2d-sprit…
drsnuggles8 commentedon Jul 9, 2026
Advanced by #601 (merged 2026-07-09); worktree removed. This delivered the 2D sprites (
Renderer2D) coordinate shift follow-up: per-BeginScene grid-snapped render origin, relative camera UBO + CPU-baked vertices (no shader change), debug leverRenderer2D::SetCameraRelativeEnabled(false), L1 + L8 visual-evidence tests (far_on == near_ref at RMSE 0.000). Umbrella stays open — remaining follow-ups: terrain geometry precision far from origin (quadtree LOD) and floating-origin / origin rebasing.drsnuggles8 commentedon Jul 10, 2026
Advanced by #605 (
feature/terrain-camera-relative-429). This delivers the terrain geometry LOD / cull far from origin follow-up.Root cause (two bugs at once). The terrain quadtree keeps node bounds in terrain-local coordinates (
minX·worldSize, no entity transform baked in) and the entity transform places them in the world at draw time — but LOD selection and chunk frustum culling were fed the world camera / view-projection / frustum. Far from origin that failed two ways: (1) it ignored the terrain's world transform, testing the world camera against local bounds, and (2) the world camera↔node subtraction cancelled catastrophically in f32. Symptom: patches degenerating to the coarsest LOD or frustum-culling entirely at ~45 km (TerrainGenerationEvidenceTesteven documents the caveat: "a translated transform would cull the terrain out of view"). Note this is not a family-A/B shader issue — the rendered vertices were already correct (terrain transform goes throughUploadModelInstance's relative shift; the pattern shaders addu_RenderOriginback). Only the CPU LOD/cull inputs were in the wrong space.Fix. Run terrain LOD/cull in terrain-local space, evaluated through the grid-snapped render origin so the large-coordinate subtraction stays precise:
Renderer/CameraRelative.h: newMakeObjectLocalViewProjection/MakeObjectLocalCameraPoshelpers (compose the existing relative-VP / relative-model helpers so the origin cancels analytically, not numerically).Scene.cpp(ProcessScene3DSharedLogic, runtime and editor path): a per-terrain-entityMakeTerrainLocalCullInputsfeeds bothSelectVisibleChunks(tessellated quadtree) andGetVisibleChunks(non-tess cull). SameIsCameraRelativeEnabled()lever as the rest of World: large-coordinate support — camera-relative rendering / floating-origin to kill far-from-origin jitter #429; a byte-identical no-op near origin. Also corrects a latent bug for any off-origin terrain (e.g.ScriptedTerrainDemo's terrain at x=±150), not just at 45 km.Tests (55 terrain + camera-relative tests green).
TerrainCameraRelativeLODTest.cpp(L1, CPU): local camera/VP reproduce the near-origin reference at 45 km; the quadtree screen-space-error decision is reproduced (same LOD) while the naive world eval collapses it; GPU-gated end-to-endTerrainQuadtree::SelectLODpicks the identical node set near vs far (diverges under the old naive path).TerrainCameraRelativeVisualEvidenceTest.cpp(L8): real tessellated editor pipeline near-origin vs +45 km — far terrain renders identically to origin (RMSE ~5 vs a culled ≈40+), PNGs inspected.Umbrella stays OPEN. Remaining follow-ups: terrain streaming far from origin (
TerrainStreamerbuilds tiles in tile-local coords but renders with the entity transform only, and never callsSelectVisibleChunksper tile — entangled with the streaming/floating-origin work) and floating-origin / origin rebasing (re-base the world as the player travels far,SceneStreamerinterop).Merge pull request #605 from drsnuggles8/feature/terrain-camera-relat…
drsnuggles8 commentedon Jul 10, 2026
Floating-origin / origin-rebasing — first slice shipped in #612
The third proposed approach on this umbrella (floating-origin / rebasing) now has a first shippable mechanism. The other two-thirds — camera-relative rendering — were already merged (#587 3D, #601 2D sprites, #605 terrain LOD/cull).
Done (#612)
Scene::RebaseOrigin(shift)shifts the whole world back toward the origin: root entity transforms (children telescope), Jolt rigid bodies + static terrain + character controllers (JoltScene::ShiftOrigin, velocities/rotations/sleep preserved), and Box2D bodies — atomically on the game thread, then re-propagates world matrices and accumulates the offset (absolute = rebased + GetWorldOrigin()).Scene::MaybeRebaseOrigin— grid-snapped auto-trigger inUpdateStreaming(physics idle, never races the parallel gameplay scheduler), gated by opt-inWorldOriginSettings(default off → every existing scene byte-identical).(0,0,0).Remaining (tracked in #613)
TerrainStreamerper-tileWorldOrigin.Leaving this umbrella open until #613 lands.
fix(scene): defer origin rebase for world-anchored constraints; use c…
Merge pull request #612 from drsnuggles8/feature/origin-rebasing-429
drsnuggles8 commentedon Jul 18, 2026
Umbrella complete. The three-part strategy from this issue's proposed approach shipped:
OloEngine/src/OloEngine/Renderer/CameraRelative.h): world positions stay f32 but the camera origin is subtracted before GPU upload, so the GPU always works near 0.Scene::RebaseOrigin/WorldOriginSettingsshipped in feat(scene): floating-origin / origin-rebasing (#429) #612 (first slice: root transforms, Jolt rigid bodies + static terrain + character controllers, Box2D), completed in Floating-origin: rebase cloth/constraints/navmesh/terrain-streamer + 50 km² scale validation (#613) #663 (cloth, world-anchored constraints, navmesh, terrain-streamer + 50 km² scale validation, closing Floating-origin: rebase cloth/vehicle/navmesh/terrain-streamer + 50 km² scale validation (#429 follow-up) #613).dvec3) — deliberately not pursued; camera-relative + rebasing cover PC/Far-Cry scale without an ECS-wide f64 migration, exactly as this issue anticipated.Acceptance criteria met:
RebaseOriginruns inUpdateStreamingon the game thread with physics idle, before the streamer update.Closing as completed (tracked continuation #613 also closed; delivery in #612 + #663).
build(deps): move the GNS overlay to v1.6.1 and record the af839a4 ve…