// 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; };