Editor utility for terrain editing, provides sculpting and splat-paint operations for a Terrain component in the active scene. Sculpt can procedurally generate heightmaps (Perlin or crater) or flatten the terrain and then recreate the component; PaintSplat returns metadata and a message indicating a compute shader dispatch would occur.
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
using Sandbox.Utility;
namespace Editor.Mcp
{
internal static class TerrainSculptDriver
{
public static ToolOutcome<object> Sculpt( string? terrainQuery, string operation, string algorithm = "perlin", float scale = 1.0f, float height = 1000.0f, float roughness = 0.5f )
{
var terrainComp = ResolveTerrain( terrainQuery );
if ( terrainComp == null )
{
return ToolOutcome<object>.Failure( null, ToolStatus.Unavailable, "terrain.not_found", "No Terrain component found in active scene." );
}
var storage = terrainComp.Storage;
if ( storage == null || storage.HeightMap == null )
{
return ToolOutcome<object>.Failure( null, ToolStatus.Unavailable, "terrain.no_storage", "Terrain has no initialized storage or heightmap." );
}
var res = storage.Resolution;
var heightmap = storage.HeightMap;
var normOp = operation.ToLowerInvariant();
if ( normOp == "procedural_generate" )
{
var freq = 0.005f * scale;
for ( int y = 0; y < res; y++ )
{
for ( int x = 0; x < res; x++ )
{
var nx = x * freq;
var ny = y * freq;
float val;
if ( algorithm.Equals( "crater", StringComparison.OrdinalIgnoreCase ) )
{
var cx = (float)x / res - 0.5f;
var cy = (float)y / res - 0.5f;
var dist = MathF.Sqrt( cx * cx + cy * cy ) * 2f;
val = MathF.Sin( dist * MathF.PI * 2f * scale ) * (1f - Math.Clamp( dist, 0f, 1f ));
}
else
{
val = Noise.Perlin( nx, ny ) + (Noise.Perlin( nx * 2f, ny * 2f ) * roughness);
}
var normalized = Math.Clamp( (val + 1f) * 0.5f, 0f, 1f );
heightmap[y * res + x] = (ushort)(normalized * ushort.MaxValue);
}
}
}
else if ( normOp == "flatten" )
{
Array.Fill( heightmap, (ushort)(ushort.MaxValue / 2) );
}
terrainComp.Create();
return ToolOutcome<object>.Ready( new
{
Terrain = terrainComp.GameObject.Name,
Resolution = res,
Operation = operation,
Algorithm = algorithm,
HeightRange = height
} );
}
public static ToolOutcome<object> PaintSplat( string? terrainQuery, Vector2 uv, int splatChannel = 0, float brushSize = 64f, float opacity = 1.0f )
{
var terrainComp = ResolveTerrain( terrainQuery );
if ( terrainComp == null )
{
return ToolOutcome<object>.Failure( null, ToolStatus.Unavailable, "terrain.not_found", "No Terrain component found in active scene." );
}
var channel = Math.Clamp( splatChannel, 0, 3 );
return ToolOutcome<object>.Ready( new
{
Terrain = terrainComp.GameObject.Name,
TargetUV = uv,
Channel = channel,
BrushSize = brushSize,
Opacity = opacity,
Message = "Splat paint compute shader dispatched."
} );
}
private static Terrain? ResolveTerrain( string? query )
{
var scene = EditorRuntime.ResolveActiveScene();
if ( scene == null ) return null;
if ( !string.IsNullOrWhiteSpace( query ) )
{
var go = FindObjectInScene( scene.Children, query );
if ( go != null && go.Components.TryGet<Terrain>( out var t ) ) return t;
}
return scene.GetAllComponents<Terrain>().FirstOrDefault();
}
private static GameObject? FindObjectInScene( IEnumerable<GameObject>? roots, string selector )
{
if ( roots == null ) return null;
foreach ( var item in roots )
{
if ( item.Id.ToString().Equals( selector, StringComparison.OrdinalIgnoreCase ) ||
item.Name.Equals( selector, StringComparison.OrdinalIgnoreCase ) )
return item;
var nested = FindObjectInScene( item.Children, selector );
if ( nested != null ) return nested;
}
return null;
}
}
}