Terrain for open levels: heightmaps, sculpt and paint, LOD, collision (F75-F77) #123
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "khyretos/claude/terrain"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Requested by Kees · project thread
What (ENG-F75, F76, F77: terrain for open levels)
kke::Terrain(kke/Terrain.h): a square heightmap. It comes from seeded gradient noise (rolling hills, or ridged mountains withridged), from a greyscale picture (8- or 16-bit), or flat. Brushes: raise/lower, smooth, flatten (a level pad), and paint across four colour layers (grass, rock, dirt, snow by default), with rock showing by itself on steep slopes.heightAt,normalAt,slopeAt.kke::TerrainChunks: 32-cell chunks (bigger on terrains over 513², so there are never more than 16 × 16), each drawn only when its bounds are in the camera's view (and in the sun's shadow view for shadows). Their detail halves with distance (every 1st, 2nd, 4th, 8th height). Skirts on every edge mean neighbours never crack. Sculpted chunks rebuild at once, LOD changes rebuild nearest first, and a new terrain builds whole on its first frame.RigidWorld::Shape::HeightField(JoltHeightFieldShape): it splits each cell on the same diagonal asTerrain::heightAt, so the ground you see is the ground you stand on. Bodies, characters, rays, AI ground height andnav.build(#121) all see it.terrain.*(kke::ScriptTerrain, in its own file so it stays out of the way of #121's ScriptModule changes):create{size, resolution, pos, height, seed, feature, octaves, roughness, ridged, image, flat, layers, rock, rockSlope},height,ground,normal,slope,raise,lower,smooth,flatten,paint,layer,info,remove. There is one terrain per game, owned by the script that made it; others can read it. Collision catches up a quarter of a second after sculpting stops.Tests
test_terrain.cpp(8 tests): the same seed gives the same land, the range comes out exact, and ridged differs;heightAtfollows the collision's two triangles; brushes report the rectangle they changed and refuse NaN or a bad radius; paint weights always add up to 255 and cliffs show rock; mesh triangles face up, match the heights, and the skirts face outward; a greyscale PNG loads (and a missing one says which); chunk LOD by distance and only touched chunks rebuild; the Lua API end to end (ownership, errors, release on unload, clamping, path refusal).RigidWorld.TerrainHeightFieldMatchesTheTerrain: rays land within 3 cm ofheightAt(heights are packed), a ball comes to rest on it, and moving or mis-sized height fields are refused.kke_tests: 1254 passed, no warnings at-Wall -Wextra.mkdocs build --strictis clean, andcheck_std_includes/check_dependenciespass.🤖 Generated with Claude Code
https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
Title
Terrain for open levels: heightmaps, sculpt and paint, LOD, collision (F75-F77)
User description
Requested by Kees · project thread
What (ENG-F75, F76, F77: terrain for open levels)
kke::Terrain(kke/Terrain.h): a square heightmap. It comes from seeded gradient noise (rolling hills, or ridged mountains withridged), from a greyscale picture (8- or 16-bit), or flat. Brushes: raise/lower, smooth, flatten (a level pad), and paint across four colour layers (grass, rock, dirt, snow by default), with rock showing by itself on steep slopes.heightAt,normalAt,slopeAt.kke::TerrainChunks: 32-cell chunks (bigger on terrains over 513², so there are never more than 16 × 16), each drawn only when its bounds are in the camera's view (and in the sun's shadow view for shadows). Their detail halves with distance (every 1st, 2nd, 4th, 8th height). Skirts on every edge mean neighbours never crack. Sculpted chunks rebuild at once, LOD changes rebuild nearest first, and a new terrain builds whole on its first frame.RigidWorld::Shape::HeightField(JoltHeightFieldShape): it splits each cell on the same diagonal asTerrain::heightAt, so the ground you see is the ground you stand on. Bodies, characters, rays, AI ground height andnav.build(#121) all see it.terrain.*(kke::ScriptTerrain, in its own file so it stays out of the way of #121's ScriptModule changes):create{size, resolution, pos, height, seed, feature, octaves, roughness, ridged, image, flat, layers, rock, rockSlope},height,ground,normal,slope,raise,lower,smooth,flatten,paint,layer,info,remove. There is one terrain per game, owned by the script that made it; others can read it. Collision catches up a quarter of a second after sculpting stops.Tests
test_terrain.cpp(8 tests): the same seed gives the same land, the range comes out exact, and ridged differs;heightAtfollows the collision's two triangles; brushes report the rectangle they changed and refuse NaN or a bad radius; paint weights always add up to 255 and cliffs show rock; mesh triangles face up, match the heights, and the skirts face outward; a greyscale PNG loads (and a missing one says which); chunk LOD by distance and only touched chunks rebuild; the Lua API end to end (ownership, errors, release on unload, clamping, path refusal).RigidWorld.TerrainHeightFieldMatchesTheTerrain: rays land within 3 cm ofheightAt(heights are packed), a ball comes to rest on it, and moving or mis-sized height fields are refused.kke_tests: 1254 passed, no warnings at-Wall -Wextra.mkdocs build --strictis clean, andcheck_std_includes/check_dependenciespass.🤖 Generated with Claude Code
https://claude.ai/code/session_019rkS6wq7fV9fWdiLwxyyc7
PR Type
Enhancement, Tests
Description
Adds heightmap terrain with sculpting, painting and LOD chunks
Integrates Jolt HeightField collision matching the visual mesh
Exposes terrain.* Lua API for generation, brushes and layers
Includes comprehensive C++ tests for terrain and physics
Diagram Walkthrough
File Walkthrough
5 files
Implements Terrain heightmap generation, sculpting and paintingLua terrain API and chunk rendering logicInitializes ScriptTerrain and calls update/renderAdds HeightField shape enum and BodyDesc fieldsImplements HeightFieldShape creation from terrain data8 files
Declares Terrain struct and TerrainChunks classDeclares ScriptTerrain wrapper classAdds terrain() accessor to ScriptModuleDocuments terrain creation, brushes and Lua APILinks new TERRAIN.md in navigationUpdates ENG-F75/F76/F77 status to yellowAdds Terrain to documentation navigationDocuments terrain.* functions in Lua API table2 files
Tests terrain generation, brushes, paint and chunksTests HeightField collision matches Terrain heights2 files
Adds Terrain and ScriptTerrain source filesAdds test_terrain.cpp to test executablePR Reviewer Guide 🔍
📋 Focus areas: 🔴 3 open · ✅ 0 handled. Every push re-checks them; a handled area moves to Resolved findings at the bottom.
Potential Data Loss in Image Loading
In
loadImage, the code reads 16-bit PNGs into astbi_usbuffer and divides by 65535.0f to get a float between 0 and 1. However, if the image contains values greater than 65535 (which shouldn't happen for 16-bit images but could occur with malformed data or future extensions), the division would be incorrect. More critically, if the image is 8-bit, the values are already in the 0-255 range, and dividing by 65535.0f will result in very small numbers (0.0039 to 0.000039), effectively flattening the terrain to near-zero heights unless theheightparameter compensates significantly. The comment says "8- or 16-bit", but the logic assumes 16-bit scaling for all cases. This could lead to unexpected terrain heights when loading 8-bit images.Missing Validation for Terrain Creation Parameters
In
terrain.create, thesizeparameter is clamped to [4.0f, 16384.0f], but there is no validation for theresolutionparameter beyond clamping it to [kMinSamples, maxSamples]. If a user provides a resolution that results in a spacing smaller than the smallest representable float difference or causes integer overflow in calculations (e.g.,m_n * m_ninheights()), it could lead to undefined behavior or memory issues. Additionally, theposparameter is not validated to ensure it doesn't cause the terrain to be placed outside reasonable world bounds, which could affect collision detection and rendering.Potential Memory Leak in HeightField Shape Creation
In the
HeightFieldcase ofadd, the code creates aJPH::HeightFieldShapeSettingsobject and then callsfromSettings(s)to create the shape. However, iffromSettingsfails or returns an invalid shape, the allocated resources might not be properly cleaned up, leading to a memory leak. The code should check the return value offromSettingsand handle errors appropriately.PR Code Suggestions ✨
No code suggestions found for the PR.
🧹 Lint report · ❌ 37 problems in 6 checks · 🧹 3 formatting checks to tidy
Nothing here blocks the merge (report only).
MARKDOWN · markdownlint: first lines of the output
CPP · cppcheck: first lines of the output
YAML · yamllint: first lines of the output
BASH · shellcheck: first lines of the output
DOCKERFILE · hadolint: first lines of the output
REPOSITORY · trivy: first lines of the output
🧹 Formatting only (layout, no behaviour; the nightly lint-fix job applies these as a PR): YAML · prettier (56 files checked), BASH · shfmt (11 files checked), JSON · prettier (145 files checked)
✅ Clean: actionlint, git_diff, gitleaks, grype, jsonlint, secretlint, stylelint, trufflehog, xmllint
MegaLinter on soucouyant · full logs in the Actions run · comment
/improvefor AI fix suggestions