tront.xyz  /  floatingorigin

Floating origin: how to walk a million units from spawn without watching the world rattle apart.

A 1996 flight-sim trick, ported to a JavaScript / Three.js sandbox built like an infinite procedural IKEA. The engine quietly teleports the world back toward (0, 0, 0) as you traverse; you stay forever in the small, precise center of float-space.

[ live demo ]   [ source ]

The float-precision wall

Every 3D engine you'll touch stores positions as 32-bit floats. They get roughly 7 digits of precision. That's plenty when your coordinates fit in a phone booth; it breaks when your coordinates fit a small country.

This is the wall every open-world game eventually runs into. Unreal solved it with a "world origin rebasing" system in 4.4; Star Citizen went full 64-bit; Minecraft straight-up freezes if you push past the Far Lands. Indie open-worlds usually pretend they'll never hit it and then they do.

The trick

You lie to the engine. Keep the player physically near (0, 0, 0) at all times. When they drift past a threshold (say 300 units), teleport them back to zero, and shift the entire world the same amount in the same direction. From inside the engine, nothing changed: same relative positions, same view, same physics. But your camera coordinates stayed small and precise.

From the player's POV the world looks seamless; nothing snaps, nothing reloads. The only thing that moved is a counter you keep in software.

Two coordinate systems running in parallel

You'll need both, and you'll need to know which one any given value lives in.

The bridge is a running tally of every shift you've ever applied; call it globalOffset. The player's absolute world position is always engineCamera + globalOffset. JavaScript makes this easy because every Number is already a double; in C# you use double explicitly.

The shift, in five lines

Per frame, after you've applied input to engineCam and worked out absPos = engineCam + globalOffset, recenter only if the engine camera has drifted past the threshold:

pseudocodeif length(engineCam.xz) > THRESHOLD:
    shift          = (engineCam.x, 0, engineCam.z)
    globalOffset  += shift
    engineCam     -= shift
    for chunk in chunks: chunk.position -= shift

Order matters: derive absPos from the still-drifted camera before you recenter, or you'll lose track of which chunk you're standing on. Place a chunk back into engine space by subtracting the offset (engineX = chunkWorldX - globalOffset.x), and hash the chunk's world coords (never the engine coords) with a fixed seed to make the procgen deterministic across machines.

Press H inside the demo for the full pseudocode walkthrough with setup, per-frame, chunk placement, and deterministic generation, in tabbed form.

Threshold tuning

Big enough that you don't shift every frame, small enough that floats stay precise.

Why parenting one big worldRoot doesn't work

A common first instinct is to parent every chunk to one big worldRoot transform and translate that root instead of the player. It seems cleaner: one transform to move, no per-chunk bookkeeping. It also breaks for the exact reason as before.

Float precision lives at the final composed transform, not just at the player. Once your worldRoot sits at (-4,000,000, 0, 0), child translations like 0.001 round down to zero. The whole world starts twitching on chunk boundaries the same way the player would have.

