// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Content/JsonAsset.h"
#include "Engine/Content/Assets/MaterialBase.h"
#include "Engine/Physics/Actors/PhysicsColliderActor.h"
class Terrain;
class TerrainChunk;
class TerrainPatch;
class TerrainManager;
struct RayCastHit;
struct RenderView;
// Maximum amount of levels of detail for the terrain chunks
#define TERRAIN_MAX_LODS 8
// Amount of units per terrain geometry vertex (can be adjusted per terrain instance using non-uniform scale factor)
#define TERRAIN_UNITS_PER_VERTEX 100.0f
// Enable/disable terrain editing and changing at runtime. If your game doesn't use procedural terrain in game then disable this option to reduce build size.
#define TERRAIN_EDITING 1
// Enable/disable terrain heightmap samples modification and gather. Used by the editor to modify the terrain with the brushes.
#define TERRAIN_UPDATING (USE_EDITOR)
// Enable/disable precise terrain geometry collision testing (with in-build vertex buffer caching, this will increase memory usage)
#define USE_PRECISE_TERRAIN_INTERSECTS (USE_EDITOR)
// Enable/disable terrain physics collision drawing
#define TERRAIN_USE_PHYSICS_DEBUG (USE_EDITOR && 1)
// Terrain splatmaps amount limit. Each splatmap can hold up to 4 layer weights.
#define TERRAIN_MAX_SPLATMAPS_COUNT 2
///
/// Represents a single terrain object.
///
///
///
API_CLASS(Sealed) class FLAXENGINE_API Terrain : public PhysicsColliderActor
{
DECLARE_SCENE_OBJECT(Terrain);
friend Terrain;
friend TerrainPatch;
friend TerrainChunk;
private:
char _lodBias;
char _forcedLod;
char _collisionLod;
byte _lodCount;
uint16 _chunkSize;
int32 _sceneRenderingKey = -1;
float _scaleInLightmap;
float _lodDistribution;
Vector3 _boundsExtent;
Float3 _cachedScale;
Array> _patches;
Array _drawChunks;
public:
///
/// Finalizes an instance of the class.
///
~Terrain();
public:
///
/// The default material used for terrain rendering (chunks can override this).
///
API_FIELD(Attributes="EditorOrder(100), DefaultValue(null), EditorDisplay(\"Terrain\")")
AssetReference Material;
///
/// The physical material used to define the terrain collider physical properties.
///
API_FIELD(Attributes="EditorOrder(520), DefaultValue(null), Limit(-1, 100, 0.1f), EditorDisplay(\"Collision\"), AssetReference(typeof(PhysicalMaterial), true)")
AssetReference PhysicalMaterial;
///
/// The draw passes to use for rendering this object.
///
API_FIELD(Attributes="EditorOrder(115), DefaultValue(DrawPass.Default), EditorDisplay(\"Terrain\")")
DrawPass DrawModes = DrawPass::Default;
public:
///
/// Gets the terrain Level Of Detail bias value. Allows to increase or decrease rendered terrain quality.
///
API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Terrain\", \"LOD Bias\")")
FORCE_INLINE int32 GetLODBias() const
{
return static_cast(_lodBias);
}
///
/// Sets the terrain Level Of Detail bias value. Allows to increase or decrease rendered terrain quality.
///
API_PROPERTY() void SetLODBias(int32 value)
{
_lodBias = static_cast(Math::Clamp(value, -100, 100));
}
///
/// Gets the terrain forced Level Of Detail index. Allows to bind the given chunks LOD to show. Value -1 disables this feature.
///
API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Terrain\", \"Forced LOD\")")
FORCE_INLINE int32 GetForcedLOD() const
{
return static_cast(_forcedLod);
}
///
/// Sets the terrain forced Level Of Detail index. Allows to bind the given chunks LOD to show. Value -1 disables this feature.
///
API_PROPERTY() void SetForcedLOD(int32 value)
{
_forcedLod = static_cast(Math::Clamp(value, -1, TERRAIN_MAX_LODS));
}
///
/// Gets the terrain LODs distribution parameter. Adjusts terrain chunks transitions distances. Use lower value to increase terrain quality or higher value to increase performance. Default value is 0.75.
///
API_PROPERTY(Attributes="EditorOrder(70), DefaultValue(0.6f), Limit(0, 5, 0.01f), EditorDisplay(\"Terrain\", \"LOD Distribution\")")
FORCE_INLINE float GetLODDistribution() const
{
return _lodDistribution;
}
///
/// Sets the terrain LODs distribution parameter. Adjusts terrain chunks transitions distances. Use lower value to increase terrain quality or higher value to increase performance. Default value is 0.75.
///
API_PROPERTY() void SetLODDistribution(float value);
///
/// Gets the terrain scale in lightmap (applied to all the chunks). Use value higher than 1 to increase baked lighting resolution.
///
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(0.1f), Limit(0, 10000, 0.1f), EditorDisplay(\"Terrain\", \"Scale In Lightmap\")")
FORCE_INLINE float GetScaleInLightmap() const
{
return _scaleInLightmap;
}
///
/// Sets the terrain scale in lightmap (applied to all the chunks). Use value higher than 1 to increase baked lighting resolution.
///
API_PROPERTY() void SetScaleInLightmap(float value);
///
/// Gets the terrain chunks bounds extent. Values used to expand terrain chunks bounding boxes. Use it when your terrain material is performing vertex offset operations on a GPU.
///
API_PROPERTY(Attributes="EditorOrder(120), EditorDisplay(\"Terrain\")")
FORCE_INLINE Vector3 GetBoundsExtent() const
{
return _boundsExtent;
}
///
/// Sets the terrain chunks bounds extent. Values used to expand terrain chunks bounding boxes. Use it when your terrain material is performing vertex offset operations on a GPU.
///
API_PROPERTY() void SetBoundsExtent(const Vector3& value);
///
/// Gets the terrain geometry LOD index used for collision.
///
API_PROPERTY(Attributes="EditorOrder(500), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Collision\", \"Collision LOD\")")
FORCE_INLINE int32 GetCollisionLOD() const
{
return _collisionLod;
}
///
/// Sets the terrain geometry LOD index used for collision.
///
API_PROPERTY() void SetCollisionLOD(int32 value);
///
/// Gets the terrain Level Of Detail count.
///
API_PROPERTY() FORCE_INLINE int32 GetLODCount() const
{
return static_cast(_lodCount);
}
///
/// Gets the terrain chunk vertices amount per edge (square).
///
API_PROPERTY() FORCE_INLINE int32 GetChunkSize() const
{
return static_cast(_chunkSize);
}
///
/// Gets the terrain patches count. Each patch contains 16 chunks arranged into a 4x4 square.
///
API_PROPERTY() FORCE_INLINE int32 GetPatchesCount() const
{
return _patches.Count();
}
///
/// Checks if terrain has the patch at the given coordinates.
///
/// The patch location (x and z).
/// True if has patch added, otherwise false.
API_FUNCTION() FORCE_INLINE bool HasPatch(API_PARAM(Ref) const Int2& patchCoord) const
{
return GetPatch(patchCoord) != nullptr;
}
///
/// Gets the patch at the given location.
///
/// The patch location (x and z).
/// The patch.
TerrainPatch* GetPatch(const Int2& patchCoord) const;
///
/// Gets the patch at the given location.
///
/// The patch location x.
/// The patch location z.
/// The patch.
TerrainPatch* GetPatch(int32 x, int32 z) const;
///
/// Gets the zero-based index of the terrain patch in the terrain patches collection.
///
/// The patch location (x and z).
/// The zero-based index of the terrain patch in the terrain patches collection. Returns -1 if patch coordinates are invalid.
API_FUNCTION() int32 GetPatchIndex(API_PARAM(Ref) const Int2& patchCoord) const;
///
/// Gets the patch at the given index.
///
/// The index.
/// The patch.
FORCE_INLINE TerrainPatch* GetPatch(int32 index) const
{
return _patches[index];
}
///
/// Gets the terrain patch coordinates (x and z) at the given index.
///
/// The zero-based index of the terrain patch in the terrain patches collection.
/// The patch location (x and z).
API_FUNCTION() void GetPatchCoord(int32 patchIndex, API_PARAM(Out) Int2& patchCoord) const;
///
/// Gets the terrain patch world bounds at the given index.
///
/// The zero-based index of the terrain patch in the terrain patches collection.
/// The patch world bounds.
API_FUNCTION() void GetPatchBounds(int32 patchIndex, API_PARAM(Out) BoundingBox& bounds) const;
///
/// Gets the terrain chunk world bounds at the given index.
///
/// The zero-based index of the terrain patch in the terrain patches collection.
/// The zero-based index of the terrain chunk in the terrain patch chunks collection (in range 0-15).
/// The chunk world bounds.
API_FUNCTION() void GetChunkBounds(int32 patchIndex, int32 chunkIndex, API_PARAM(Out) BoundingBox& bounds) const;
///
/// Gets the chunk material that overrides the terrain default one.
///
/// The patch coordinates (x and z).
/// The chunk coordinates (x and z).
/// The value.
API_FUNCTION() MaterialBase* GetChunkOverrideMaterial(API_PARAM(Ref) const Int2& patchCoord, API_PARAM(Ref) const Int2& chunkCoord) const;
///
/// Sets the chunk material to override the terrain default one.
///
/// The patch coordinates (x and z).
/// The chunk coordinates (x and z).
/// The value to set.
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.
///
/// The patch coordinates (x and z).
/// The height map array length. It must match the terrain descriptor, so it should be (chunkSize*4+1)^2. Patch is a 4 by 4 square made of chunks. Each chunk has chunkSize quads on edge.
/// The height map. Each array item contains a height value.
/// 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 SetupPatchHeightMap(API_PARAM(Ref) const Int2& patchCoord, int32 heightMapLength, const float* heightMap, const byte* holesMask = nullptr, bool forceUseVirtualStorage = false);
///
/// Setups the terrain patch layer weights using the specified splatmaps data.
///
/// The patch coordinates (x and z).
/// The zero-based index of the splatmap texture.
/// The splatmap map array length. It must match the terrain descriptor, so it should be (chunkSize*4+1)^2. Patch is a 4 by 4 square made of chunks. Each chunk has chunkSize quads on edge.
/// 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 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.
///
/// The LODs count. The actual amount of LODs may be lower due to provided chunk size (each LOD has 4 times less quads).
/// The size of the chunk (amount of quads per edge for the highest LOD). Must be power of two minus one (eg. 63 or 127).
API_FUNCTION() void Setup(int32 lodCount = 6, int32 chunkSize = 127);
///
/// Adds the patches to the terrain (clears existing ones).
///
/// The number of patches (x and z axis).
API_FUNCTION() void AddPatches(API_PARAM(Ref) const Int2& numberOfPatches);
///
/// Adds the patch.
///
/// The patch location (x and z).
API_FUNCTION() void AddPatch(API_PARAM(Ref) const Int2& patchCoord);
///
/// Removes the patch.
///
/// The patch location (x and z).
API_FUNCTION() void RemovePatch(API_PARAM(Ref) const Int2& patchCoord);
#endif
///
/// Updates the cached bounds of the actor. Updates the cached world bounds for every patch and chunk.
///
void UpdateBounds();
///
/// Caches the neighbor chunks of this terrain.
///
void CacheNeighbors();
///
/// Updates the collider shapes collisions/queries layer mask bits.
///
void UpdateLayerBits();
///
/// Removes the lightmap data from the terrain.
///
void RemoveLightmap();
public:
///
/// Performs a raycast against this terrain collision shape.
///
/// The origin of the ray.
/// The normalized direction of the ray.
/// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything.
/// The maximum distance the ray should check for collisions.
/// True if ray hits an object, otherwise false.
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const;
///
/// Performs a raycast against this terrain collision shape. Returns the hit chunk.
///
/// The origin of the ray.
/// The normalized direction of the ray.
/// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything.
/// The raycast result hit chunk. Valid only if raycast hits anything.
/// The maximum distance the ray should check for collisions.
/// True if ray hits an object, otherwise false.
bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, TerrainChunk*& resultChunk, float maxDistance = MAX_float) const;
///
/// Performs a raycast against this terrain collision shape. Returns the hit chunk.
///
/// The ray to test.
/// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything.
/// The raycast result hit terrain patch coordinates (x and z). Valid only if raycast hits anything.
/// The raycast result hit terrain chunk coordinates (relative to the patch, x and z). Valid only if raycast hits anything.
/// The maximum distance the ray should check for collisions.
/// True if ray hits an object, otherwise false.
API_FUNCTION() bool RayCast(const Ray& ray, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) Int2& resultPatchCoord, API_PARAM(Out) Int2& resultChunkCoord, float maxDistance = MAX_float) const;
///
/// Performs a raycast against terrain collision, returns results in a RayCastHit structure.
///
/// The origin of the ray.
/// The normalized direction of the ray.
/// The result hit information. Valid only when method returns true.
/// The maximum distance the ray should check for collisions.
/// True if ray hits an object, otherwise false.
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const;
///
/// Gets a point on the terrain collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects.
///
/// The position to find the closest point to it.
/// The result point on the collider that is closest to the specified location.
API_FUNCTION() void ClosestPoint(const Vector3& position, API_PARAM(Out) Vector3& result) const;
///
/// Draws the terrain patch.
///
/// The rendering context.
/// The patch location (x and z).
/// The material to use for rendering.
/// The LOD index.
API_FUNCTION() void DrawPatch(API_PARAM(Ref) const RenderContext& renderContext, API_PARAM(Ref) const Int2& patchCoord, MaterialBase* material, int32 lodIndex = 0) const;
///
/// Draws the terrain chunk.
///
/// The rendering context.
/// The patch location (x and z).
/// The chunk location (x and z).
/// The material to use for rendering.
/// The LOD index.
API_FUNCTION() void DrawChunk(API_PARAM(Ref) const RenderContext& renderContext, API_PARAM(Ref) const Int2& patchCoord, API_PARAM(Ref) const Int2& chunkCoord, MaterialBase* material, int32 lodIndex = 0) const;
private:
void OnPhysicalMaterialChanged();
#if TERRAIN_USE_PHYSICS_DEBUG
void DrawPhysicsDebug(RenderView& view);
#endif
public:
// [PhysicsColliderActor]
void Draw(RenderContext& renderContext) override;
#if USE_EDITOR
void OnDebugDrawSelected() override;
#endif
void OnLayerChanged() override;
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
RigidBody* GetAttachedRigidBody() const override;
protected:
// [PhysicsColliderActor]
void OnEnable() override;
void OnDisable() override;
void OnTransformChanged() override;
void OnActiveInTreeChanged() override;
void OnPhysicsSceneChanged(PhysicsScene* previous) override;
void BeginPlay(SceneBeginData* data) override;
void EndPlay() override;
};