Skip to content

Physics: terrain has no collision — wire Jolt HeightFieldShape into TerrainComponent #428

Closed

Description

@drsnuggles8

Problem

Terrain renders fully (quadtree LOD, tessellation, splatmaps, streaming) but has no physics collision. Jolt's heightfield shape is explicitly rejected today:

  • OloEngine/src/OloEngine/Physics3D/JoltShapes.cpp:542 — GetShapeType: HeightField shape type {0} not supported, defaulting to Box
  • OloEngine/src/OloEngine/Physics3D/JoltBinaryStream.cpp:394 reads a HeightField case, but no shape is ever constructed for terrain.
  • No terrain-collider component exists under OloEngine/src/OloEngine/Terrain/.

Consequence: the player falls through terrain, NPCs can't walk on it, vehicles can't drive on it.

Why it matters

Genre-blocking for an FPS — collision against the ground is table stakes. This is the single most urgent Tier 1 gap.

Proposed approach

  • Build JPH::HeightFieldShapeSettings from the terrain heightmap (CPU heights already available via TerrainData::GetHeightAt), matching world scale and tile origin.
  • Add a TerrainColliderComponent (or extend TerrainComponent) that creates a static Jolt body from the heightfield on scene start / terrain edit. (Remember the cross-binding touch-points if adding a component.)
  • For streamed terrain (TerrainStreamer), create/destroy heightfield bodies per loaded tile; update the dirty region on sculpt/erosion edits (partial heightfield update).
  • Resolve JoltShapes.cpp::GetShapeType to handle HeightField for serialization round-trip.

Acceptance criteria

  • A character controller and a vehicle rest on / drive over terrain without falling through.
  • Raycasts (AI perception, weapons) hit the terrain surface.
  • Streamed tiles add/remove collision in step with their visual load/unload.
  • Sculpt/erosion edits update collision for the affected region.

Tier 1 — architectural. From the "production-ready open-world FPS" gap analysis.

Score

capability: 8
craft: 3
stability: 3
decay: 1
effort: 3
confidence: 0.8
learning: 3
fun: 3
kano: table-stakes
blocked_by: []
blocks: []

Rated per issue-scoring · score = confidence × (capability + craft + stability + decay) / effort, derived by the picker.

Activity

  1. coderabbitai commented on Jun 28, 2026

    @coderabbitai
    Contributor
    🔗 Related PRs

    #78 - Feature/physics integration [merged]
    #225 - Compute exact mesh collider volume and centroid [merged]
    #340 - feat(physics): add CollideConnected toggle for joints (#308 item 1) [merged]


    🧪 Issue enrichment is currently in open beta.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. drsnuggles8 commented on Jun 29, 2026

    @drsnuggles8
    OwnerAuthor

    Implemented in PR #468 (branch feature/physics-terrain-heightfield).

    What's done

    • JoltShapes::CreateTerrainHeightFieldShape builds a static JPH::HeightFieldShape from the terrain's CPU height field, mapping worldSize/heightScale/entity scale exactly onto Jolt's offset + scale·(x, sample, z) surface so collision coincides with the rendered mesh. Odd resolutions are padded to Jolt's block-size multiple; bit-depth is adaptive (~2 cm).
    • ShapeType::HeightField + GetShapeType round-trip resolved (it was defaulting to Box).
    • JoltScene static terrain-body lifecycle on NON_MOVING; body UserData = entity UUID so raycasts (AI perception / weapons) resolve back to the terrain entity.
    • TerrainComponent.m_CollisionEnabled (serialized, default on); round-trips through scene YAML + save-game (format v5→v6).
    • Built GPU-free from CPU heights at OnPhysics3DStart, so it also works headless / on the dedicated server; re-synced on runtime Regenerate.

    Evidence (104/104 tests green) — new TerrainCollisionTest.cpp drives the real Scene::OnPhysics3DStart + Jolt step path, headless:

    • DynamicBodyRestsOnFlatTerrainSurface — a dropped body rests one radius above a flat terrain translated to y=10 (proves no fall-through + transform application).
    • DynamicBodyRestsOnProceduralSurface — a dropped body's rest height tracks the local procedural surface (proves the shape follows the height field; uses an odd resolution to exercise the padding path).
    • RaycastHitsTerrainSurface — a downward ray hits the terrain at the expected surface height and resolves to the terrain entity.

    All cross-binding coverage guards, save-game round-trips, SceneSerializer, and LuaDrivesTerrainRegeneration still pass.

    Acceptance criteria status

    Leaving this open for you to close once PR #468 merges.

  3. added 2 commits that reference this issue on Jun 29, 2026
  4. drsnuggles8 commented on Jun 29, 2026

    @drsnuggles8
    OwnerAuthor

    Advanced by #468 (merged 2026-06-29); worktree removed. Static terrain collision delivered: JoltShapes::CreateTerrainHeightFieldShape builds a static JPH::HeightFieldShape body GPU-free from CPU heights at OnPhysics3DStart, ShapeType::HeightField round-trips, TerrainComponent.m_CollisionEnabled persists through scene YAML + save-game, and TerrainCollisionTest proves rest-on-surface + raycast-hit (incl. odd-resolution padding). Two acceptance criteria deferred to a separately-filed follow-up: streamed per-tile collision (TerrainStreamer) and sculpt/erosion dirty-region partial updates (HeightFieldShape::SetHeights). Leaving open pending confirmation those are tracked in the new follow-up issue(s).

  5. drsnuggles8 commented on Jun 30, 2026

    @drsnuggles8
    OwnerAuthor

    Core scope appears resolved by #468 (merged 2026-06-29): feat(physics): wire Jolt HeightFieldShape into terrain collision (#428) — builds JPH::HeightFieldShapeSettings from the terrain heightmap and creates a static collision body (acceptance criteria 1–2). The streamed-tile + sculpt-edit heightfield-update follow-up (acceptance criteria 3–4) is tracked separately in #469. Flagging the core scope for closure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions