// Copyright (c) 2012-2023 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.
///
class FLAXENGINE_API TerrainPatch : public ISerializable
{
friend Terrain;
friend TerrainPatch;
friend TerrainChunk;
public:
enum
{
CHUNKS_COUNT = 16,
CHUNKS_COUNT_EDGE = 4,
};
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[CHUNKS_COUNT];
///
/// The heightmap texture.
///
AssetReference Heightmap;
///
/// The splatmap textures.
///
AssetReference Splatmap[TERRAIN_MAX_SPLATMAPS_COUNT];
public:
///
/// Gets the Y axis heightmap offset from terrain origin.
///
/// The offset.
FORCE_INLINE float GetOffsetY() const
{
return _yOffset;
}
///
/// Gets the Y axis heightmap height.
///
/// The height.
FORCE_INLINE float GetHeightY() const
{
return _yHeight;
}
///
/// Gets the x coordinate.
///
/// The x position.
FORCE_INLINE int32 GetX() const
{
return _x;
}
///
/// Gets the z coordinate.
///
/// The z position.
FORCE_INLINE int32 GetZ() const
{
return _z;
}
///
/// Gets the terrain.
///
/// The terrain,
FORCE_INLINE Terrain* GetTerrain() const
{
return _terrain;
}
///
/// Gets the chunk at the given index.
///
/// The chunk zero-based index.
/// The chunk.
TerrainChunk* GetChunk(int32 index)
{
if (index < 0 || index >= CHUNKS_COUNT)
return nullptr;
return &Chunks[index];
}
///
/// Gets the chunk at the given location.
///
/// The chunk location (x and z).
/// The chunk.
TerrainChunk* GetChunk(const Int2& chunkCoord)
{
return GetChunk(chunkCoord.Y * CHUNKS_COUNT_EDGE + chunkCoord.X);
}
///
/// Gets the chunk at the given location.
///
/// The chunk location x.
/// The chunk location z.
/// The chunk.
TerrainChunk* GetChunk(int32 x, int32 z)
{
return GetChunk(z * CHUNKS_COUNT_EDGE + x);
}
///
/// Gets the patch world bounds.
///
/// The bounding box.
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.
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.
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.
///
/// 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.
bool SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false);
#endif
#if TERRAIN_UPDATING
///
/// Gets the raw pointer to the heightmap data.
///
/// The heightmap data.
float* GetHeightmapData();
///
/// Clears cache of the heightmap data.
///
void ClearHeightmapCache();
///
/// Gets the raw pointer to the holes mask data.
///
/// The holes mask data.
byte* GetHolesMaskData();
///
/// Clears cache of the holes mask data.
///
void ClearHolesMaskCache();
///
/// Gets the raw pointer to the splat map data.
///
/// The zero-based index of the splatmap texture.
/// The splat map data.
Color32* GetSplatMapData(int32 index);
///
/// Clears cache of the splat map data.
///
void ClearSplatMapCache();
///
/// Clears all caches.
///
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.
bool ModifyHeightMap(const float* samples, const Int2& modifiedOffset, 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.
bool ModifyHolesMask(const byte* samples, const Int2& modifiedOffset, 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.
bool ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize);
private:
bool UpdateHeightData(const struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged);
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.
bool RayCast(const Vector3& origin, const Vector3& direction, 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.
bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, 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.
bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, 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.
bool RayCast(const Vector3& origin, const Vector3& direction, 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.
void ClosestPoint(const Vector3& position, 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(const BoundingSphere& bounds, Array& result);
#endif
///
/// Extracts the collision data geometry into list of triangles.
///
/// The output vertex buffer.
/// The output index buffer.
void ExtractCollisionGeometry(Array& vertexBuffer, 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;
};