From c12553812cd74acf177175206980884f426931fc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 25 Mar 2026 22:31:36 +0100 Subject: [PATCH] Update `Terrain` scripting API --- Source/Editor/Editor.Build.cs | 1 + .../Editor/Tools/Terrain/EditTerrainGizmo.cs | 2 +- Source/Editor/Tools/Terrain/Paint/Mode.cs | 4 +-- Source/Editor/Tools/Terrain/Sculpt/Mode.cs | 4 +-- .../Terrain/Undo/EditTerrainMapAction.cs | 3 +- .../Viewport/Previews/MaterialPreview.cs | 3 +- .../Materials/TerrainMaterialShader.cpp | 5 ++- Source/Engine/Terrain/Terrain.Build.cs | 11 ++++++- Source/Engine/Terrain/Terrain.cpp | 10 ++++++ Source/Engine/Terrain/Terrain.h | 24 ++++++++++---- Source/Engine/Terrain/TerrainPatch.cpp | 29 +++++++++-------- Source/Engine/Terrain/TerrainPatch.h | 32 +++++++++++-------- 12 files changed, 81 insertions(+), 47 deletions(-) diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs index e75954909..3fd8ea98c 100644 --- a/Source/Editor/Editor.Build.cs +++ b/Source/Editor/Editor.Build.cs @@ -61,6 +61,7 @@ public class Editor : EditorModule options.PrivateDependencies.Add("Renderer"); options.PrivateDependencies.Add("TextureTool"); options.PrivateDependencies.Add("Particles"); + options.PrivateDependencies.Add("Terrain"); var platformToolsRoot = Path.Combine(FolderPath, "Cooker", "Platform"); var platformToolsRootExternal = Path.Combine(Globals.EngineRoot, "Source", "Platforms"); diff --git a/Source/Editor/Tools/Terrain/EditTerrainGizmo.cs b/Source/Editor/Tools/Terrain/EditTerrainGizmo.cs index 5fc0e894f..c6926f32e 100644 --- a/Source/Editor/Tools/Terrain/EditTerrainGizmo.cs +++ b/Source/Editor/Tools/Terrain/EditTerrainGizmo.cs @@ -89,7 +89,7 @@ namespace FlaxEditor.Tools.Terrain if (!terrain.HasPatch(ref patchCoord) && _planeModel) { var planeSize = 100.0f; - var patchSize = terrain.ChunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount; + var patchSize = terrain.PatchSize; Matrix world = Matrix.RotationX(-Mathf.PiOverTwo) * Matrix.Scaling(patchSize / planeSize) * Matrix.Translation(patchSize * (0.5f + patchCoord.X), 0, patchSize * (0.5f + patchCoord.Y)) * diff --git a/Source/Editor/Tools/Terrain/Paint/Mode.cs b/Source/Editor/Tools/Terrain/Paint/Mode.cs index cee3157d3..624a74c3f 100644 --- a/Source/Editor/Tools/Terrain/Paint/Mode.cs +++ b/Source/Editor/Tools/Terrain/Paint/Mode.cs @@ -69,9 +69,9 @@ namespace FlaxEditor.Tools.Terrain.Paint var splatmapIndex = ActiveSplatmapIndex; var splatmapIndexOther = (splatmapIndex + 1) % 2; var chunkSize = terrain.ChunkSize; - var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1; + var heightmapSize = terrain.HeightmapSize; var heightmapLength = heightmapSize * heightmapSize; - var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount; + var patchSize = terrain.PatchSize; var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, splatmapIndex).ToPointer(); var tempBufferOther = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, (splatmapIndex + 1) % 2).ToPointer(); var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex; diff --git a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs index f37902f39..55d11b21a 100644 --- a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs +++ b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs @@ -70,9 +70,9 @@ namespace FlaxEditor.Tools.Terrain.Sculpt // Prepare var chunkSize = terrain.ChunkSize; - var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1; + var heightmapSize = terrain.HeightmapSize; var heightmapLength = heightmapSize * heightmapSize; - var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount; + var patchSize = terrain.PatchSize; var tempBuffer = (float*)gizmo.GetHeightmapTempBuffer(heightmapLength * sizeof(float)).ToPointer(); var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex; diff --git a/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs b/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs index afac0948e..93fc0470e 100644 --- a/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs +++ b/Source/Editor/Tools/Terrain/Undo/EditTerrainMapAction.cs @@ -85,8 +85,7 @@ namespace FlaxEditor.Tools.Terrain.Undo { _terrain = terrain.ID; _patches = new List(4); - var chunkSize = terrain.ChunkSize; - var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1; + var heightmapSize = terrain.HeightmapSize; _heightmapLength = heightmapSize * heightmapSize; _heightmapDataSize = _heightmapLength * stride; diff --git a/Source/Editor/Viewport/Previews/MaterialPreview.cs b/Source/Editor/Viewport/Previews/MaterialPreview.cs index 8ba8a00ee..e494df14a 100644 --- a/Source/Editor/Viewport/Previews/MaterialPreview.cs +++ b/Source/Editor/Viewport/Previews/MaterialPreview.cs @@ -303,8 +303,7 @@ namespace FlaxEditor.Viewport.Previews { _terrain = new Terrain(); _terrain.Setup(1, 63); - var chunkSize = _terrain.ChunkSize; - var heightMapSize = chunkSize * Terrain.PatchEdgeChunksCount + 1; + var heightMapSize = _terrain.HeightmapSize; var heightMapLength = heightMapSize * heightMapSize; var heightmap = new float[heightMapLength]; var patchCoord = new Int2(0, 0); diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp index b6b455f20..30aa8b280 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp @@ -88,9 +88,8 @@ void TerrainMaterialShader::Bind(BindParameters& params) } // Bind terrain textures - const auto heightmap = drawCall.Terrain.Patch->Heightmap->GetTexture(); - const auto splatmap0 = drawCall.Terrain.Patch->Splatmap[0] ? drawCall.Terrain.Patch->Splatmap[0]->GetTexture() : nullptr; - const auto splatmap1 = drawCall.Terrain.Patch->Splatmap[1] ? drawCall.Terrain.Patch->Splatmap[1]->GetTexture() : nullptr; + GPUTexture* heightmap, *splatmap0, *splatmap1; + drawCall.Terrain.Patch->GetTextures(heightmap, splatmap0, splatmap1); context->BindSR(0, heightmap); context->BindSR(1, splatmap0); context->BindSR(2, splatmap1); diff --git a/Source/Engine/Terrain/Terrain.Build.cs b/Source/Engine/Terrain/Terrain.Build.cs index 178938da8..55a590dd2 100644 --- a/Source/Engine/Terrain/Terrain.Build.cs +++ b/Source/Engine/Terrain/Terrain.Build.cs @@ -8,13 +8,22 @@ using Flax.Build.NativeCpp; /// public class Terrain : EngineModule { + /// + /// Enables terrain editing and changing at runtime. If your game doesn't use procedural terrain in game then disable this option to reduce build size. + /// + public static bool WithEditing = true; + /// public override void Setup(BuildOptions options) { base.Setup(options); - options.PrivateDependencies.Add("Physics"); + if (!WithEditing) + { + options.PublicDefinitions.Add("TERRAIN_EDITING=0"); + } + options.PrivateDependencies.Add("Physics"); if (options.Target.IsEditor) { options.PrivateDependencies.Add("ContentImporters"); diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index f0ad65894..9185f71a9 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -306,6 +306,16 @@ void Terrain::SetPhysicalMaterials(const Array(_chunkSize); } + /// + /// Gets the heightmap texture size (square) used by a single patch (shared by all chunks within that patch). + /// + /// ChunkSize * ChunksCountEdge + 1 + API_PROPERTY() int32 GetHeightmapSize() const; + + /// + /// Gets the size of the patch in world-units (square) without actor scale. + /// + /// UnitsPerVertex * ChunksCountEdge * ChunkSize + API_PROPERTY() float GetPatchSize() const; + /// /// Gets the terrain patches count. Each patch contains 16 chunks arranged into a 4x4 square. /// @@ -329,7 +344,6 @@ public: API_FUNCTION() void SetChunkOverrideMaterial(API_PARAM(Ref) const Int2& patchCoord, API_PARAM(Ref) const Int2& chunkCoord, MaterialBase* value); #if TERRAIN_EDITING - /// /// Setups the terrain patch using the specified heightmap data. /// @@ -352,10 +366,6 @@ public: /// True if failed, otherwise false. API_FUNCTION() bool SetupPatchSplatMap(API_PARAM(Ref) const Int2& patchCoord, int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false); -#endif - -public: -#if TERRAIN_EDITING /// /// Setups the terrain. Clears the existing data. /// diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 61bbf7e3c..cadc9821c 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -17,6 +17,7 @@ #include "Engine/Threading/Threading.h" #if TERRAIN_EDITING #include "Engine/Core/Math/Packed.h" +#include "Engine/Core/Collections/ArrayExtensions.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderView.h" @@ -27,11 +28,6 @@ #include "Editor/Editor.h" #include "Engine/ContentImporters/AssetsImportingManager.h" #endif -#endif -#if TERRAIN_EDITING || TERRAIN_UPDATING -#include "Engine/Core/Collections/ArrayExtensions.h" -#endif -#if USE_EDITOR #include "Engine/Debug/DebugDraw.h" #endif #if TERRAIN_USE_PHYSICS_DEBUG @@ -90,7 +86,7 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z) Splatmap[i] = nullptr; } _heightfield = nullptr; -#if TERRAIN_UPDATING +#if TERRAIN_EDITING _cachedHeightMap.Resize(0); _cachedHolesMask.Resize(0); _wasHeightModified = false; @@ -114,7 +110,7 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z) TerrainPatch::~TerrainPatch() { -#if TERRAIN_UPDATING +#if TERRAIN_EDITING SAFE_DELETE(_dataHeightmap); for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++) { @@ -134,6 +130,13 @@ RawDataAsset* TerrainPatch::GetHeightfield() const return _heightfield.Get(); } +void TerrainPatch::GetTextures(GPUTexture*& heightmap, GPUTexture*& splatmap0, GPUTexture*& splatmap1) const +{ + heightmap = Heightmap->GetTexture(); + splatmap0 = Splatmap[0] ? Splatmap[0]->GetTexture() : nullptr; + splatmap1 = Splatmap[1] ? Splatmap[1]->GetTexture() : nullptr; +} + void TerrainPatch::RemoveLightmap() { for (auto& chunk : Chunks) @@ -178,7 +181,7 @@ void TerrainPatch::UpdateTransform() _collisionVertices.Resize(0); } -#if TERRAIN_EDITING || TERRAIN_UPDATING +#if TERRAIN_EDITING bool IsValidMaterial(const JsonAssetReference& e) { @@ -217,7 +220,7 @@ struct TerrainDataUpdateInfo // When using physical materials, then get splatmaps data required for per-triangle material indices void GetSplatMaps() { -#if TERRAIN_UPDATING +#if TERRAIN_EDITING if (SplatMaps[0]) return; if (UsePhysicalMaterials()) @@ -1021,7 +1024,7 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, _terrain->UpdateBounds(); _terrain->UpdateLayerBits(); -#if TERRAIN_UPDATING +#if TERRAIN_EDITING // Invalidate cache _cachedHeightMap.Resize(0); _cachedHolesMask.Resize(0); @@ -1169,7 +1172,7 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3 } #endif -#if TERRAIN_UPDATING +#if TERRAIN_EDITING // Invalidate cache _cachedSplatMap[index].Resize(0); _wasSplatmapModified[index] = false; @@ -1191,7 +1194,7 @@ bool TerrainPatch::InitializeHeightMap() return SetupHeightMap(heightmap.Count(), heightmap.Get()); } -#if TERRAIN_UPDATING +#if TERRAIN_EDITING float* TerrainPatch::GetHeightmapData() { @@ -2631,7 +2634,7 @@ void TerrainPatch::Serialize(SerializeStream& stream, const void* otherObj) } stream.EndArray(); -#if TERRAIN_UPDATING +#if TERRAIN_EDITING SaveHeightData(); SaveSplatData(); #endif diff --git a/Source/Engine/Terrain/TerrainPatch.h b/Source/Engine/Terrain/TerrainPatch.h index 7d85c5b1c..79e56e6da 100644 --- a/Source/Engine/Terrain/TerrainPatch.h +++ b/Source/Engine/Terrain/TerrainPatch.h @@ -12,6 +12,10 @@ struct RayCastHit; class TerrainMaterialShader; +#ifndef TERRAIN_EDITING +#define TERRAIN_EDITING 1 +#endif + /// /// Represents single terrain patch made of 16 terrain chunks. /// @@ -34,7 +38,7 @@ private: void* _physicsHeightField; CriticalSection _collisionLocker; float _collisionScaleXZ; -#if TERRAIN_UPDATING +#if TERRAIN_EDITING Array _cachedHeightMap; Array _cachedHolesMask; Array _cachedSplatMap[TERRAIN_MAX_SPLATMAPS_COUNT]; @@ -189,6 +193,8 @@ public: return _bounds; } + void GetTextures(GPUTexture*& heightmap, GPUTexture*& splatmap0, GPUTexture*& splatmap1) const; + public: /// /// Removes the lightmap data from the terrain patch. @@ -220,7 +226,7 @@ public: /// The holes mask (optional). Normalized to 0-1 range values with holes mask per-vertex. Must match the heightmap dimensions. /// If set to true patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM). /// True if failed, otherwise false. - API_FUNCTION() bool SetupHeightMap(int32 heightMapLength, API_PARAM(Ref) const float* heightMap, API_PARAM(Ref) const byte* holesMask = nullptr, bool forceUseVirtualStorage = false); + API_FUNCTION() bool SetupHeightMap(int32 heightMapLength, const float* heightMap, const byte* holesMask = nullptr, bool forceUseVirtualStorage = false); /// /// Setups the terrain patch layer weights using the specified splatmaps data. @@ -230,14 +236,12 @@ public: /// The splat map. Each array item contains 4 layer weights. /// If set to true patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM). /// True if failed, otherwise false. - API_FUNCTION() bool SetupSplatMap(int32 index, int32 splatMapLength, API_PARAM(Ref) const Color32* splatMap, bool forceUseVirtualStorage = false); -#endif + API_FUNCTION() bool SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false); -#if TERRAIN_UPDATING /// - /// Gets the raw pointer to the heightmap data. + /// Gets the raw pointer to the heightmap data. Array size is square of Terrain.HeightmapSize. /// - /// The heightmap data. + /// The heightmap data. Null if empty or failed to access it. API_FUNCTION() float* GetHeightmapData(); /// @@ -246,9 +250,9 @@ public: API_FUNCTION() void ClearHeightmapCache(); /// - /// Gets the raw pointer to the holes mask data. + /// Gets the raw pointer to the holes mask data. Array size is square of Terrain.HeightmapSize. /// - /// The holes mask data. + /// The holes mask data. Null if empty/unused or failed to access it. API_FUNCTION() byte* GetHolesMaskData(); /// @@ -257,10 +261,10 @@ public: API_FUNCTION() void ClearHolesMaskCache(); /// - /// Gets the raw pointer to the splat map data. + /// Gets the raw pointer to the splat map data. Array size is square of Terrain.HeightmapSize. /// /// The zero-based index of the splatmap texture. - /// The splat map data. + /// The splat map data. Null if empty/unused or failed to access it. API_FUNCTION() Color32* GetSplatMapData(int32 index); /// @@ -280,7 +284,7 @@ public: /// The offset from the first row and column of the heightmap data (offset destination x and z start position). /// The size of the heightmap to modify (x and z). Amount of samples in each direction. /// True if failed, otherwise false. - API_FUNCTION() bool ModifyHeightMap(API_PARAM(Ref) const float* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize); + API_FUNCTION() bool ModifyHeightMap(const float* samples, const Int2& modifiedOffset, const Int2& modifiedSize); /// /// Modifies the terrain patch holes mask with the given samples. @@ -289,7 +293,7 @@ public: /// The offset from the first row and column of the holes map data (offset destination x and z start position). /// The size of the holes map to modify (x and z). Amount of samples in each direction. /// True if failed, otherwise false. - API_FUNCTION() bool ModifyHolesMask(API_PARAM(Ref) const byte* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize); + API_FUNCTION() bool ModifyHolesMask(const byte* samples, const Int2& modifiedOffset, const Int2& modifiedSize); /// /// Modifies the terrain patch splat map (layers mask) with the given samples. @@ -299,7 +303,7 @@ public: /// The offset from the first row and column of the splat map data (offset destination x and z start position). /// The size of the splat map to modify (x and z). Amount of samples in each direction. /// True if failed, otherwise false. - API_FUNCTION() bool ModifySplatMap(int32 index, API_PARAM(Ref) const Color32* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize); + API_FUNCTION() bool ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize); private: bool UpdateHeightData(struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged);