Skip to content

Game.Simulation.TerrainUtils

Assembly: Assembly-CSharp
Namespace: Game.Simulation

Type: public static class

Base: System.Object

Summary:
Utility helpers for converting between world space and terrain heightmap space, sampling heights and normals from a TerrainHeightData, computing bounds, and raycasting against the terrain heightmap. These methods operate on TerrainHeightData (and sometimes TerrainSystem) using Colossal.Mathematics / Unity.Mathematics types (float2/float3/float4, int2/int4, Bounds3/Bounds1, Line3.Segment). Most operations work in heightmap-space internally (by applying data.offset and data.scale) and convert results back to world-space when returning heights or bounds.


Fields

  • None
    There are no fields on this static utility class.

Properties

  • None
    This static class exposes only methods.

Constructors

  • None
    As a static class it has no public constructors.

Methods

  • public static float3 ToHeightmapSpace(ref TerrainHeightData data, float3 worldPosition)
    Converts a world-space position to heightmap-space by applying data.offset and data.scale: (worldPosition + data.offset) * data.scale. Useful as a first step before sampling or grid lookups.

  • public static Line3.Segment ToHeightmapSpace(ref TerrainHeightData data, Line3.Segment worldLine)
    Converts both endpoints of a world-space line segment into heightmap-space (uses the float3 overload).

  • public static float ToWorldSpace(ref TerrainHeightData data, float heightmapHeight)
    Converts a height value from heightmap-space back into world-space: heightmapHeight / data.scale.y - data.offset.y. Typically used to convert sampled height values to actual world Y coordinates.

  • public static Bounds3 GetBounds(ref TerrainHeightData data)
    Returns the bounds of the heightmap in heightmap-space converted to world-space extents by undoing the offset/scale transform: new Bounds3(-data.offset, (data.resolution - 1) / data.scale - data.offset). Use to get the terrain extents.

  • public static Bounds3 GetEditorCameraBounds(TerrainSystem terrainSystem, ref TerrainHeightData data)
    If a TerrainSystem.worldHeightmap is available, returns bounds computed from the TerrainSystem (using worldOffset, heightScaleOffset, worldSize); otherwise falls back to GetBounds(ref data). This is used by editor camera logic to get an appropriate bounds set.

  • public static Bounds1 GetHeightRange(ref TerrainHeightData data, Bounds3 worldBounds)
    Scans the integer heightmap samples that cover worldBounds and computes a min/max height range. Internally:

  • Transforms worldBounds corners to heightmap-space.
  • Clamps integer indices into the heightmap resolution.
  • Iterates over the covered samples, finds min/max stored heights (the raw stored integer heights), then converts min/max back to world-space using ToWorldSpace. Returns a Bounds1 containing world-space min/max heights. Note: this inspects raw grid samples (no interpolation).

  • public static float SampleHeight(ref TerrainHeightData data, float3 worldPosition)
    Samples the height at worldPosition by:

  • Transforming to heightmap-space.
  • Locating the four surrounding integer height samples.
  • Bilinearly interpolating those samples.
  • Converting the interpolated height back to world-space with ToWorldSpace. Returns the world-space height (float). Uses math.saturate, math.lerp and casts stored sample integers to float for interpolation.

  • public static float SampleHeight(ref TerrainHeightData data, float3 worldPosition, out float3 normal)
    Same as the previous SampleHeight but also computes an approximate normal at the sampled point. The normal is computed from the differences between the two bilinearly interpolated rows/columns and normalized (math.normalizesafe). Returns the world-space height and outputs a normal in world-space orientation suitable for e.g. placing objects aligned to terrain.

  • public static bool Raycast(ref TerrainHeightData data, Line3.Segment worldLine, bool outside, out float t, out float3 normal)
    Performs a raycast of the given worldLine against the terrain heightmap. Behavior:

  • Converts the worldLine into heightmap-space.
  • Builds a heightmap-space AABB (with Y extended by [-50..+100]) and intersects it with the line (if outside==true uses only Y overlap check).
  • Cuts the line to the intersection interval and then traverses the covered grid cells in a 2D DDA-like fashion, calling RaycastCell for each cell encountered.
  • If a triangle hit within a cell is found, returns true, sets t to the param along the original worldLine in [0,1] (saturated), and outputs the geometric normal (normalized).
  • If no hit, returns false and sets t = 2 and normal = default. Note: the 'outside' flag controls whether the algorithm allows hits where some cell corners are out-of-bounds (it affects how per-cell bounds are computed).

  • private static bool RaycastCell(ref TerrainHeightData data, Line3.Segment localLine, int2 pos, bool outside, out float t, out float3 normal)
    Tests intersection between localLine (already in heightmap-space and possibly cut to the cell area) and the two triangulated rectangles formed by four height samples at cell (pos). Behavior:

  • Clamps/ selects the four sample indices for the cell (or for outside behavior uses pos/clamped values).
  • Builds corner positions for the cell using sample heights and computes a cell AABB to test a quick reject.
  • If the AABB intersects the localLine, it builds four triangles by splitting the cell into four triangles around the center and performs triangle-line intersections for each.
  • If any triangle intersects, returns true, sets t to the smallest intersection parameter (relative to localLine) and sets normal to the triangle's geometric (unnormalized) normal (caller normalizes it).
  • If no intersection, returns false. This is the low-level per-cell intersection routine used by Raycast.

