Add support for multiple physical materials in terrain - one for each painted layer
#1112 #2159
This commit is contained in:
@@ -45,9 +45,6 @@ namespace FlaxEditor.Tools.Terrain
|
||||
[EditorOrder(130), EditorDisplay("Layout"), DefaultValue(null), Tooltip("The default material used for terrain rendering (chunks can override this). It must have Domain set to terrain.")]
|
||||
public MaterialBase Material;
|
||||
|
||||
[EditorOrder(200), EditorDisplay("Collision"), DefaultValue(null), AssetReference(typeof(PhysicalMaterial), true), Tooltip("Terrain default physical material used to define the collider physical properties.")]
|
||||
public JsonAsset PhysicalMaterial;
|
||||
|
||||
[EditorOrder(210), EditorDisplay("Collision", "Collision LOD"), DefaultValue(-1), Limit(-1, 100, 0.1f), Tooltip("Terrain geometry LOD index used for collision.")]
|
||||
public int CollisionLOD = -1;
|
||||
|
||||
@@ -152,7 +149,6 @@ namespace FlaxEditor.Tools.Terrain
|
||||
terrain.Setup(_options.LODCount, (int)_options.ChunkSize);
|
||||
terrain.Transform = new Transform(_options.Position, _options.Orientation, _options.Scale);
|
||||
terrain.Material = _options.Material;
|
||||
terrain.PhysicalMaterial = _options.PhysicalMaterial;
|
||||
terrain.CollisionLOD = _options.CollisionLOD;
|
||||
if (_options.Heightmap)
|
||||
terrain.Position -= new Vector3(0, _options.HeightmapScale * 0.5f, 0);
|
||||
|
||||
@@ -644,6 +644,19 @@ void GetShapeGeometry(const CollisionShape& shape, PxGeometryHolder& geometry)
|
||||
}
|
||||
}
|
||||
|
||||
void GetShapeMaterials(Array<PxMaterial*, InlinedAllocation<1>>& materialsPhysX, Span<JsonAsset*> materials)
|
||||
{
|
||||
materialsPhysX.Resize(materials.Length());
|
||||
for (int32 i = 0; i < materials.Length(); i++)
|
||||
{
|
||||
PxMaterial* materialPhysX = DefaultMaterial;
|
||||
const JsonAsset* material = materials.Get()[i];
|
||||
if (material && !material->WaitForLoaded() && material->Instance)
|
||||
materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial();
|
||||
materialsPhysX.Get()[i] = materialPhysX;
|
||||
}
|
||||
}
|
||||
|
||||
PxFilterFlags FilterShader(
|
||||
PxFilterObjectAttributes attributes0, PxFilterData filterData0,
|
||||
PxFilterObjectAttributes attributes1, PxFilterData filterData1,
|
||||
@@ -2449,17 +2462,14 @@ void PhysicsBackend::AddRigidDynamicActorTorque(void* actor, const Vector3& torq
|
||||
actorPhysX->addTorque(C2P(torque), static_cast<PxForceMode::Enum>(mode));
|
||||
}
|
||||
|
||||
void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger)
|
||||
void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span<JsonAsset*> materials, bool enabled, bool trigger)
|
||||
{
|
||||
const PxShapeFlags shapeFlags = GetShapeFlags(trigger, enabled);
|
||||
PxMaterial* materialPhysX = DefaultMaterial;
|
||||
if (material && !material->WaitForLoaded() && material->Instance)
|
||||
{
|
||||
materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial();
|
||||
}
|
||||
Array<PxMaterial*, InlinedAllocation<1>> materialsPhysX;
|
||||
GetShapeMaterials(materialsPhysX, materials);
|
||||
PxGeometryHolder geometryPhysX;
|
||||
GetShapeGeometry(geometry, geometryPhysX);
|
||||
PxShape* shapePhysX = PhysX->createShape(geometryPhysX.any(), *materialPhysX, true, shapeFlags);
|
||||
PxShape* shapePhysX = PhysX->createShape(geometryPhysX.any(), materialsPhysX.Get(), materialsPhysX.Count(), true, shapeFlags);
|
||||
shapePhysX->userData = collider;
|
||||
#if PHYSX_DEBUG_NAMING
|
||||
shapePhysX->setName("Shape");
|
||||
@@ -2549,15 +2559,12 @@ void PhysicsBackend::SetShapeContactOffset(void* shape, float value)
|
||||
shapePhysX->setContactOffset(Math::Max(shapePhysX->getRestOffset() + ZeroTolerance, value));
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetShapeMaterial(void* shape, JsonAsset* material)
|
||||
void PhysicsBackend::SetShapeMaterials(void* shape, Span<JsonAsset*> materials)
|
||||
{
|
||||
auto shapePhysX = (PxShape*)shape;
|
||||
PxMaterial* materialPhysX = DefaultMaterial;
|
||||
if (material && !material->WaitForLoaded() && material->Instance)
|
||||
{
|
||||
materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial();
|
||||
}
|
||||
shapePhysX->setMaterials(&materialPhysX, 1);
|
||||
Array<PxMaterial*, InlinedAllocation<1>> materialsPhysX;
|
||||
GetShapeMaterials(materialsPhysX, materials);
|
||||
shapePhysX->setMaterials(materialsPhysX.Get(), materialsPhysX.Count());
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetShapeGeometry(void* shape, const CollisionShape& geometry)
|
||||
@@ -4077,10 +4084,17 @@ void PhysicsBackend::GetHeightFieldSize(void* heightField, int32& rows, int32& c
|
||||
columns = (int32)heightFieldPhysX->getNbColumns();
|
||||
}
|
||||
|
||||
float PhysicsBackend::GetHeightFieldHeight(void* heightField, float x, float z)
|
||||
float PhysicsBackend::GetHeightFieldHeight(void* heightField, int32 x, int32 z)
|
||||
{
|
||||
auto heightFieldPhysX = (PxHeightField*)heightField;
|
||||
return heightFieldPhysX->getHeight(x, z);
|
||||
return heightFieldPhysX->getHeight((float)x, (float)z);
|
||||
}
|
||||
|
||||
PhysicsBackend::HeightFieldSample PhysicsBackend::GetHeightFieldSample(void* heightField, int32 x, int32 z)
|
||||
{
|
||||
auto heightFieldPhysX = (PxHeightField*)heightField;
|
||||
auto sample = heightFieldPhysX->getSample(x, z);
|
||||
return { sample.height, sample.materialIndex0, sample.materialIndex1 };
|
||||
}
|
||||
|
||||
bool PhysicsBackend::ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "Physics.h"
|
||||
#include "PhysicsSettings.h"
|
||||
#include "Engine/Core/Types/Span.h"
|
||||
|
||||
struct HingeJointDrive;
|
||||
struct SpringParameters;
|
||||
@@ -182,7 +183,7 @@ public:
|
||||
static void AddRigidDynamicActorTorque(void* actor, const Vector3& torque, ForceMode mode);
|
||||
|
||||
// Shapes
|
||||
static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger);
|
||||
static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span<JsonAsset*> materials, bool enabled, bool trigger);
|
||||
static void SetShapeState(void* shape, bool enabled, bool trigger);
|
||||
static void SetShapeFilterMask(void* shape, uint32 mask0, uint32 mask1);
|
||||
static void* GetShapeActor(void* shape);
|
||||
@@ -191,7 +192,7 @@ public:
|
||||
static void GetShapeLocalPose(void* shape, Vector3& position, Quaternion& orientation);
|
||||
static void SetShapeLocalPose(void* shape, const Vector3& position, const Quaternion& orientation);
|
||||
static void SetShapeContactOffset(void* shape, float value);
|
||||
static void SetShapeMaterial(void* shape, JsonAsset* material);
|
||||
static void SetShapeMaterials(void* shape, Span<JsonAsset*> materials);
|
||||
static void SetShapeGeometry(void* shape, const CollisionShape& geometry);
|
||||
static void AttachShape(void* shape, void* actor);
|
||||
static void DetachShape(void* shape, void* actor);
|
||||
@@ -303,7 +304,8 @@ public:
|
||||
static void GetTriangleMeshTriangles(void* triangleMesh, Array<Float3, HeapAllocation>& vertexBuffer, Array<int32, HeapAllocation>& indexBuffer);
|
||||
static const uint32* GetTriangleMeshRemap(void* triangleMesh, uint32& count);
|
||||
static void GetHeightFieldSize(void* heightField, int32& rows, int32& columns);
|
||||
static float GetHeightFieldHeight(void* heightField, float x, float z);
|
||||
static float GetHeightFieldHeight(void* heightField, int32 x, int32 z);
|
||||
static HeightFieldSample GetHeightFieldSample(void* heightField, int32 x, int32 z);
|
||||
static bool ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data);
|
||||
static void FlushRequests();
|
||||
static void FlushRequests(void* scene);
|
||||
@@ -330,6 +332,14 @@ public:
|
||||
flags = (RigidDynamicFlags)(((uint32)flags & ~(uint32)flag) | (value ? (uint32)flag : 0));
|
||||
SetRigidDynamicActorFlags(actor, flags);
|
||||
}
|
||||
FORCE_INLINE static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger)
|
||||
{
|
||||
return CreateShape(collider, geometry, Span<JsonAsset*>(&material, 1), enabled, trigger);
|
||||
}
|
||||
FORCE_INLINE static void SetShapeMaterial(void* shape, JsonAsset* material)
|
||||
{
|
||||
SetShapeMaterials(shape, Span<JsonAsset*>(&material, 1));
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_ENUM_OPERATORS(PhysicsBackend::ActorFlags);
|
||||
|
||||
@@ -408,7 +408,7 @@ void PhysicsBackend::AddRigidDynamicActorTorque(void* actor, const Vector3& torq
|
||||
{
|
||||
}
|
||||
|
||||
void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger)
|
||||
void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span<JsonAsset*> materials, bool enabled, bool trigger)
|
||||
{
|
||||
return DUMY_HANDLE;
|
||||
}
|
||||
@@ -447,7 +447,7 @@ void PhysicsBackend::SetShapeContactOffset(void* shape, float value)
|
||||
{
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetShapeMaterial(void* shape, JsonAsset* material)
|
||||
void PhysicsBackend::SetShapeMaterials(void* shape, Span<JsonAsset*> materials)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -826,11 +826,16 @@ void PhysicsBackend::GetHeightFieldSize(void* heightField, int32& rows, int32& c
|
||||
columns = 0;
|
||||
}
|
||||
|
||||
float PhysicsBackend::GetHeightFieldHeight(void* heightField, float x, float z)
|
||||
float PhysicsBackend::GetHeightFieldHeight(void* heightField, int32 x, int32 z)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
PhysicsBackend::HeightFieldSample PhysicsBackend::GetHeightFieldSample(void* heightField, int32 x, int32 z)
|
||||
{
|
||||
return HeightFieldSample();
|
||||
}
|
||||
|
||||
bool PhysicsBackend::ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data)
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -29,7 +29,7 @@ Terrain::Terrain(const SpawnParams& params)
|
||||
, _cachedScale(1.0f)
|
||||
{
|
||||
_drawCategory = SceneRendering::SceneDrawAsync;
|
||||
PhysicalMaterial.Changed.Bind<Terrain, &Terrain::OnPhysicalMaterialChanged>(this);
|
||||
_physicalMaterials.Resize(8);
|
||||
}
|
||||
|
||||
Terrain::~Terrain()
|
||||
@@ -228,22 +228,6 @@ void Terrain::DrawChunk(const RenderContext& renderContext, const Int2& patchCoo
|
||||
}
|
||||
}
|
||||
|
||||
void Terrain::OnPhysicalMaterialChanged()
|
||||
{
|
||||
if (_patches.IsEmpty())
|
||||
return;
|
||||
|
||||
// Update the shapes material
|
||||
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
|
||||
{
|
||||
const auto patch = _patches[pathIndex];
|
||||
if (patch->HasCollision())
|
||||
{
|
||||
PhysicsBackend::SetShapeMaterial(patch->_physicsShape, PhysicalMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if TERRAIN_USE_PHYSICS_DEBUG
|
||||
|
||||
void Terrain::DrawPhysicsDebug(RenderView& view)
|
||||
@@ -295,6 +279,21 @@ void Terrain::SetCollisionLOD(int32 value)
|
||||
#endif
|
||||
}
|
||||
|
||||
void Terrain::SetPhysicalMaterials(const Array<JsonAssetReference<PhysicalMaterial>, FixedAllocation<8>>& value)
|
||||
{
|
||||
_physicalMaterials = value;
|
||||
_physicalMaterials.Resize(8);
|
||||
JsonAsset* materials[8];
|
||||
for (int32 i = 0;i<8;i++)
|
||||
materials[i] = _physicalMaterials[i];
|
||||
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
|
||||
{
|
||||
const auto patch = _patches.Get()[pathIndex];
|
||||
if (patch->HasCollision())
|
||||
PhysicsBackend::SetShapeMaterials(patch->_physicsShape, ToSpan(materials, 8));
|
||||
}
|
||||
}
|
||||
|
||||
TerrainPatch* Terrain::GetPatch(const Int2& patchCoord) const
|
||||
{
|
||||
return GetPatch(patchCoord.X, patchCoord.Y);
|
||||
@@ -667,8 +666,8 @@ void Terrain::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
SERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap);
|
||||
SERIALIZE_MEMBER(BoundsExtent, _boundsExtent);
|
||||
SERIALIZE_MEMBER(CollisionLOD, _collisionLod);
|
||||
SERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials);
|
||||
SERIALIZE(Material);
|
||||
SERIALIZE(PhysicalMaterial);
|
||||
SERIALIZE(DrawModes);
|
||||
|
||||
SERIALIZE_MEMBER(LODCount, _lodCount);
|
||||
@@ -714,8 +713,8 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
|
||||
DESERIALIZE_MEMBER(LODDistribution, _lodDistribution);
|
||||
DESERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap);
|
||||
DESERIALIZE_MEMBER(BoundsExtent, _boundsExtent);
|
||||
DESERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials);
|
||||
DESERIALIZE(Material);
|
||||
DESERIALIZE(PhysicalMaterial);
|
||||
DESERIALIZE(DrawModes);
|
||||
|
||||
member = stream.FindMember("LODCount");
|
||||
@@ -780,6 +779,15 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
|
||||
// [Deprecated on 27.04.2022, expires on 27.04.2024]
|
||||
if (modifier->EngineBuild <= 6331)
|
||||
DrawModes |= DrawPass::GlobalSurfaceAtlas;
|
||||
|
||||
// [Deprecated on 15.02.2024, expires on 15.02.2026]
|
||||
JsonAssetReference<PhysicalMaterial> PhysicalMaterial;
|
||||
DESERIALIZE(PhysicalMaterial);
|
||||
if (PhysicalMaterial)
|
||||
{
|
||||
for (auto& e : _physicalMaterials)
|
||||
e = PhysicalMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
RigidBody* Terrain::GetAttachedRigidBody() const
|
||||
|
||||
@@ -41,13 +41,12 @@ struct RenderView;
|
||||
/// <seealso cref="PhysicsColliderActor" />
|
||||
API_CLASS(Sealed) class FLAXENGINE_API Terrain : public PhysicsColliderActor
|
||||
{
|
||||
DECLARE_SCENE_OBJECT(Terrain);
|
||||
DECLARE_SCENE_OBJECT(Terrain);
|
||||
friend Terrain;
|
||||
friend TerrainPatch;
|
||||
friend TerrainChunk;
|
||||
|
||||
private:
|
||||
|
||||
char _lodBias;
|
||||
char _forcedLod;
|
||||
char _collisionLod;
|
||||
@@ -60,28 +59,21 @@ private:
|
||||
Float3 _cachedScale;
|
||||
Array<TerrainPatch*, InlinedAllocation<64>> _patches;
|
||||
Array<TerrainChunk*> _drawChunks;
|
||||
Array<JsonAssetReference<PhysicalMaterial>, FixedAllocation<8>> _physicalMaterials;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="Terrain"/> class.
|
||||
/// </summary>
|
||||
~Terrain();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The default material used for terrain rendering (chunks can override this).
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(100), DefaultValue(null), EditorDisplay(\"Terrain\")")
|
||||
AssetReference<MaterialBase> Material;
|
||||
|
||||
/// <summary>
|
||||
/// The physical material used to define the terrain collider physical properties.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(520), DefaultValue(null), Limit(-1, 100, 0.1f), EditorDisplay(\"Collision\")")
|
||||
JsonAssetReference<::PhysicalMaterial> PhysicalMaterial;
|
||||
|
||||
/// <summary>
|
||||
/// The draw passes to use for rendering this object.
|
||||
/// </summary>
|
||||
@@ -89,7 +81,6 @@ public:
|
||||
DrawPass DrawModes = DrawPass::Default;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the terrain Level Of Detail bias value. Allows to increase or decrease rendered terrain quality.
|
||||
/// </summary>
|
||||
@@ -180,6 +171,21 @@ public:
|
||||
/// </summary>
|
||||
API_PROPERTY() void SetCollisionLOD(int32 value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list with physical materials used to define the terrain collider physical properties - each for terrain layer (layer index matches index in this array).
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="EditorOrder(520), EditorDisplay(\"Collision\"), Collection(MinCount = 8, MaxCount = 8)")
|
||||
FORCE_INLINE const Array<JsonAssetReference<PhysicalMaterial>, FixedAllocation<8>>& GetPhysicalMaterials() const
|
||||
{
|
||||
return _physicalMaterials;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the list with physical materials used to define the terrain collider physical properties - each for terrain layer (layer index matches index in this array).
|
||||
/// </summary>
|
||||
API_PROPERTY()
|
||||
void SetPhysicalMaterials(const Array<JsonAssetReference<PhysicalMaterial>, FixedAllocation<8>>& value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the terrain Level Of Detail count.
|
||||
/// </summary>
|
||||
@@ -311,7 +317,6 @@ public:
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
#if TERRAIN_EDITING
|
||||
|
||||
/// <summary>
|
||||
@@ -362,7 +367,6 @@ public:
|
||||
void RemoveLightmap();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Performs a raycast against this terrain collision shape.
|
||||
/// </summary>
|
||||
@@ -432,14 +436,11 @@ public:
|
||||
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);
|
||||
void DrawPhysicsDebug(RenderView& view);
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [PhysicsColliderActor]
|
||||
void Draw(RenderContext& renderContext) override;
|
||||
#if USE_EDITOR
|
||||
@@ -452,7 +453,6 @@ public:
|
||||
RigidBody* GetAttachedRigidBody() const override;
|
||||
|
||||
protected:
|
||||
|
||||
// [PhysicsColliderActor]
|
||||
void OnEnable() override;
|
||||
void OnDisable() override;
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
#include "Engine/ContentImporters/AssetsImportingManager.h"
|
||||
#endif
|
||||
#endif
|
||||
#if TERRAIN_UPDATING
|
||||
#include "Engine/Core/Collections/ArrayExtensions.h"
|
||||
#endif
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Debug/DebugDraw.h"
|
||||
#endif
|
||||
@@ -105,6 +108,7 @@ void TerrainPatch::RemoveLightmap()
|
||||
|
||||
void TerrainPatch::UpdateBounds()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
Chunks[0].UpdateBounds();
|
||||
_bounds = Chunks[0]._bounds;
|
||||
for (int32 i = 1; i < CHUNKS_COUNT; i++)
|
||||
@@ -116,6 +120,8 @@ void TerrainPatch::UpdateBounds()
|
||||
|
||||
void TerrainPatch::UpdateTransform()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
// Update physics
|
||||
if (_physicsActor)
|
||||
{
|
||||
@@ -138,8 +144,14 @@ void TerrainPatch::UpdateTransform()
|
||||
|
||||
#if TERRAIN_EDITING || TERRAIN_UPDATING
|
||||
|
||||
bool IsValidMaterial(const JsonAssetReference<PhysicalMaterial>& e)
|
||||
{
|
||||
return e;
|
||||
}
|
||||
|
||||
struct TerrainDataUpdateInfo
|
||||
{
|
||||
TerrainPatch* Patch;
|
||||
int32 ChunkSize;
|
||||
int32 VertexCountEdge;
|
||||
int32 HeightmapSize;
|
||||
@@ -147,6 +159,36 @@ struct TerrainDataUpdateInfo
|
||||
int32 TextureSize;
|
||||
float PatchOffset;
|
||||
float PatchHeight;
|
||||
Color32* SplatMaps[TERRAIN_MAX_SPLATMAPS_COUNT] = {};
|
||||
|
||||
TerrainDataUpdateInfo(TerrainPatch* patch, float patchOffset = 0.0f, float patchHeight = 1.0f)
|
||||
: Patch(patch)
|
||||
, PatchOffset(patchOffset)
|
||||
, PatchHeight(patchHeight)
|
||||
{
|
||||
ChunkSize = patch->GetTerrain()->GetChunkSize();
|
||||
VertexCountEdge = ChunkSize + 1;
|
||||
HeightmapSize = ChunkSize * TerrainPatch::CHUNKS_COUNT_EDGE + 1;
|
||||
HeightmapLength = HeightmapSize * HeightmapSize;
|
||||
TextureSize = VertexCountEdge * TerrainPatch::CHUNKS_COUNT_EDGE;
|
||||
}
|
||||
|
||||
bool UsePhysicalMaterials() const
|
||||
{
|
||||
return ArrayExtensions::Any<JsonAssetReference<PhysicalMaterial>>(Patch->GetTerrain()->GetPhysicalMaterials(), IsValidMaterial);
|
||||
}
|
||||
|
||||
// When using physical materials, then get splatmaps data required for per-triangle material indices
|
||||
void GetSplatMaps()
|
||||
{
|
||||
if (SplatMaps[0])
|
||||
return;
|
||||
if (UsePhysicalMaterials())
|
||||
{
|
||||
for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++)
|
||||
SplatMaps[i] = Patch->GetSplatMapData(i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Shared data container for the terrain data updating shared by the normals and collision generation logic
|
||||
@@ -321,10 +363,9 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
|
||||
PROFILE_CPU_NAMED("Terrain.CalculateNormals");
|
||||
|
||||
// Expand the area for the normals to prevent issues on the edges (for the averaged normals)
|
||||
const int32 heightMapSize = info.HeightmapSize;
|
||||
const Int2 modifiedEnd = modifiedOffset + modifiedSize;
|
||||
const Int2 normalsStart = Int2::Max(Int2::Zero, modifiedOffset - 1);
|
||||
const Int2 normalsEnd = Int2::Min(heightMapSize, modifiedEnd + 1);
|
||||
const Int2 normalsEnd = Int2::Min(info.HeightmapSize, modifiedEnd + 1);
|
||||
const Int2 normalsSize = normalsEnd - normalsStart;
|
||||
|
||||
// Prepare memory
|
||||
@@ -342,7 +383,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
|
||||
// Get four vertices from the quad
|
||||
#define GET_VERTEX(a, b) \
|
||||
int32 i##a##b = (z + (b) - normalsStart.Y) * normalsSize.X + (x + (a) - normalsStart.X); \
|
||||
int32 h##a##b = (z + (b)) * heightMapSize + (x + (a)); \
|
||||
int32 h##a##b = (z + (b)) * info.HeightmapSize + (x + (a)); \
|
||||
Float3 v##a##b; v##a##b.X = (x + (a)) * TERRAIN_UNITS_PER_VERTEX; \
|
||||
v##a##b.Y = heightmap[h##a##b]; \
|
||||
v##a##b.Z = (z + (b)) * TERRAIN_UNITS_PER_VERTEX
|
||||
@@ -428,7 +469,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
|
||||
const int32 dz = chunkHeightmapZ + z - modifiedOffset.Y;
|
||||
if (dz < 0 || dz >= modifiedSize.Y)
|
||||
continue;
|
||||
const int32 hz = (chunkHeightmapZ + z) * heightMapSize;
|
||||
const int32 hz = (chunkHeightmapZ + z) * info.HeightmapSize;
|
||||
const int32 sz = (chunkHeightmapZ + z - normalsStart.Y) * normalsSize.X;
|
||||
const int32 tz = (chunkTextureZ + z) * info.TextureSize;
|
||||
|
||||
@@ -546,13 +587,52 @@ void FixMips(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData,
|
||||
}
|
||||
}
|
||||
|
||||
bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, Array<byte>* collisionData)
|
||||
FORCE_INLINE byte GetPhysicalMaterial(const Color32& raw, const TerrainDataUpdateInfo& info, int32 chunkZ, int32 chunkX, int32 z, int32 x)
|
||||
{
|
||||
byte result = 0;
|
||||
if (ReadIsHole(raw))
|
||||
{
|
||||
// Hole
|
||||
result = (uint8)PhysicsBackend::HeightFieldMaterial::Hole;
|
||||
}
|
||||
else if (info.SplatMaps[0])
|
||||
{
|
||||
// Use the layer with the highest influence (splatmap data is Mip0 so convert x/z coords back to LOD0)
|
||||
uint8 layer = 0;
|
||||
uint8 layerWeight = 0;
|
||||
const int32 splatmapTextureIndex = (chunkZ * info.ChunkSize + z) * info.HeightmapSize + chunkX * info.ChunkSize + x;
|
||||
for (int32 splatIndex = 0; splatIndex < TERRAIN_MAX_SPLATMAPS_COUNT; splatIndex++)
|
||||
{
|
||||
for (int32 channelIndex = 0; channelIndex < 4; channelIndex++)
|
||||
{
|
||||
// Assume splatmap data pitch matches the row size and shift by channel index to simply sample at R chanel
|
||||
const Color32* splatmap = (const Color32*)((const byte*)info.SplatMaps[splatIndex] + channelIndex);
|
||||
const uint8 splat = splatmap[splatmapTextureIndex].R;
|
||||
if (splat > layerWeight)
|
||||
{
|
||||
layer = splatIndex * 4 + channelIndex;
|
||||
layerWeight = splat;
|
||||
if (layerWeight == MAX_uint8)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (layerWeight == MAX_uint8)
|
||||
break;
|
||||
}
|
||||
result = layer;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CookCollision(TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, Array<byte>* collisionData)
|
||||
{
|
||||
#if COMPILE_WITH_PHYSICS_COOKING
|
||||
info.GetSplatMaps();
|
||||
PROFILE_CPU_NAMED("Terrain.CookCollision");
|
||||
|
||||
// Prepare data
|
||||
const int32 collisionLOD = Math::Clamp<int32>(collisionLod, 0, initData->Mips.Count() - 1);
|
||||
const int32 collisionLODInv = (int32)Math::Pow(2.0f, (float)collisionLOD);
|
||||
const int32 heightFieldChunkSize = ((info.ChunkSize + 1) >> collisionLOD) - 1;
|
||||
const int32 heightFieldSize = heightFieldChunkSize * TerrainPatch::CHUNKS_COUNT_EDGE + 1;
|
||||
const int32 heightFieldLength = heightFieldSize * heightFieldSize;
|
||||
@@ -562,36 +642,30 @@ bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* ini
|
||||
Platform::MemoryClear(heightFieldData, sizeof(PhysicsBackend::HeightFieldSample) * heightFieldLength);
|
||||
|
||||
// Setup terrain collision information
|
||||
auto& mip = initData->Mips[collisionLOD];
|
||||
const auto& mip = initData->Mips[collisionLOD];
|
||||
const int32 vertexCountEdgeMip = info.VertexCountEdge >> collisionLOD;
|
||||
const int32 textureSizeMip = info.TextureSize >> collisionLOD;
|
||||
for (int32 chunkX = 0; chunkX < TerrainPatch::CHUNKS_COUNT_EDGE; chunkX++)
|
||||
{
|
||||
const int32 chunkTextureX = chunkX * vertexCountEdgeMip;
|
||||
const int32 chunkStartX = chunkX * heightFieldChunkSize;
|
||||
|
||||
for (int32 chunkZ = 0; chunkZ < TerrainPatch::CHUNKS_COUNT_EDGE; chunkZ++)
|
||||
{
|
||||
const int32 chunkTextureZ = chunkZ * vertexCountEdgeMip;
|
||||
const int32 chunkStartZ = chunkZ * heightFieldChunkSize;
|
||||
|
||||
for (int32 z = 0; z < vertexCountEdgeMip; z++)
|
||||
{
|
||||
const int32 heightmapZ = chunkStartZ + z;
|
||||
for (int32 x = 0; x < vertexCountEdgeMip; x++)
|
||||
{
|
||||
const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x;
|
||||
|
||||
const Color32 raw = mip.Data.Get<Color32>()[textureIndex];
|
||||
const float normalizedHeight = ReadNormalizedHeight(raw);
|
||||
const bool isHole = ReadIsHole(raw);
|
||||
|
||||
const int32 heightmapX = chunkStartX + x;
|
||||
const int32 heightmapZ = chunkStartZ + z;
|
||||
|
||||
const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x;
|
||||
const Color32 raw = mip.Data.Get<Color32>()[textureIndex];
|
||||
sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * ReadNormalizedHeight(raw));
|
||||
sample.MaterialIndex0 = sample.MaterialIndex1 = GetPhysicalMaterial(raw, info, chunkZ, chunkX, z * collisionLODInv, x * collisionLODInv);
|
||||
|
||||
const int32 dstIndex = (heightmapX * heightFieldSize) + heightmapZ;
|
||||
|
||||
sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * normalizedHeight);
|
||||
sample.MaterialIndex0 = sample.MaterialIndex1 = isHole ? (uint8)PhysicsBackend::HeightFieldMaterial::Hole : 0;
|
||||
|
||||
heightFieldData[dstIndex] = sample;
|
||||
}
|
||||
}
|
||||
@@ -620,14 +694,16 @@ bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* ini
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, const Int2& modifiedOffset, const Int2& modifiedSize, void* heightField)
|
||||
bool ModifyCollision(TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, const Int2& modifiedOffset, const Int2& modifiedSize, void* heightField)
|
||||
{
|
||||
info.GetSplatMaps();
|
||||
PROFILE_CPU_NAMED("Terrain.ModifyCollision");
|
||||
|
||||
// Prepare data
|
||||
const Vector2 modifiedOffsetRatio((float)modifiedOffset.X / info.HeightmapSize, (float)modifiedOffset.Y / info.HeightmapSize);
|
||||
const Vector2 modifiedSizeRatio((float)modifiedSize.X / info.HeightmapSize, (float)modifiedSize.Y / info.HeightmapSize);
|
||||
const int32 collisionLOD = Math::Clamp<int32>(collisionLod, 0, initData->Mips.Count() - 1);
|
||||
const int32 collisionLODInv = (int32)Math::Pow(2.0f, (float)collisionLOD);
|
||||
const int32 heightFieldChunkSize = ((info.ChunkSize + 1) >> collisionLOD) - 1;
|
||||
const int32 heightFieldSize = heightFieldChunkSize * TerrainPatch::CHUNKS_COUNT_EDGE + 1;
|
||||
const Int2 samplesOffset(Vector2::Floor(modifiedOffsetRatio * (float)heightFieldSize));
|
||||
@@ -646,56 +722,45 @@ bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* i
|
||||
Platform::MemoryClear(heightFieldData, sizeof(PhysicsBackend::HeightFieldSample) * heightFieldDataLength);
|
||||
|
||||
// Setup terrain collision information
|
||||
auto& mip = initData->Mips[collisionLOD];
|
||||
const auto& mip = initData->Mips[collisionLOD];
|
||||
const int32 vertexCountEdgeMip = info.VertexCountEdge >> collisionLOD;
|
||||
const int32 textureSizeMip = info.TextureSize >> collisionLOD;
|
||||
for (int32 chunkX = 0; chunkX < TerrainPatch::CHUNKS_COUNT_EDGE; chunkX++)
|
||||
{
|
||||
const int32 chunkTextureX = chunkX * vertexCountEdgeMip;
|
||||
const int32 chunkStartX = chunkX * heightFieldChunkSize;
|
||||
|
||||
// Skip unmodified chunks
|
||||
if (chunkStartX >= samplesEnd.X || chunkStartX + vertexCountEdgeMip < samplesOffset.X)
|
||||
continue;
|
||||
continue; // Skip unmodified chunks
|
||||
|
||||
for (int32 chunkZ = 0; chunkZ < TerrainPatch::CHUNKS_COUNT_EDGE; chunkZ++)
|
||||
{
|
||||
const int32 chunkTextureZ = chunkZ * vertexCountEdgeMip;
|
||||
const int32 chunkStartZ = chunkZ * heightFieldChunkSize;
|
||||
|
||||
// Skip unmodified chunks
|
||||
if (chunkStartZ >= samplesEnd.Y || chunkStartZ + vertexCountEdgeMip < samplesOffset.Y)
|
||||
continue;
|
||||
continue; // Skip unmodified chunks
|
||||
|
||||
// TODO: adjust loop range to reduce iterations count for edge cases (skip checking unmodified samples)
|
||||
for (int32 z = 0; z < vertexCountEdgeMip; z++)
|
||||
{
|
||||
// Skip unmodified columns
|
||||
const int32 heightmapZ = chunkStartZ + z;
|
||||
const int32 heightmapLocalZ = heightmapZ - samplesOffset.Y;
|
||||
if (heightmapLocalZ < 0 || heightmapLocalZ >= samplesSize.Y)
|
||||
continue;
|
||||
continue; // Skip unmodified columns
|
||||
|
||||
// TODO: adjust loop range to reduce iterations count for edge cases (skip checking unmodified samples)
|
||||
for (int32 x = 0; x < vertexCountEdgeMip; x++)
|
||||
{
|
||||
// Skip unmodified rows
|
||||
const int32 heightmapX = chunkStartX + x;
|
||||
const int32 heightmapLocalX = heightmapX - samplesOffset.X;
|
||||
if (heightmapLocalX < 0 || heightmapLocalX >= samplesSize.X)
|
||||
continue;
|
||||
continue; // Skip unmodified rows
|
||||
|
||||
const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x;
|
||||
|
||||
const Color32 raw = mip.Data.Get<Color32>()[textureIndex];
|
||||
const float normalizedHeight = ReadNormalizedHeight(raw);
|
||||
const bool isHole = ReadIsHole(raw);
|
||||
sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * ReadNormalizedHeight(raw));
|
||||
sample.MaterialIndex0 = sample.MaterialIndex1 = GetPhysicalMaterial(raw, info, chunkZ, chunkX, z * collisionLODInv, x * collisionLODInv);
|
||||
|
||||
const int32 dstIndex = (heightmapLocalX * samplesSize.Y) + heightmapLocalZ;
|
||||
|
||||
sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * normalizedHeight);
|
||||
sample.MaterialIndex0 = sample.MaterialIndex1 = isHole ? (uint8)PhysicsBackend::HeightFieldMaterial::Hole : 0;
|
||||
|
||||
heightFieldData[dstIndex] = sample;
|
||||
}
|
||||
}
|
||||
@@ -718,38 +783,23 @@ bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* i
|
||||
|
||||
bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, const byte* holesMask, bool forceUseVirtualStorage)
|
||||
{
|
||||
// Validate input
|
||||
PROFILE_CPU_NAMED("Terrain.Setup");
|
||||
if (heightMap == nullptr)
|
||||
{
|
||||
LOG(Warning, "Cannot create terrain without a heightmap specified.");
|
||||
return true;
|
||||
}
|
||||
const int32 chunkSize = _terrain->_chunkSize;
|
||||
const int32 vertexCountEdge = chunkSize + 1;
|
||||
const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
|
||||
if (heightMapLength != heightMapSize * heightMapSize)
|
||||
TerrainDataUpdateInfo info(this);
|
||||
if (heightMapLength != info.HeightmapLength)
|
||||
{
|
||||
LOG(Warning, "Invalid heightmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", chunkSize, heightMapSize, heightMapSize * heightMapSize, heightMapLength);
|
||||
LOG(Warning, "Invalid heightmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", info.ChunkSize, info.HeightmapSize, info.HeightmapLength, heightMapLength);
|
||||
return true;
|
||||
}
|
||||
const PixelFormat pixelFormat = PixelFormat::R8G8B8A8_UNorm;
|
||||
|
||||
PROFILE_CPU_NAMED("Terrain.Setup");
|
||||
|
||||
// Input heightmap data overlaps on chunk edges but it needs to be duplicated for chunks (each chunk has own scale-bias for height values normalization)
|
||||
const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
|
||||
const int32 pixelStride = PixelFormatExtensions::SizeInBytes(pixelFormat);
|
||||
const int32 lodCount = Math::Min<int32>(_terrain->_lodCount, MipLevelsCount(vertexCountEdge) - 2);
|
||||
|
||||
// Setup patch data info
|
||||
TerrainDataUpdateInfo info;
|
||||
info.ChunkSize = chunkSize;
|
||||
info.VertexCountEdge = vertexCountEdge;
|
||||
info.HeightmapSize = heightMapSize;
|
||||
info.HeightmapLength = heightMapLength;
|
||||
info.TextureSize = textureSize;
|
||||
info.PatchOffset = 0.0f;
|
||||
info.PatchHeight = 1.0f;
|
||||
const int32 lodCount = Math::Min<int32>(_terrain->_lodCount, MipLevelsCount(info.VertexCountEdge) - 2);
|
||||
|
||||
// Process heightmap to get per-patch height normalization values
|
||||
float chunkOffsets[CHUNKS_COUNT];
|
||||
@@ -782,18 +832,17 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap,
|
||||
// Create heightmap texture data source container
|
||||
auto initData = New<TextureBase::InitData>();
|
||||
initData->Format = pixelFormat;
|
||||
initData->Width = textureSize;
|
||||
initData->Height = textureSize;
|
||||
initData->Width = info.TextureSize;
|
||||
initData->Height = info.TextureSize;
|
||||
initData->ArraySize = 1;
|
||||
initData->Mips.Resize(lodCount);
|
||||
|
||||
// Allocate top mip data
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.AllocateHeightmap");
|
||||
|
||||
auto& mip = initData->Mips[0];
|
||||
mip.RowPitch = textureSize * pixelStride;
|
||||
mip.SlicePitch = mip.RowPitch * textureSize;
|
||||
mip.RowPitch = info.TextureSize * pixelStride;
|
||||
mip.SlicePitch = mip.RowPitch * info.TextureSize;
|
||||
mip.Data.Allocate(mip.SlicePitch);
|
||||
}
|
||||
|
||||
@@ -945,21 +994,17 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap,
|
||||
|
||||
bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.SetupSplatMap");
|
||||
CHECK_RETURN(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT, true);
|
||||
|
||||
// Validate input
|
||||
if (splatMap == nullptr)
|
||||
{
|
||||
LOG(Warning, "Cannot create terrain without any splatmap specified.");
|
||||
return true;
|
||||
}
|
||||
const int32 chunkSize = _terrain->_chunkSize;
|
||||
const int32 vertexCountEdge = chunkSize + 1;
|
||||
const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
|
||||
const int32 heightMapLength = heightMapSize * heightMapSize;
|
||||
if (splatMapLength != heightMapLength)
|
||||
TerrainDataUpdateInfo info(this, _yOffset, _yHeight);
|
||||
if (splatMapLength != info.HeightmapLength)
|
||||
{
|
||||
LOG(Warning, "Invalid splatmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", chunkSize, heightMapSize, heightMapLength, splatMapLength);
|
||||
LOG(Warning, "Invalid splatmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", info.ChunkSize, info.HeightmapSize, info.HeightmapLength, splatMapLength);
|
||||
return true;
|
||||
}
|
||||
const PixelFormat pixelFormat = PixelFormat::R8G8B8A8_UNorm;
|
||||
@@ -974,22 +1019,9 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3
|
||||
}
|
||||
}
|
||||
|
||||
PROFILE_CPU_NAMED("Terrain.SetupSplatMap");
|
||||
|
||||
// Input splatmap data overlaps on chunk edges but it needs to be duplicated for chunks
|
||||
const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
|
||||
const int32 pixelStride = PixelFormatExtensions::SizeInBytes(pixelFormat);
|
||||
const int32 lodCount = Math::Min<int32>(_terrain->_lodCount, MipLevelsCount(vertexCountEdge) - 2);
|
||||
|
||||
// Setup patch data info
|
||||
TerrainDataUpdateInfo info;
|
||||
info.ChunkSize = chunkSize;
|
||||
info.VertexCountEdge = vertexCountEdge;
|
||||
info.HeightmapSize = heightMapSize;
|
||||
info.HeightmapLength = heightMapLength;
|
||||
info.TextureSize = textureSize;
|
||||
info.PatchOffset = _yOffset;
|
||||
info.PatchHeight = _yHeight;
|
||||
const int32 lodCount = Math::Min<int32>(_terrain->_lodCount, MipLevelsCount(info.VertexCountEdge) - 2);
|
||||
|
||||
// Prepare
|
||||
#if USE_EDITOR
|
||||
@@ -1016,18 +1048,17 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3
|
||||
// Create heightmap texture data source container
|
||||
auto initData = New<TextureBase::InitData>();
|
||||
initData->Format = pixelFormat;
|
||||
initData->Width = textureSize;
|
||||
initData->Height = textureSize;
|
||||
initData->Width = info.TextureSize;
|
||||
initData->Height = info.TextureSize;
|
||||
initData->ArraySize = 1;
|
||||
initData->Mips.Resize(lodCount);
|
||||
|
||||
// Allocate top mip data
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.AllocateSplatmap");
|
||||
|
||||
auto& mip = initData->Mips[0];
|
||||
mip.RowPitch = textureSize * pixelStride;
|
||||
mip.SlicePitch = mip.RowPitch * textureSize;
|
||||
mip.RowPitch = info.TextureSize * pixelStride;
|
||||
mip.SlicePitch = mip.RowPitch * info.TextureSize;
|
||||
mip.Data.Allocate(mip.SlicePitch);
|
||||
}
|
||||
|
||||
@@ -1112,8 +1143,6 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3
|
||||
bool TerrainPatch::InitializeHeightMap()
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.InitializeHeightMap");
|
||||
|
||||
// Initialize with flat heightmap data
|
||||
const auto heightmapSize = _terrain->GetChunkSize() * TerrainPatch::CHUNKS_COUNT_EDGE + 1;
|
||||
Array<float> heightmap;
|
||||
heightmap.Resize(heightmapSize * heightmapSize);
|
||||
@@ -1179,6 +1208,7 @@ void TerrainPatch::ClearCache()
|
||||
void TerrainPatch::CacheHeightData()
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.CacheHeightData");
|
||||
const TerrainDataUpdateInfo info(this);
|
||||
|
||||
// Ensure that heightmap data is all loaded
|
||||
// TODO: disable streaming for heightmap texture if it's being modified by the editor
|
||||
@@ -1198,16 +1228,9 @@ void TerrainPatch::CacheHeightData()
|
||||
return;
|
||||
}
|
||||
|
||||
// Get texture input (note: this must match Setup method)
|
||||
const int32 chunkSize = _terrain->_chunkSize;
|
||||
const int32 vertexCountEdge = chunkSize + 1;
|
||||
const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
|
||||
const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
|
||||
|
||||
// Allocate data
|
||||
const int32 heightMapLength = heightMapSize * heightMapSize;
|
||||
_cachedHeightMap.Resize(heightMapLength);
|
||||
_cachedHolesMask.Resize(heightMapLength);
|
||||
_cachedHeightMap.Resize(info.HeightmapLength);
|
||||
_cachedHolesMask.Resize(info.HeightmapLength);
|
||||
_wasHeightModified = false;
|
||||
|
||||
// Extract heightmap data and denormalize it to get the pure height field
|
||||
@@ -1217,18 +1240,18 @@ void TerrainPatch::CacheHeightData()
|
||||
const auto holesMaskPtr = _cachedHolesMask.Get();
|
||||
for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++)
|
||||
{
|
||||
const int32 chunkTextureX = Chunks[chunkIndex]._x * vertexCountEdge;
|
||||
const int32 chunkTextureZ = Chunks[chunkIndex]._z * vertexCountEdge;
|
||||
const int32 chunkTextureX = Chunks[chunkIndex]._x * info.VertexCountEdge;
|
||||
const int32 chunkTextureZ = Chunks[chunkIndex]._z * info.VertexCountEdge;
|
||||
|
||||
const int32 chunkHeightmapX = Chunks[chunkIndex]._x * chunkSize;
|
||||
const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * chunkSize;
|
||||
const int32 chunkHeightmapX = Chunks[chunkIndex]._x * info.ChunkSize;
|
||||
const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * info.ChunkSize;
|
||||
|
||||
for (int32 z = 0; z < vertexCountEdge; z++)
|
||||
for (int32 z = 0; z < info.VertexCountEdge; z++)
|
||||
{
|
||||
const int32 tz = (chunkTextureZ + z) * textureSize;
|
||||
const int32 sz = (chunkHeightmapZ + z) * heightMapSize;
|
||||
const int32 tz = (chunkTextureZ + z) * info.TextureSize;
|
||||
const int32 sz = (chunkHeightmapZ + z) * info.HeightmapSize;
|
||||
|
||||
for (int32 x = 0; x < vertexCountEdge; x++)
|
||||
for (int32 x = 0; x < info.VertexCountEdge; x++)
|
||||
{
|
||||
const int32 tx = chunkTextureX + x;
|
||||
const int32 sx = chunkHeightmapX + x;
|
||||
@@ -1249,18 +1272,14 @@ void TerrainPatch::CacheHeightData()
|
||||
|
||||
void TerrainPatch::CacheSplatData()
|
||||
{
|
||||
// Prepare
|
||||
const int32 chunkSize = _terrain->_chunkSize;
|
||||
const int32 vertexCountEdge = chunkSize + 1;
|
||||
const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
|
||||
const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
|
||||
const int32 heightMapLength = heightMapSize * heightMapSize;
|
||||
PROFILE_CPU_NAMED("Terrain.CacheSplatData");
|
||||
const TerrainDataUpdateInfo info(this);
|
||||
|
||||
// Cache all the splatmaps
|
||||
for (int32 index = 0; index < TERRAIN_MAX_SPLATMAPS_COUNT; index++)
|
||||
{
|
||||
// Allocate data
|
||||
_cachedSplatMap[index].Resize(heightMapLength);
|
||||
_cachedSplatMap[index].Resize(info.HeightmapLength);
|
||||
_wasSplatmapModified[index] = false;
|
||||
|
||||
// Skip if has missing splatmap asset
|
||||
@@ -1272,8 +1291,6 @@ void TerrainPatch::CacheSplatData()
|
||||
continue;
|
||||
}
|
||||
|
||||
PROFILE_CPU_NAMED("Terrain.CacheSplatData");
|
||||
|
||||
// Ensure that splatmap data is all loaded
|
||||
// TODO: disable streaming for heightmap texture if it's being modified by the editor
|
||||
if (Splatmap[index]->WaitForLoaded())
|
||||
@@ -1296,18 +1313,18 @@ void TerrainPatch::CacheSplatData()
|
||||
const auto splatMapPtr = static_cast<Color32*>(_cachedSplatMap[index].Get());
|
||||
for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++)
|
||||
{
|
||||
const int32 chunkTextureX = Chunks[chunkIndex]._x * vertexCountEdge;
|
||||
const int32 chunkTextureZ = Chunks[chunkIndex]._z * vertexCountEdge;
|
||||
const int32 chunkTextureX = Chunks[chunkIndex]._x * info.VertexCountEdge;
|
||||
const int32 chunkTextureZ = Chunks[chunkIndex]._z * info.VertexCountEdge;
|
||||
|
||||
const int32 chunkHeightmapX = Chunks[chunkIndex]._x * chunkSize;
|
||||
const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * chunkSize;
|
||||
const int32 chunkHeightmapX = Chunks[chunkIndex]._x * info.ChunkSize;
|
||||
const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * info.ChunkSize;
|
||||
|
||||
for (int32 z = 0; z < vertexCountEdge; z++)
|
||||
for (int32 z = 0; z < info.VertexCountEdge; z++)
|
||||
{
|
||||
const int32 tz = (chunkTextureZ + z) * textureSize;
|
||||
const int32 sz = (chunkHeightmapZ + z) * heightMapSize;
|
||||
const int32 tz = (chunkTextureZ + z) * info.TextureSize;
|
||||
const int32 sz = (chunkHeightmapZ + z) * info.HeightmapSize;
|
||||
|
||||
for (int32 x = 0; x < vertexCountEdge; x++)
|
||||
for (int32 x = 0; x < info.VertexCountEdge; x++)
|
||||
{
|
||||
const int32 tx = chunkTextureX + x;
|
||||
const int32 sx = chunkHeightmapX + x;
|
||||
@@ -1324,9 +1341,7 @@ void TerrainPatch::CacheSplatData()
|
||||
bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOffset, const Int2& modifiedSize)
|
||||
{
|
||||
// Validate input samples range
|
||||
const int32 chunkSize = _terrain->_chunkSize;
|
||||
const int32 vertexCountEdge = chunkSize + 1;
|
||||
const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
|
||||
TerrainDataUpdateInfo info(this);
|
||||
if (samples == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing heightmap samples data.");
|
||||
@@ -1334,13 +1349,12 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff
|
||||
}
|
||||
if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 ||
|
||||
modifiedSize.X <= 0 || modifiedSize.Y <= 0 ||
|
||||
modifiedOffset.X + modifiedSize.X > heightMapSize ||
|
||||
modifiedOffset.Y + modifiedSize.Y > heightMapSize)
|
||||
modifiedOffset.X + modifiedSize.X > info.HeightmapSize ||
|
||||
modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize)
|
||||
{
|
||||
LOG(Warning, "Invalid heightmap samples range.");
|
||||
return true;
|
||||
}
|
||||
|
||||
PROFILE_CPU_NAMED("Terrain.ModifyHeightMap");
|
||||
|
||||
// Check if has no heightmap
|
||||
@@ -1364,28 +1378,17 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff
|
||||
// Modify heightmap data
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.WrtieCache");
|
||||
|
||||
for (int32 z = 0; z < modifiedSize.Y; z++)
|
||||
{
|
||||
// TODO: use batches row mem copy
|
||||
|
||||
for (int32 x = 0; x < modifiedSize.X; x++)
|
||||
{
|
||||
heightMap[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x];
|
||||
heightMap[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup patch data info
|
||||
TerrainDataUpdateInfo info;
|
||||
info.ChunkSize = chunkSize;
|
||||
info.VertexCountEdge = vertexCountEdge;
|
||||
info.HeightmapSize = heightMapSize;
|
||||
info.HeightmapLength = heightMapSize * heightMapSize;
|
||||
info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
|
||||
info.PatchOffset = 0.0f;
|
||||
info.PatchHeight = 1.0f;
|
||||
|
||||
// Process heightmap to get per-patch height normalization values
|
||||
float chunkOffsets[CHUNKS_COUNT];
|
||||
float chunkHeights[CHUNKS_COUNT];
|
||||
@@ -1426,15 +1429,13 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff
|
||||
chunk.UpdateTransform();
|
||||
}
|
||||
_terrain->UpdateBounds();
|
||||
return UpdateHeightData(info, modifiedOffset, modifiedSize, wasHeightRangeChanged);
|
||||
return UpdateHeightData(info, modifiedOffset, modifiedSize, wasHeightRangeChanged, true);
|
||||
}
|
||||
|
||||
bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffset, const Int2& modifiedSize)
|
||||
{
|
||||
// Validate input samples range
|
||||
const int32 chunkSize = _terrain->_chunkSize;
|
||||
const int32 vertexCountEdge = chunkSize + 1;
|
||||
const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
|
||||
TerrainDataUpdateInfo info(this, _yOffset, _yHeight);
|
||||
if (samples == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing holes mask samples data.");
|
||||
@@ -1442,13 +1443,12 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs
|
||||
}
|
||||
if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 ||
|
||||
modifiedSize.X <= 0 || modifiedSize.Y <= 0 ||
|
||||
modifiedOffset.X + modifiedSize.X > heightMapSize ||
|
||||
modifiedOffset.Y + modifiedSize.Y > heightMapSize)
|
||||
modifiedOffset.X + modifiedSize.X > info.HeightmapSize ||
|
||||
modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize)
|
||||
{
|
||||
LOG(Warning, "Invalid holes mask samples range.");
|
||||
return true;
|
||||
}
|
||||
|
||||
PROFILE_CPU_NAMED("Terrain.ModifyHolesMask");
|
||||
|
||||
// Check if has no heightmap
|
||||
@@ -1472,28 +1472,17 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs
|
||||
// Modify holes mask data
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.WrtieCache");
|
||||
|
||||
for (int32 z = 0; z < modifiedSize.Y; z++)
|
||||
{
|
||||
// TODO: use batches row mem copy
|
||||
|
||||
for (int32 x = 0; x < modifiedSize.X; x++)
|
||||
{
|
||||
holesMask[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x];
|
||||
holesMask[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup patch data info
|
||||
TerrainDataUpdateInfo info;
|
||||
info.ChunkSize = chunkSize;
|
||||
info.VertexCountEdge = vertexCountEdge;
|
||||
info.HeightmapSize = heightMapSize;
|
||||
info.HeightmapLength = heightMapSize * heightMapSize;
|
||||
info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
|
||||
info.PatchOffset = _yOffset;
|
||||
info.PatchHeight = _yHeight;
|
||||
|
||||
// Check if has allocated texture
|
||||
if (_dataHeightmap)
|
||||
{
|
||||
@@ -1505,7 +1494,7 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs
|
||||
}
|
||||
|
||||
// Update all the stuff
|
||||
return UpdateHeightData(info, modifiedOffset, modifiedSize, false);
|
||||
return UpdateHeightData(info, modifiedOffset, modifiedSize, false, true);
|
||||
}
|
||||
|
||||
bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize)
|
||||
@@ -1523,9 +1512,7 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
|
||||
}
|
||||
|
||||
// Validate input samples range
|
||||
const int32 chunkSize = _terrain->_chunkSize;
|
||||
const int32 vertexCountEdge = chunkSize + 1;
|
||||
const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1;
|
||||
TerrainDataUpdateInfo info(this, _yOffset, _yHeight);
|
||||
if (samples == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing splatmap samples data.");
|
||||
@@ -1533,13 +1520,12 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
|
||||
}
|
||||
if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 ||
|
||||
modifiedSize.X <= 0 || modifiedSize.Y <= 0 ||
|
||||
modifiedOffset.X + modifiedSize.X > heightMapSize ||
|
||||
modifiedOffset.Y + modifiedSize.Y > heightMapSize)
|
||||
modifiedOffset.X + modifiedSize.X > info.HeightmapSize ||
|
||||
modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize)
|
||||
{
|
||||
LOG(Warning, "Invalid heightmap samples range.");
|
||||
return true;
|
||||
}
|
||||
|
||||
PROFILE_CPU_NAMED("Terrain.ModifySplatMap");
|
||||
|
||||
// Get the current data to modify it
|
||||
@@ -1552,14 +1538,13 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
|
||||
// Modify splat map data
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.WrtieCache");
|
||||
|
||||
for (int32 z = 0; z < modifiedSize.Y; z++)
|
||||
{
|
||||
// TODO: use batches row mem copy
|
||||
|
||||
for (int32 x = 0; x < modifiedSize.X; x++)
|
||||
{
|
||||
splatMap[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x];
|
||||
splatMap[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1570,7 +1555,6 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
|
||||
if (dataSplatmap == nullptr)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.InitDataStorage");
|
||||
|
||||
if (Heightmap->WaitForLoaded())
|
||||
{
|
||||
LOG(Error, "Failed to load heightmap.");
|
||||
@@ -1597,16 +1581,6 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
|
||||
mip.Data.Allocate(mip.SlicePitch);
|
||||
}
|
||||
|
||||
// Setup patch data info
|
||||
TerrainDataUpdateInfo info;
|
||||
info.ChunkSize = chunkSize;
|
||||
info.VertexCountEdge = vertexCountEdge;
|
||||
info.HeightmapSize = heightMapSize;
|
||||
info.HeightmapLength = heightMapSize * heightMapSize;
|
||||
info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE;
|
||||
info.PatchOffset = _yOffset;
|
||||
info.PatchHeight = _yHeight;
|
||||
|
||||
// Update splat map storage data
|
||||
const bool hasSplatmap = splatmap;
|
||||
const auto splatmapData = dataSplatmap->Mips[0].Data.Get();
|
||||
@@ -1712,12 +1686,18 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int
|
||||
|
||||
// TODO: disable splatmap dynamic streaming - data on a GPU was modified and we don't want to override it with the old data stored in the asset container
|
||||
|
||||
// Update heightfield to reflect physical materials layering
|
||||
if (info.UsePhysicalMaterials() && HasCollision())
|
||||
{
|
||||
UpdateHeightData(info, modifiedOffset, modifiedSize, false, false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged)
|
||||
bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged)
|
||||
{
|
||||
// Prepare
|
||||
PROFILE_CPU();
|
||||
float* heightMap = GetHeightmapData();
|
||||
byte* holesMask = GetHolesMaskData();
|
||||
ASSERT(heightMap && holesMask);
|
||||
@@ -1753,9 +1733,7 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int
|
||||
|
||||
// Downscale mip data for all lower LODs
|
||||
if (GenerateMips(_dataHeightmap))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fix generated mip maps to keep the same values for chunk edges (reduce cracks on continuous LOD transitions)
|
||||
FixMips(info, _dataHeightmap, pixelStride);
|
||||
@@ -1779,9 +1757,7 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int
|
||||
}
|
||||
const auto collisionData = &_heightfield->Data;
|
||||
if (CookCollision(info, _dataHeightmap, _terrain->_collisionLod, collisionData))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
UpdateCollision();
|
||||
}
|
||||
else
|
||||
@@ -1789,7 +1765,8 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int
|
||||
ScopeLock lock(_collisionLocker);
|
||||
if (ModifyCollision(info, _dataHeightmap, _terrain->_collisionLod, modifiedOffset, modifiedSize, _physicsHeightField))
|
||||
return true;
|
||||
UpdateCollisionScale();
|
||||
if (wasHeightChanged)
|
||||
UpdateCollisionScale();
|
||||
}
|
||||
#else
|
||||
// Modify heightfield samples (without cooking collision which is done on a separate async task)
|
||||
@@ -1811,6 +1788,9 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!wasHeightChanged)
|
||||
return false;
|
||||
|
||||
// Invalidate cache
|
||||
#if TERRAIN_USE_PHYSICS_DEBUG
|
||||
_debugLines.Resize(0);
|
||||
@@ -1843,18 +1823,8 @@ void TerrainPatch::SaveHeightData()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PROFILE_CPU_NAMED("Terrain.Save");
|
||||
|
||||
// Setup patch data info
|
||||
TerrainDataUpdateInfo info;
|
||||
info.ChunkSize = _terrain->_chunkSize;
|
||||
info.VertexCountEdge = info.ChunkSize + 1;
|
||||
info.HeightmapSize = info.ChunkSize * CHUNKS_COUNT_EDGE + 1;
|
||||
info.HeightmapLength = info.HeightmapSize * info.HeightmapSize;
|
||||
info.TextureSize = info.VertexCountEdge * CHUNKS_COUNT_EDGE;
|
||||
info.PatchOffset = _yOffset;
|
||||
info.PatchHeight = _yHeight;
|
||||
TerrainDataUpdateInfo info(this, _yOffset, _yHeight);
|
||||
|
||||
// Save heightmap to asset
|
||||
if (Heightmap->WaitForLoaded())
|
||||
@@ -1913,7 +1883,6 @@ void TerrainPatch::SaveSplatData(int32 index)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PROFILE_CPU_NAMED("Terrain.Save");
|
||||
|
||||
// Save splatmap to asset
|
||||
@@ -1937,6 +1906,7 @@ void TerrainPatch::SaveSplatData(int32 index)
|
||||
|
||||
bool TerrainPatch::UpdateCollision()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ScopeLock lock(_collisionLocker);
|
||||
|
||||
// Update collision
|
||||
@@ -2111,6 +2081,7 @@ void TerrainPatch::UpdatePostManualDeserialization()
|
||||
|
||||
void TerrainPatch::CreateCollision()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ASSERT(!HasCollision());
|
||||
if (CreateHeightField())
|
||||
return;
|
||||
@@ -2125,7 +2096,10 @@ void TerrainPatch::CreateCollision()
|
||||
shape.SetHeightField(_physicsHeightField, heightScale, rowScale, columnScale);
|
||||
|
||||
// Create shape
|
||||
_physicsShape = PhysicsBackend::CreateShape(_terrain, shape, _terrain->PhysicalMaterial, _terrain->IsActiveInHierarchy(), false);
|
||||
JsonAsset* materials[8];
|
||||
for (int32 i = 0;i<8;i++)
|
||||
materials[i] = _terrain->GetPhysicalMaterials()[i];
|
||||
_physicsShape = PhysicsBackend::CreateShape(_terrain, shape, ToSpan(materials, 8), _terrain->IsActiveInHierarchy(), false);
|
||||
PhysicsBackend::SetShapeLocalPose(_physicsShape, Vector3(0, _yOffset * terrainTransform.Scale.Y, 0), Quaternion::Identity);
|
||||
|
||||
// Create static actor
|
||||
@@ -2137,6 +2111,7 @@ void TerrainPatch::CreateCollision()
|
||||
|
||||
bool TerrainPatch::CreateHeightField()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ASSERT(_physicsHeightField == nullptr);
|
||||
|
||||
// Skip if height field data is missing but warn on loading failed
|
||||
@@ -2162,6 +2137,7 @@ bool TerrainPatch::CreateHeightField()
|
||||
|
||||
void TerrainPatch::UpdateCollisionScale() const
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ASSERT(HasCollision());
|
||||
|
||||
// Create geometry
|
||||
@@ -2179,6 +2155,7 @@ void TerrainPatch::UpdateCollisionScale() const
|
||||
|
||||
void TerrainPatch::DestroyCollision()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ScopeLock lock(_collisionLocker);
|
||||
ASSERT(HasCollision());
|
||||
|
||||
@@ -2205,6 +2182,7 @@ void TerrainPatch::DestroyCollision()
|
||||
|
||||
void TerrainPatch::CacheDebugLines()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ASSERT(_debugLines.IsEmpty() && _physicsHeightField);
|
||||
|
||||
int32 rows, cols;
|
||||
@@ -2213,12 +2191,21 @@ void TerrainPatch::CacheDebugLines()
|
||||
_debugLines.Resize((rows - 1) * (cols - 1) * 6 + (cols + rows - 2) * 2);
|
||||
Vector3* data = _debugLines.Get();
|
||||
|
||||
#define GET_VERTEX(x, y) const Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, (float)(row + (x)), (float)(col + (y))) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y)))
|
||||
#define GET_VERTEX(x, y) const Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y)))
|
||||
|
||||
for (int32 row = 0; row < rows - 1; row++)
|
||||
{
|
||||
for (int32 col = 0; col < cols - 1; col++)
|
||||
{
|
||||
// Skip holes
|
||||
const auto sample = PhysicsBackend::GetHeightFieldSample(_physicsHeightField, row, col);
|
||||
if (sample.MaterialIndex0 == (uint8)PhysicsBackend::HeightFieldMaterial::Hole)
|
||||
{
|
||||
for (int32 i = 0; i < 6; i++)
|
||||
*data++ = Vector3::Zero;
|
||||
continue;
|
||||
}
|
||||
|
||||
GET_VERTEX(0, 0);
|
||||
GET_VERTEX(0, 1);
|
||||
GET_VERTEX(1, 0);
|
||||
@@ -2294,6 +2281,7 @@ const Array<Vector3>& TerrainPatch::GetCollisionTriangles()
|
||||
ScopeLock lock(_collisionLocker);
|
||||
if (!_physicsShape || _collisionTriangles.HasItems())
|
||||
return _collisionTriangles;
|
||||
PROFILE_CPU();
|
||||
|
||||
int32 rows, cols;
|
||||
PhysicsBackend::GetHeightFieldSize(_physicsHeightField, rows, cols);
|
||||
@@ -2301,7 +2289,7 @@ const Array<Vector3>& TerrainPatch::GetCollisionTriangles()
|
||||
_collisionTriangles.Resize((rows - 1) * (cols - 1) * 6);
|
||||
Vector3* data = _collisionTriangles.Get();
|
||||
|
||||
#define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, (float)(row + (x)), (float)(col + (y))) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y)
|
||||
#define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y)
|
||||
|
||||
const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * CHUNKS_COUNT_EDGE;
|
||||
const Transform terrainTransform = _terrain->_transform;
|
||||
@@ -2312,6 +2300,15 @@ const Array<Vector3>& TerrainPatch::GetCollisionTriangles()
|
||||
{
|
||||
for (int32 col = 0; col < cols - 1; col++)
|
||||
{
|
||||
// Skip holes
|
||||
const auto sample = PhysicsBackend::GetHeightFieldSample(_physicsHeightField, row, col);
|
||||
if (sample.MaterialIndex0 == (uint8)PhysicsBackend::HeightFieldMaterial::Hole)
|
||||
{
|
||||
for (int32 i = 0; i < 6; i++)
|
||||
*data++ = Vector3::Zero;
|
||||
continue;
|
||||
}
|
||||
|
||||
GET_VERTEX(0, 0);
|
||||
GET_VERTEX(0, 1);
|
||||
GET_VERTEX(1, 0);
|
||||
@@ -2334,6 +2331,7 @@ const Array<Vector3>& TerrainPatch::GetCollisionTriangles()
|
||||
|
||||
void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array<Vector3>& result)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
result.Clear();
|
||||
|
||||
// Skip if no intersection with patch
|
||||
@@ -2430,6 +2428,7 @@ void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array<Vec
|
||||
|
||||
void TerrainPatch::ExtractCollisionGeometry(Array<Float3>& vertexBuffer, Array<int32>& indexBuffer)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
vertexBuffer.Clear();
|
||||
indexBuffer.Clear();
|
||||
|
||||
@@ -2459,7 +2458,7 @@ void TerrainPatch::ExtractCollisionGeometry(Array<Float3>& vertexBuffer, Array<i
|
||||
{
|
||||
for (int32 col = 0; col < cols; col++)
|
||||
{
|
||||
Float3 v((float)row, PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, (float)row, (float)col) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)col);
|
||||
Float3 v((float)row, PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row, col) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)col);
|
||||
Float3::Transform(v, world, v);
|
||||
*vb++ = v;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ class FLAXENGINE_API TerrainPatch : public ISerializable
|
||||
friend TerrainChunk;
|
||||
|
||||
public:
|
||||
|
||||
enum
|
||||
{
|
||||
CHUNKS_COUNT = 16,
|
||||
@@ -30,7 +29,6 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
Terrain* _terrain;
|
||||
int16 _x, _z;
|
||||
float _yOffset, _yHeight;
|
||||
@@ -52,7 +50,7 @@ private:
|
||||
TextureBase::InitData* _dataSplatmap[TERRAIN_MAX_SPLATMAPS_COUNT] = {};
|
||||
#endif
|
||||
#if TERRAIN_USE_PHYSICS_DEBUG
|
||||
Array<Vector3> _debugLines; // TODO: large-worlds
|
||||
Array<Vector3> _debugLines; // TODO: large-worlds
|
||||
#endif
|
||||
#if USE_EDITOR
|
||||
Array<Vector3> _collisionTriangles; // TODO: large-worlds
|
||||
@@ -62,14 +60,12 @@ private:
|
||||
void Init(Terrain* terrain, int16 x, int16 z);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="TerrainPatch"/> class.
|
||||
/// </summary>
|
||||
~TerrainPatch();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The chunks contained within the patch. Organized in 4x4 square.
|
||||
/// </summary>
|
||||
@@ -86,11 +82,9 @@ public:
|
||||
AssetReference<Texture> Splatmap[TERRAIN_MAX_SPLATMAPS_COUNT];
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Y axis heightmap offset from terrain origin.
|
||||
/// </summary>
|
||||
/// <returns>The offset.</returns>
|
||||
FORCE_INLINE float GetOffsetY() const
|
||||
{
|
||||
return _yOffset;
|
||||
@@ -99,7 +93,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the Y axis heightmap height.
|
||||
/// </summary>
|
||||
/// <returns>The height.</returns>
|
||||
FORCE_INLINE float GetHeightY() const
|
||||
{
|
||||
return _yHeight;
|
||||
@@ -108,7 +101,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the x coordinate.
|
||||
/// </summary>
|
||||
/// <returns>The x position.</returns>
|
||||
FORCE_INLINE int32 GetX() const
|
||||
{
|
||||
return _x;
|
||||
@@ -117,7 +109,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the z coordinate.
|
||||
/// </summary>
|
||||
/// <returns>The z position.</returns>
|
||||
FORCE_INLINE int32 GetZ() const
|
||||
{
|
||||
return _z;
|
||||
@@ -126,7 +117,6 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the terrain.
|
||||
/// </summary>
|
||||
/// <returns>The terrain,</returns>
|
||||
FORCE_INLINE Terrain* GetTerrain() const
|
||||
{
|
||||
return _terrain;
|
||||
@@ -168,14 +158,12 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the patch world bounds.
|
||||
/// </summary>
|
||||
/// <returns>The bounding box.</returns>
|
||||
FORCE_INLINE const BoundingBox& GetBounds() const
|
||||
{
|
||||
return _bounds;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Removes the lightmap data from the terrain patch.
|
||||
/// </summary>
|
||||
@@ -192,7 +180,6 @@ public:
|
||||
void UpdateTransform();
|
||||
|
||||
#if TERRAIN_EDITING
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the patch heightmap and collision to the default flat level.
|
||||
/// </summary>
|
||||
@@ -218,11 +205,9 @@ public:
|
||||
/// <param name="forceUseVirtualStorage">If set to <c>true</c> 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).</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false);
|
||||
|
||||
#endif
|
||||
|
||||
#if TERRAIN_UPDATING
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw pointer to the heightmap data.
|
||||
/// </summary>
|
||||
@@ -291,18 +276,15 @@ public:
|
||||
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);
|
||||
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:
|
||||
|
||||
/// <summary>
|
||||
/// Performs a raycast against this terrain collision shape.
|
||||
/// </summary>
|
||||
@@ -353,18 +335,14 @@ public:
|
||||
void ClosestPoint(const Vector3& position, Vector3& result) const;
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// Updates the patch data after manual deserialization called at runtime (eg. by editor undo).
|
||||
/// </summary>
|
||||
void UpdatePostManualDeserialization();
|
||||
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collision mesh triangles array (3 vertices per triangle in linear list). Cached internally to reuse data.
|
||||
/// </summary>
|
||||
@@ -377,7 +355,6 @@ public:
|
||||
/// <param name="bounds">The world-space bounds to find terrain triangles that intersect with it.</param>
|
||||
/// <param name="result">The result triangles that intersect with the given bounds (in world-space).</param>
|
||||
void GetCollisionTriangles(const BoundingSphere& bounds, Array<Vector3>& result);
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
@@ -388,7 +365,6 @@ public:
|
||||
void ExtractCollisionGeometry(Array<Float3>& vertexBuffer, Array<int32>& indexBuffer);
|
||||
|
||||
private:
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this patch has created collision representation.
|
||||
/// </summary>
|
||||
@@ -419,8 +395,8 @@ private:
|
||||
void DestroyCollision();
|
||||
|
||||
#if TERRAIN_USE_PHYSICS_DEBUG
|
||||
void CacheDebugLines();
|
||||
void DrawPhysicsDebug(RenderView& view);
|
||||
void CacheDebugLines();
|
||||
void DrawPhysicsDebug(RenderView& view);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
@@ -430,8 +406,8 @@ private:
|
||||
bool UpdateCollision();
|
||||
|
||||
void OnPhysicsSceneChanged(PhysicsScene* previous);
|
||||
public:
|
||||
|
||||
public:
|
||||
// [ISerializable]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
|
||||
Reference in New Issue
Block a user