Add heightfield terrain generation support#1033
Conversation
- Add heightfield_to_mesh() function to convert 2D heightfields to watertight triangle meshes - Add 'heightfield' terrain type to generate_terrain_grid() - Export heightfield_to_mesh in public API (newton.geometry) - Creates closed meshes with top surface, bottom surface, and side walls - Supports custom center position and ground level
|
|
📝 WalkthroughWalkthroughAdds heightfield-based terrain support: new functions convert 2D heightfields into closed meshes (top, bottom, sides) and integrate into terrain generation; the symbol is exported from internal and public geometry modules and tests for the new behavior were added. (47 words) Changes
Sequence Diagram(s)sequenceDiagram
participant U as User / Caller
participant G as generate_terrain_grid
participant Hwrap as _heightfield_terrain
participant Hmesh as heightfield_to_mesh
participant F as _flat_terrain
U->>G: request terrain type "heightfield" with params
G->>Hwrap: invoke with size, heightfield, center, ground_z
alt heightfield provided
Hwrap->>Hmesh: heightfield_to_mesh(heightfield, extent_x, extent_y, center_x, center_y, ground_z)
Hmesh-->>Hwrap: vertices, faces (watertight mesh: top, bottom, sides)
else heightfield is None
Hwrap->>F: _flat_terrain(size)
F-->>Hwrap: vertices, faces (flat mesh)
end
Hwrap-->>G: vertices, faces (terrain mesh)
G-->>U: assembled terrain grid mesh
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
newton/_src/geometry/terrain_generator.py (1)
455-470: terrain_params annotation rejects valid heightfield argsWith the new
"heightfield"type we now legitimately passnp.ndarrayparameters, but the signature still locksterrain_paramstodict[str, dict[str, float]]. Static type checkers will flag the array argument, and IDEs will warn even though runtime code expects it. Please widen the annotation (e.g.dict[str, dict[str, Any]]) and importAnyaccordingly.-import numpy as np +from typing import Any + +import numpy as np @@ -def generate_terrain_grid( +def generate_terrain_grid( @@ - terrain_params: dict[str, dict[str, float]] | None = None, + terrain_params: dict[str, dict[str, Any]] | None = None,
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
newton/_src/geometry/__init__.py(2 hunks)newton/_src/geometry/terrain_generator.py(4 hunks)newton/geometry.py(2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-12T17:51:37.474Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).
Applied to files:
newton/geometry.pynewton/_src/geometry/__init__.py
📚 Learning: 2025-08-12T17:58:00.815Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/_src/solvers/featherstone/kernels.py:18-20
Timestamp: 2025-08-12T17:58:00.815Z
Learning: In newton/_src/core/__init__.py, the function transform_twist is properly re-exported from the spatial module via "from .spatial import (..., transform_twist, ...)" and included in __all__, making it available for import as "from ...core import transform_twist" from other modules.
Applied to files:
newton/geometry.pynewton/_src/geometry/__init__.py
📚 Learning: 2025-08-14T17:38:36.106Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/__init__.py:25-29
Timestamp: 2025-08-14T17:38:36.106Z
Learning: The Newton project prefers incremental __all__ building using __all__ += [...] pattern to group exports with their related imports, rather than a single consolidated __all__ at the end of the file.
Applied to files:
newton/_src/geometry/__init__.py
🧬 Code graph analysis (2)
newton/geometry.py (1)
newton/_src/geometry/terrain_generator.py (1)
heightfield_to_mesh(597-700)
newton/_src/geometry/__init__.py (1)
newton/_src/geometry/terrain_generator.py (1)
heightfield_to_mesh(597-700)
🪛 Ruff (0.14.3)
newton/_src/geometry/terrain_generator.py
623-623: Avoid specifying long messages outside the exception class
(TRY003)
- Default center_x/center_y to size/2 instead of 0.0 - This aligns heightfield blocks with other terrain types (flat, wave, etc.) - Prevents overlap when mixing terrain types in generate_terrain_grid() - Addresses CodeRabbit review feedback
- Change terrain_params type from dict[str, dict[str, float]] to dict[str, dict[str, Any]] - Import Any from typing module - Allows passing np.ndarray for heightfield parameter without type checker warnings - Addresses CodeRabbit type annotation feedback
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
newton/_src/geometry/terrain_generator.py(4 hunks)
🧰 Additional context used
🪛 Ruff (0.14.3)
newton/_src/geometry/terrain_generator.py
629-629: Avoid specifying long messages outside the exception class
(TRY003)
🔇 Additional comments (1)
newton/_src/geometry/terrain_generator.py (1)
412-455: LGTM! Alignment issue resolved.The default centering at
size[0] / 2andsize[1] / 2now aligns heightfield blocks with other terrain primitives that span[0, size], fixing the overlap issue flagged in the previous review. The fallback to_flat_terrainwhen no heightfield is provided is a sensible default.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
newton/_src/geometry/terrain_generator.py(5 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: shi-eric
Repo: newton-physics/newton PR: 521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.423Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.
🪛 Ruff (0.14.3)
newton/_src/geometry/terrain_generator.py
631-631: Avoid specifying long messages outside the exception class
(TRY003)
🔇 Additional comments (5)
newton/_src/geometry/terrain_generator.py (5)
23-23: LGTM!The
Anyimport is needed to support flexible terrain parameter types, particularly for the heightfield terrain which accepts numpy arrays.
414-456: LGTM!The alignment issue from the previous review has been properly addressed. Defaulting
center_xandcenter_ytosize[0]/2andsize[1]/2ensures that heightfield tiles align correctly with other terrain types in the grid, spanning[0, size]as expected.
463-463: LGTM!Using
Anyfor terrain parameter values is appropriate here, as different terrain types require different parameter types (e.g., numpy arrays for heightfield, floats for wave amplitude).
475-478: LGTM!Documentation clearly describes the new heightfield terrain type and its parameters.
497-497: LGTM!The heightfield terrain type is properly registered in the terrain functions mapping.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
@maxkra15 Thanks for this contribution. The code looks fine, ideally we would have some a small unit test but I'm aware that the rest of the terrain generator is also not properly tested yet (it's only tested indirectly by being used in one of the walking robot examples). Please let me know if writing a small test would be fine, otherwise we can merge as it is and I can take care of writing proper tests for all of terrain_generator.py |
|
@nvtw I agree that having a small unit test would be valuable, and it would be ideal to add it alongside broader coverage in the terrain generator to keep things consistent imo. If it is easier for the project, I can also add a minimal test now. Just let me know. |
|
A minimal test would be great. If you feel very motivated, feel free to add tests for all terrain generator functionality. |
…ation - Add 20 comprehensive unit tests covering all terrain generation functions - Add input validation to heightfield_to_mesh: * Validate heightfield is 2D array * Validate minimum grid size (2x2) * Validate extent values are positive - Test coverage includes: * All existing terrain functions (flat, pyramid_stairs, random_grid, wave, box, gap) * heightfield_to_mesh with various scenarios (basic, sloped, random, flat) * Edge cases (minimum grid size, center offset, watertight mesh) * Error conditions (invalid dimensions, too small grid, non-positive extents) * Terrain grid generation (single/multiple blocks, seeding)
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
newton/_src/geometry/terrain_generator.py (1)
605-726: Comprehensive validation and solid implementation.The function includes all necessary input validation as requested in previous reviews:
- 2D array dimensionality check
- Minimum grid size (2×2) enforcement
- Positive extent validation
The mesh generation logic correctly creates a watertight mesh with top surface, bottom surface, and side walls. The implementation is thorough and well-structured.
Optional: Address linting hints for consistency.
The static analysis tool flags the exception messages on lines 637, 643-645, 649, and 651 (TRY003) for including dynamic values. While the current messages are helpful for debugging, you could optionally shorten them if you want to strictly follow the linting guideline:
- if heightfield.ndim != 2: - raise ValueError(f"heightfield must be 2D array, got shape {heightfield.shape}") + if heightfield.ndim != 2: + raise ValueError("heightfield must be a 2D array")- if grid_size_x < 2 or grid_size_y < 2: - raise ValueError( - f"heightfield must be at least 2x2, got shape ({grid_size_x}, {grid_size_y})" - ) + if grid_size_x < 2 or grid_size_y < 2: + raise ValueError("heightfield must be at least 2x2")- if extent_x <= 0: - raise ValueError(f"extent_x must be positive, got {extent_x}") + if extent_x <= 0: + raise ValueError("extent_x must be positive") if extent_y <= 0: - raise ValueError(f"extent_y must be positive, got {extent_y}") + raise ValueError("extent_y must be positive")newton/tests/test_terrain_generator.py (1)
1-457: Excellent comprehensive test coverage!This test suite thoroughly validates the heightfield terrain functionality with:
- 11 tests for
heightfield_to_meshcovering validation, edge cases, and various terrain types- Tests for all existing terrain generation functions
- Integration tests for
generate_terrain_gridwith heightfield support- Proper verification of dtypes, shapes, watertightness, and numerical accuracy
The tests address the unit testing needs mentioned in the PR discussion. Great work on the comprehensive coverage!
Optional: Fix unused variable warnings for clarity.
Several tests unpack variables that aren't used (lines 138, 155, 167, 181, 204, 220, 284, 372). Consider prefixing unused variables with
_to indicate intentionality:- vertices, indices = heightfield_to_mesh( + vertices, _indices = heightfield_to_mesh(This addresses the RUF059 linting hints and makes the code more explicit about which variables are intentionally unused.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📥 Commits
Reviewing files that changed from the base of the PR and between 34091c2 and 4302b998db1f14d1609b41b724c27364e3e5dd82.
📒 Files selected for processing (2)
newton/_src/geometry/terrain_generator.py(5 hunks)newton/tests/test_terrain_generator.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
newton/tests/test_terrain_generator.py (2)
newton/_src/geometry/terrain_generator.py (9)
_box_terrain(346-370)_flat_terrain(171-188)_gap_terrain(373-406)_heightfield_terrain(414-456)_pyramid_stairs_terrain(191-244)_random_grid_terrain(247-303)_wave_terrain(306-343)generate_terrain_grid(459-553)heightfield_to_mesh(605-726)newton/tests/unittest_utils.py (1)
assert_np_equal(240-246)
🪛 Ruff (0.14.3)
newton/tests/test_terrain_generator.py
138-138: Unpacked variable indices is never used
Prefix it with an underscore or any other dummy variable pattern
(RUF059)
155-155: Unpacked variable indices is never used
Prefix it with an underscore or any other dummy variable pattern
(RUF059)
167-167: Unpacked variable indices is never used
Prefix it with an underscore or any other dummy variable pattern
(RUF059)
181-181: Unpacked variable indices is never used
Prefix it with an underscore or any other dummy variable pattern
(RUF059)
204-204: Unpacked variable vertices is never used
Prefix it with an underscore or any other dummy variable pattern
(RUF059)
220-220: Unpacked variable indices is never used
Prefix it with an underscore or any other dummy variable pattern
(RUF059)
284-284: Unpacked variable indices3 is never used
Prefix it with an underscore or any other dummy variable pattern
(RUF059)
372-372: Unpacked variable indices is never used
Prefix it with an underscore or any other dummy variable pattern
(RUF059)
newton/_src/geometry/terrain_generator.py
637-637: Avoid specifying long messages outside the exception class
(TRY003)
643-645: Avoid specifying long messages outside the exception class
(TRY003)
649-649: Avoid specifying long messages outside the exception class
(TRY003)
651-651: Avoid specifying long messages outside the exception class
(TRY003)
🔇 Additional comments (2)
newton/_src/geometry/terrain_generator.py (2)
414-456: Excellent implementation - alignment issue resolved.The function properly defaults
center_xandcenter_ytosize[0]/2andsize[1]/2, ensuring heightfield tiles align with other terrain types in the grid (which span[0, size]). The fallback to_flat_terrainwhenheightfieldisNoneprovides sensible default behavior.
459-553: Clean integration of heightfield terrain type.The changes properly register the heightfield terrain type, update the type annotation to allow flexible parameters (
Any), and document the new terrain type. The integration is minimal and non-invasive.
- Test custom heightfield input - Test None heightfield (fallback to flat terrain) - Test custom center coordinates - Test custom ground_z parameter - Test heightfield terrain type in generate_terrain_grid This brings total test count to 25, covering all terrain generation functions including the heightfield terrain wrapper.
4302b99 to
e23ede6
Compare
|
@nvtw this should cover basic functionality, hope this helps |
|
pre-commit.ci autofix |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
newton/_src/geometry/terrain_generator.py (1)
605-724: LGTM! Comprehensive validation and correct mesh generation.The function properly validates inputs (dimensionality, minimum size, positive extents) and correctly generates a watertight mesh with top surface, bottom surface, and side walls. The mesh topology and winding orders are correct for proper rendering.
Optional: Address linting hints for error messages.
Ruff TRY003 suggests keeping error messages concise. While the current messages with dynamic values are helpful for debugging, you could optionally shorten them:
- if heightfield.ndim != 2: - raise ValueError(f"heightfield must be 2D array, got shape {heightfield.shape}") + if heightfield.ndim != 2: + raise ValueError("heightfield must be a 2D array") - if grid_size_x < 2 or grid_size_y < 2: - raise ValueError(f"heightfield must be at least 2x2, got shape ({grid_size_x}, {grid_size_y})") + if grid_size_x < 2 or grid_size_y < 2: + raise ValueError("heightfield must be at least 2x2") - if extent_x <= 0: - raise ValueError(f"extent_x must be positive, got {extent_x}") + if extent_x <= 0: + raise ValueError("extent_x must be positive") - if extent_y <= 0: - raise ValueError(f"extent_y must be positive, got {extent_y}") + if extent_y <= 0: + raise ValueError("extent_y must be positive")However, the current error messages are more informative and the code already passed linting checks, so this is purely optional.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
newton/_src/geometry/terrain_generator.py(5 hunks)
🧰 Additional context used
🪛 Ruff (0.14.3)
newton/_src/geometry/terrain_generator.py
637-637: Avoid specifying long messages outside the exception class
(TRY003)
643-643: Avoid specifying long messages outside the exception class
(TRY003)
647-647: Avoid specifying long messages outside the exception class
(TRY003)
649-649: Avoid specifying long messages outside the exception class
(TRY003)
🔇 Additional comments (3)
newton/_src/geometry/terrain_generator.py (3)
23-23: LGTM!The
Anyimport is appropriately used for theterrain_paramstype hint on line 463, which allows flexible per-terrain-type parameters.
414-456: LGTM! Alignment issue resolved.The function correctly defaults
center_xandcenter_ytosize[0]/2andsize[1]/2(lines 441-444), ensuring heightfield tiles align with other terrain types in the grid. The fallback to_flat_terrainwhenheightfieldisNoneis a sensible default.
463-463: LGTM!The integration of heightfield terrain is clean and consistent:
- Type hint for
terrain_paramsproperly accommodates per-type configuration- Documentation updated to include the new 'heightfield' type
- Function mapping correctly registered
Also applies to: 475-477, 497-497
Co-authored-by: nvtw <110816143+nvtw@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: nvtw <110816143+nvtw@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Description
This PR adds support for custom heightfield terrain generation to Newton's terrain generator, enabling users to create terrain from 2D elevation data.
Newton Migration Guide
Please ensure the migration guide for warp.sim users is up-to-date with the changes made in this PR.
docs/migration.rstis up-to dateBefore your PR is "Ready for review"
newton/tests/test_examples.py)pre-commit run -aSummary by CodeRabbit
New Features
Public API
Tests