Notes and implementation details: - All sampling and raycasting is done by converting coordinates to heightmap-space first: heightmapPosition = (worldPosition + data.offset) * data.scale. This means indices and interpolation operate in the heightmap grid coordinates. Conversion back to world-space heights uses ToWorldSpace. - The heights array is read as integer samples (cast from data.heights[] to int/float) — these represent stored heightmap values (heightmap units). Be aware of units and scale when interpreting results. - The Raycast method returns t = 2 when failure/no hit. Successful hits return t in [0,1] mapped to the original worldLine. The normal is a geometric normal computed from triangle vertices and normalized by the caller. - Performance: the methods, especially GetHeightRange, SampleHeight and Raycast, perform loops/array accesses. If you need to sample many points per frame, prefer bulk/jobified approaches if available. These helpers are convenient but not optimized for massive per-pixel workloads. - Safety: Many internal indices are clamped to [0, resolution - 1] but callers should still ensure TerrainHeightData and its arrays are valid and that data.resolution is correct to avoid out-of-bounds.

Usage Example

// Example: sample height and normal at a world position
float3 worldPos = new float3(100.0f, 0.0f, 200.0f);
float3 normal;
float height = TerrainUtils.SampleHeight(ref terrainHeightData, worldPos, out normal);
// height is world-space Y; normal is normalized direction to align objects.


// Example: get height range within a world-space AABB
Bounds3 worldBounds = new Bounds3(new float3(50, 0, 50), new float3(150, 0, 150));
Bounds1 range = TerrainUtils.GetHeightRange(ref terrainHeightData, worldBounds);
float minWorldY = range.min;
float maxWorldY = range.max;


// Example: raycast a line against terrain
Line3.Segment worldLine = new Line3.Segment(new float3(10, 200, 10), new float3(10, -50, 10));
if (TerrainUtils.Raycast(ref terrainHeightData, worldLine, outside: false, out float tHit, out float3 hitNormal))
{
    // tHit is param along worldLine (0..1). Compute hit position:
    float3 hitPos = math.lerp(worldLine.a, worldLine.b, tHit);
    // hitNormal is normalized surface normal.
}
else
{
    // No hit (tHit == 2).
}

If you need additional examples (e.g., converting a Line3.Segment to heightmap space before custom sampling, or bulk-sampling strategies), tell me which scenario and I can add a focused snippet.