// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#include "Terrain.h"
#include "TerrainChunk.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Level/Scene/Lightmap.h"
#include "Engine/Content/Assets/RawDataAsset.h"
struct RayCastHit;
class TerrainMaterialShader;
///
/// Represents single terrain patch made of 16 terrain chunks.
///
API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API TerrainPatch : public ScriptingObject, public ISerializable
{
DECLARE_SCRIPTING_TYPE(TerrainPatch);
friend Terrain;
friend TerrainPatch;
friend TerrainChunk;
private:
Terrain* _terrain;
int16 _x, _z;
float _yOffset, _yHeight;
BoundingBox _bounds;
Float3 _offset;
AssetReference _heightfield;
void* _physicsShape;
void* _physicsActor;
void* _physicsHeightField;
CriticalSection _collisionLocker;
float _collisionScaleXZ;
#if TERRAIN_UPDATING
Array _cachedHeightMap;
Array _cachedHolesMask;
Array _cachedSplatMap[TERRAIN_MAX_SPLATMAPS_COUNT];
bool _wasHeightModified;
bool _wasSplatmapModified[TERRAIN_MAX_SPLATMAPS_COUNT];
TextureBase::InitData* _dataHeightmap = nullptr;
TextureBase::InitData* _dataSplatmap[TERRAIN_MAX_SPLATMAPS_COUNT] = {};
#endif
#if TERRAIN_USE_PHYSICS_DEBUG
Array _debugLines; // TODO: large-worlds
#endif
#if USE_EDITOR
Array _collisionTriangles; // TODO: large-worlds
#endif
Array _collisionVertices; // TODO: large-worlds
void Init(Terrain* terrain, int16 x, int16 z);
public:
///
/// Finalizes an instance of the class.
///
~TerrainPatch();
public:
///
/// The chunks contained within the patch. Organized in 4x4 square.
///
TerrainChunk Chunks[Terrain::ChunksCount];
///
/// The heightmap texture.
///
API_FIELD() AssetReference Heightmap;
///
/// The splatmap textures.
///
AssetReference Splatmap[TERRAIN_MAX_SPLATMAPS_COUNT];
public:
///
/// Gets the Y axis heightmap offset from terrain origin.
///
API_FUNCTION() FORCE_INLINE float GetOffsetY() const
{
return _yOffset;
}
///
/// Gets the Y axis heightmap height.
///
API_FUNCTION() FORCE_INLINE float GetHeightY() const
{
return _yHeight;
}
///
/// Gets the x coordinate.
///
API_FUNCTION() FORCE_INLINE int32 GetX() const
{
return _x;
}
///
/// Gets the z coordinate.
///
API_FUNCTION() FORCE_INLINE int32 GetZ() const
{
return _z;
}
///
/// Gets the terrain.
///
API_FUNCTION() FORCE_INLINE Terrain* GetTerrain() const
{
return _terrain;
}
///
/// Gets the chunk at the given index.
///
/// The chunk zero-based index.
/// The chunk.
API_FUNCTION() TerrainChunk* GetChunk(int32 index)
{
if (index < 0 || index >= Terrain::ChunksCount)
return nullptr;
return &Chunks[index];
}
///
/// Gets the chunk at the given location.
///
/// The chunk location (x and z).
/// The chunk.
API_FUNCTION() TerrainChunk* GetChunk(API_PARAM(Ref) const Int2& chunkCoord)
{
return GetChunk(chunkCoord.Y * Terrain::ChunksCountEdge + chunkCoord.X);
}
///
/// Gets the chunk at the given location.
///
/// The chunk location x.
/// The chunk location z.
/// The chunk.
API_FUNCTION() TerrainChunk* GetChunk(int32 x, int32 z)
{
return GetChunk(z * Terrain::ChunksCountEdge + x);
}
///
/// Gets the splatmap assigned to this patch.
///
/// The zero-based index of the splatmap.
/// The splatmap texture.
API_FUNCTION() AssetReference GetSplatmap(int32 index)
{
if (index < 0 || index >= TERRAIN_MAX_SPLATMAPS_COUNT)
return nullptr;
return Splatmap[index];
}
///
/// Sets a splatmap to this patch.
///
/// The zero-based index of the splatmap.
/// Splatmap texture.
API_FUNCTION() void SetSplatmap(int32 index, const AssetReference& splatMap)
{
if (index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT)
Splatmap[index] = splatMap;
}
///
/// Gets the patch world bounds.
///
API_FUNCTION() FORCE_INLINE const BoundingBox& GetBounds() const
{
return _bounds;
}
public:
///
/// Removes the lightmap data from the terrain patch.
///
void RemoveLightmap();
///
/// Updates the cached bounds of the patch and child chunks.
///
void UpdateBounds();
///
/// Updates the cached transform of the patch and child chunks.
///
void UpdateTransform();
#if TERRAIN_EDITING
///
/// Initializes the patch heightmap and collision to the default flat level.
///
/// True if failed, otherwise false.
API_FUNCTION() bool InitializeHeightMap();
///
/// Setups the terrain patch using the specified heightmap data.
///
/// 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 SetupHeightMap(int32 heightMapLength, API_PARAM(Ref) const float* heightMap, API_PARAM(Ref) const byte* holesMask = nullptr, bool forceUseVirtualStorage = false);
///
/// Setups the terrain patch layer weights using the specified splatmaps data.
///
/// 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 SetupSplatMap(int32 index, int32 splatMapLength, API_PARAM(Ref) const Color32* splatMap, bool forceUseVirtualStorage = false);
#endif
#if TERRAIN_UPDATING
///
/// Gets the raw pointer to the heightmap data.
///
/// The heightmap data.
API_FUNCTION() float* GetHeightmapData();
///
/// Clears cache of the heightmap data.
///
API_FUNCTION() void ClearHeightmapCache();
///
/// Gets the raw pointer to the holes mask data.
///
/// The holes mask data.
API_FUNCTION() byte* GetHolesMaskData();
///
/// Clears cache of the holes mask data.
///
API_FUNCTION() void ClearHolesMaskCache();
///
/// Gets the raw pointer to the splat map data.
///
/// The zero-based index of the splatmap texture.
/// The splat map data.
API_FUNCTION() Color32* GetSplatMapData(int32 index);
///
/// Clears cache of the splat map data.
///
API_FUNCTION() void ClearSplatMapCache();
///
/// Clears all caches.
///
API_FUNCTION() void ClearCache();
///
/// Modifies the terrain patch heightmap with the given samples.
///
/// The samples. The array length is size.X*size.Y.
/// 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);
///
/// Modifies the terrain patch holes mask with the given samples.
///
/// The samples. The array length is size.X*size.Y.
/// 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);
///
/// Modifies the terrain patch splat map (layers mask) with the given samples.
///
/// The zero-based index of the splatmap texture.
/// The samples. The array length is size.X*size.Y.
/// 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);
private:
bool UpdateHeightData(struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged);
void SaveHeightData();
void CacheHeightData();
void SaveSplatData();
void SaveSplatData(int32 index);
void CacheSplatData();
#endif
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, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const;
///
/// 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 raycast result hit position normal vector. 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, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) Vector3& resultHitNormal, 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.
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) TerrainChunk*& resultChunk, 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(API_PARAM(Ref) const Vector3& position, API_PARAM(Out) Vector3& result) const;
#if USE_EDITOR
///
/// Updates the patch data after manual deserialization called at runtime (eg. by editor undo).
///
void UpdatePostManualDeserialization();
#endif
public:
#if USE_EDITOR
///
/// Gets the collision mesh triangles array (3 vertices per triangle in linear list). Cached internally to reuse data.
///
/// The collision triangles vertices list (in world-space).
const Array& GetCollisionTriangles();
///
/// Gets the collision mesh triangles array (3 vertices per triangle in linear list) that intersect with the given bounds.
///
/// The world-space bounds to find terrain triangles that intersect with it.
/// The result triangles that intersect with the given bounds (in world-space).
void GetCollisionTriangles(API_PARAM(Ref) const BoundingSphere& bounds, API_PARAM(Out) Array& result);
#endif
///
/// Extracts the collision data geometry into list of triangles.
///
/// The output vertex buffer.
/// The output index buffer.
API_FUNCTION() void ExtractCollisionGeometry(API_PARAM(Out) Array& vertexBuffer, API_PARAM(Out) Array& indexBuffer);
private:
///
/// Determines whether this patch has created collision representation.
///
FORCE_INLINE bool HasCollision() const
{
return _physicsShape != nullptr;
}
///
/// Creates the collision object for the patch.
///
void CreateCollision();
///
/// Creates the height field from the collision data and caches height field XZ scale parameter.
///
/// True if failed, otherwise false.
bool CreateHeightField();
///
/// Updates the collision geometry scale for the patch. Called when terrain actor scale gets changed.
///
void UpdateCollisionScale() const;
///
/// Deletes the collision object from the patch.
///
void DestroyCollision();
#if TERRAIN_USE_PHYSICS_DEBUG
void CacheDebugLines();
void DrawPhysicsDebug(RenderView& view);
#endif
///
/// Updates the collision heightfield.
///
/// True if failed, otherwise false.
bool UpdateCollision();
void OnPhysicsSceneChanged(PhysicsScene* previous);
public:
// [ISerializable]
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
};