You have to shift each chunk individually (or shift a small pool of nearby roots that themselves don't drift far). That's what the demo does, that's what real implementations do.

Watch the demo. The yellow pillar sits at engine origin (0, 0, 0). As you walk, it stays put, because it does. When the floating-origin event fires, the pillar appears to teleport toward you, but actually you teleported to it. Everything else shifts to compensate, so the world looks seamless. Hold SHIFT to traverse fast and trigger shifts in seconds.

Live demo

interactive sandbox
Infinite Ikea / Floating Origin
Walk an infinite procedural IKEA at full sprint. The yellow pillar sits at engine origin (0, 0, 0); when you drift past the threshold ring, watch the world snap toward you as the shift counter ticks. WASD + mouse, SHIFT for 14x boost, SPACE / CTRL for altitude, H for the in-demo tabbed reference.
launch demo →

Opens in a new tab. Pointer-lock + the HUD panels (telemetry, threshold ring, chunk minimap, controls) want the whole viewport; iframing them down to 16:9 crowds the layout.

Unity C#: FloatingOrigin.cs

Attach to a manager GameObject. player is whatever you consider the center of the world (usually the main camera root or character controller). Threshold of 1000 is a typical Unity choice; the float-stable range is generous before precision visibly suffers.

C#using UnityEngine;
using UnityEngine.SceneManagement;

public class FloatingOrigin : MonoBehaviour
{
    public Transform player;
    public float threshold = 1000f;

    // Doubles survive way past Unity float range.
    public double absoluteX, absoluteZ;

    // Cache these at chunk load; FindObjectsOfType in LateUpdate would tank frame time.
    [SerializeField] Rigidbody[] trackedBodies;
    [SerializeField] ParticleSystem[] trackedParticles;

    void LateUpdate()
    {
        Vector3 p = player.position;
        if (p.x * p.x + p.z * p.z < threshold * threshold) return;

        Vector3 shift = new Vector3(p.x, 0f, p.z);
        absoluteX += shift.x;
        absoluteZ += shift.z;

        // 1. Recenter every root transform.
        foreach (GameObject root in SceneManager.GetActiveScene().GetRootGameObjects())
            root.transform.position -= shift;

        // 2. Nudge active rigidbodies so interpolation doesn't snap.
        foreach (Rigidbody rb in trackedBodies)
            if (rb && !rb.isKinematic) rb.position -= shift;

        // 3. Translate every live particle in world-simulated systems.
        foreach (ParticleSystem ps in trackedParticles)
        {
            if (!ps || ps.main.simulationSpace != ParticleSystemSimulationSpace.World) continue;
            var buf = new ParticleSystem.Particle[ps.particleCount];
            int n = ps.GetParticles(buf);
            for (int i = 0; i < n; i++) buf[i].position -= shift;
            ps.SetParticles(buf, n);
        }
    }
}

Reading the absolute position from elsewhere

C#// Anywhere in your code:
Vector3 engine = player.position;
double worldX = origin.absoluteX + engine.x;
double worldZ = origin.absoluteZ + engine.z;

// Feed worldX/worldZ to chunk generation, save files, network sync, etc.
int chunkX = (int)System.Math.Floor(worldX / CHUNK_SIZE);
int chunkZ = (int)System.Math.Floor(worldZ / CHUNK_SIZE);

Gotchas the basic version forgets

The 30-line implementation above gets the camera + static geometry working. Real games have a lot more state living in world space that also has to shift, or it'll snap and pop and break your suspension of disbelief.

Rigidbodies

Set position directly on shift. Don't use MovePosition; that interpolates over the next physics step and you'll see a one-frame snap. For active simulations, also kill any cached predicted positions in your physics layer.

Particles

Only world-simulated particles need translation. Local-space particles move with their transform automatically. The above code does the right check; if you wire it up by hand, mind the simulationSpace.

Trails & Line Renderers

Their internal points live in world space and won't move with the transform. Either Clear() them on shift (acceptable for short-lived trails like bullets), or iterate GetPositions(), offset every vertex, and SetPositions() back (necessary for long persistent trails like skid marks).

Audio sources

3D positional audio on transforms shifts cleanly with the rest of the scene. For streaming sources tied to the player (footsteps, suit comms, music), parent them to the player so they never drift to begin with.

NavMesh

Bake per chunk with NavMeshSurface. A monolithic baked navmesh tied to world coordinates won't survive shifts; it'll either crash the agent or send them through walls.

Networked clients

Each client maintains its own engine-space origin. Never sync engine-space positions over the wire. Sync the absolute world position (a double pair), then convert to local engine space on receive. If you sync engine-space, two players in the same room can disagree about who's at (50, 0, 0) by 800,000 units.

Performance. FindObjectsOfType in LateUpdate would tank your frame time the moment you have more than a few hundred Rigidbodies. Cache the rigidbodies and particle systems when chunks spawn; drop them when chunks despawn. The shift itself is cheap once the lists are pre-built.

Credits + further reading

The floating-origin trick goes back at least to FlightGear in the late 90s; flight sims hit the precision wall before anyone else because their playable area was the actual surface of the Earth. Unity's manual still calls it "floating origin"; Unreal ships a built-in "World Origin Rebasing" since 4.4. The Three.js / IKEA implementation behind the demo is mine.

If you ship something built on this, drop a note in the Discord.