Files
FlaxEngine/Source/Engine/Terrain/Terrain.cpp
2024-02-26 19:00:48 +01:00

917 lines
25 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "Terrain.h"
#include "TerrainPatch.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Ray.h"
#include "Engine/Level/Scene/SceneRendering.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Physics/PhysicalMaterial.h"
#include "Engine/Physics/PhysicsBackend.h"
#include "Engine/Graphics/RenderView.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/GlobalSignDistanceFieldPass.h"
#include "Engine/Renderer/GI/GlobalSurfaceAtlasPass.h"
Terrain::Terrain(const SpawnParams& params)
: PhysicsColliderActor(params)
, _lodBias(0)
, _forcedLod(-1)
, _collisionLod(-1)
, _lodCount(0)
, _chunkSize(0)
, _scaleInLightmap(0.1f)
, _lodDistribution(0.6f)
, _boundsExtent(Vector3::Zero)
, _cachedScale(1.0f)
{
_drawCategory = SceneRendering::SceneDrawAsync;
_physicalMaterials.Resize(8);
}
Terrain::~Terrain()
{
// Cleanup
_patches.ClearDelete();
}
void Terrain::UpdateBounds()
{
PROFILE_CPU();
_box = BoundingBox(_transform.Translation);
for (int32 i = 0; i < _patches.Count(); i++)
{
auto patch = _patches[i];
patch->UpdateBounds();
BoundingBox::Merge(_box, patch->_bounds, _box);
}
BoundingSphere::FromBox(_box, _sphere);
if (_sceneRenderingKey != -1)
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey);
}
void Terrain::CacheNeighbors()
{
PROFILE_CPU();
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
patch->Chunks[chunkIndex].CacheNeighbors();
}
}
}
void Terrain::UpdateLayerBits()
{
if (_patches.IsEmpty())
return;
// Own layer ID
const uint32 mask0 = GetLayerMask();
// Own layer mask
const uint32 mask1 = Physics::LayerMasks[GetLayer()];
// Update the shapes layer bits
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
if (patch->HasCollision())
{
PhysicsBackend::SetShapeFilterMask(patch->_physicsShape, mask0, mask1);
}
}
}
void Terrain::RemoveLightmap()
{
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
patch->RemoveLightmap();
}
}
bool Terrain::RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance) const
{
float minDistance = MAX_float;
bool result = false;
const Ray ray(origin, direction);
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
if (patch->HasCollision() &&
patch->_bounds.Intersects(ray) &&
patch->RayCast(origin, direction, resultHitDistance, maxDistance) &&
resultHitDistance < minDistance)
{
minDistance = resultHitDistance;
result = true;
}
}
resultHitDistance = minDistance;
return result;
}
bool Terrain::RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, TerrainChunk*& resultChunk, float maxDistance) const
{
float minDistance = MAX_float;
TerrainChunk* minChunk = nullptr;
bool result = false;
const Ray ray(origin, direction);
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
if (patch->HasCollision() &&
patch->_bounds.Intersects(ray) &&
patch->RayCast(origin, direction, resultHitDistance, resultChunk, maxDistance) &&
resultHitDistance < minDistance)
{
minDistance = resultHitDistance;
minChunk = resultChunk;
result = true;
}
}
resultHitDistance = minDistance;
resultChunk = minChunk;
return result;
}
bool Terrain::RayCast(const Ray& ray, float& resultHitDistance, Int2& resultPatchCoord, Int2& resultChunkCoord, float maxDistance) const
{
TerrainChunk* resultChunk;
if (RayCast(ray.Position, ray.Direction, resultHitDistance, resultChunk, maxDistance))
{
resultPatchCoord.X = resultChunk->GetPatch()->GetX();
resultPatchCoord.Y = resultChunk->GetPatch()->GetZ();
resultChunkCoord.X = resultChunk->GetX();
resultChunkCoord.Y = resultChunk->GetZ();
return true;
}
resultPatchCoord = Int2::Zero;
resultChunkCoord = Int2::Zero;
return false;
}
bool Terrain::RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance) const
{
float minDistance = MAX_float;
bool result = false;
RayCastHit tmpHit;
const Ray ray(origin, direction);
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
if (patch->HasCollision() &&
patch->_bounds.Intersects(ray) &&
patch->RayCast(origin, direction, tmpHit, maxDistance) &&
tmpHit.Distance < minDistance)
{
minDistance = tmpHit.Distance;
hitInfo = tmpHit;
result = true;
}
}
return result;
}
void Terrain::ClosestPoint(const Vector3& point, Vector3& result) const
{
Real minDistance = MAX_Real;
Vector3 tmp;
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
if (patch->HasCollision())
{
patch->ClosestPoint(point, tmp);
const auto distance = Vector3::DistanceSquared(point, tmp);
if (distance < minDistance)
{
minDistance = distance;
result = tmp;
}
}
}
}
bool Terrain::ContainsPoint(const Vector3& point) const
{
return false;
}
void Terrain::DrawPatch(const RenderContext& renderContext, const Int2& patchCoord, MaterialBase* material, int32 lodIndex) const
{
auto patch = GetPatch(patchCoord);
if (patch)
{
for (int32 i = 0; i < Terrain::ChunksCount; i++)
patch->Chunks[i].Draw(renderContext, material, lodIndex);
}
}
void Terrain::DrawChunk(const RenderContext& renderContext, const Int2& patchCoord, const Int2& chunkCoord, MaterialBase* material, int32 lodIndex) const
{
auto patch = GetPatch(patchCoord);
if (patch)
{
const auto chunk = patch->GetChunk(chunkCoord);
if (chunk)
{
chunk->Draw(renderContext, material, lodIndex);
}
}
}
#if TERRAIN_USE_PHYSICS_DEBUG
void Terrain::DrawPhysicsDebug(RenderView& view)
{
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
_patches[pathIndex]->DrawPhysicsDebug(view);
}
}
#endif
void Terrain::SetLODDistribution(float value)
{
_lodDistribution = value;
}
void Terrain::SetScaleInLightmap(float value)
{
_scaleInLightmap = value;
}
void Terrain::SetBoundsExtent(const Vector3& value)
{
if (Vector3::NearEqual(_boundsExtent, value))
return;
_boundsExtent = value;
UpdateBounds();
}
void Terrain::SetCollisionLOD(int32 value)
{
value = Math::Clamp(value, -1, TERRAIN_MAX_LODS);
if (value == _collisionLod)
return;
_collisionLod = value;
#if !BUILD_RELEASE
for (int32 i = 0; i < _patches.Count(); i++)
{
const auto patch = _patches[i];
if (patch->HasCollision())
{
LOG(Warning, "Changing Terrain CollisionLOD has no effect for patches that have already collision created. Patch {0}x{1} won't be updated.", patch->_x, patch->_z);
}
}
#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);
}
TerrainPatch* Terrain::GetPatch(int32 x, int32 z) const
{
TerrainPatch* result = nullptr;
for (int32 i = 0; i < _patches.Count(); i++)
{
const auto patch = _patches[i];
if (patch->_x == x && patch->_z == z)
{
result = patch;
break;
}
}
return result;
}
int32 Terrain::GetPatchIndex(const Int2& patchCoord) const
{
for (int32 i = 0; i < _patches.Count(); i++)
{
const auto patch = _patches[i];
if (patch->_x == patchCoord.X && patch->_z == patchCoord.Y)
return i;
}
return -1;
}
void Terrain::GetPatchCoord(int32 patchIndex, Int2& patchCoord) const
{
const auto patch = GetPatch(patchIndex);
if (patch)
{
patchCoord.X = patch->GetX();
patchCoord.Y = patch->GetZ();
}
}
void Terrain::GetPatchBounds(int32 patchIndex, BoundingBox& bounds) const
{
const auto patch = GetPatch(patchIndex);
if (patch)
{
bounds = patch->GetBounds();
}
}
void Terrain::GetChunkBounds(int32 patchIndex, int32 chunkIndex, BoundingBox& bounds) const
{
const auto patch = GetPatch(patchIndex);
if (patch)
{
const auto chunk = patch->GetChunk(chunkIndex);
if (chunk)
{
bounds = chunk->GetBounds();
}
}
}
MaterialBase* Terrain::GetChunkOverrideMaterial(const Int2& patchCoord, const Int2& chunkCoord) const
{
auto patch = GetPatch(patchCoord);
if (patch)
{
const auto chunk = patch->GetChunk(chunkCoord);
if (chunk)
{
return chunk->OverrideMaterial;
}
}
return nullptr;
}
void Terrain::SetChunkOverrideMaterial(const Int2& patchCoord, const Int2& chunkCoord, MaterialBase* value)
{
auto patch = GetPatch(patchCoord);
if (patch)
{
const auto chunk = patch->GetChunk(chunkCoord);
if (chunk)
{
chunk->OverrideMaterial = value;
}
}
}
#if TERRAIN_EDITING
bool Terrain::SetupPatchHeightMap(const Int2& patchCoord, int32 heightMapLength, const float* heightMap, const byte* holesMask, bool forceUseVirtualStorage)
{
auto patch = GetPatch(patchCoord);
if (patch)
{
return patch->SetupHeightMap(heightMapLength, heightMap, holesMask, forceUseVirtualStorage);
}
return true;
}
bool Terrain::SetupPatchSplatMap(const Int2& patchCoord, int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage)
{
auto patch = GetPatch(patchCoord);
if (patch)
{
return patch->SetupSplatMap(index, splatMapLength, splatMap, forceUseVirtualStorage);
}
return true;
}
#endif
#if TERRAIN_EDITING
void Terrain::Setup(int32 lodCount, int32 chunkSize)
{
LOG(Info, "Terrain setup for {0} LODs ({1} chunk edge quads)", lodCount, chunkSize);
_patches.ClearDelete();
_lodCount = lodCount;
_chunkSize = chunkSize;
}
void Terrain::AddPatches(const Int2& numberOfPatches)
{
if (_chunkSize == 0)
Setup();
_patches.ClearDelete();
_patches.EnsureCapacity(numberOfPatches.X * numberOfPatches.Y);
for (int32 z = 0; z < numberOfPatches.Y; z++)
{
for (int32 x = 0; x < numberOfPatches.X; x++)
{
auto patch = ::New<TerrainPatch>();
patch->Init(this, x, z);
_patches.Add(patch);
}
}
CacheNeighbors();
if (IsDuringPlay())
{
for (int32 i = 0; i < _patches.Count(); i++)
{
auto patch = _patches[i];
patch->UpdateTransform();
patch->CreateCollision();
}
UpdateLayerBits();
}
UpdateBounds();
}
void Terrain::AddPatch(const Int2& patchCoord)
{
auto patch = GetPatch(patchCoord);
if (patch != nullptr)
{
LOG(Warning, "Cannot add patch at {0}x{1}. The patch at the given location already exists.", patchCoord.X, patchCoord.Y);
return;
}
if (_chunkSize == 0)
Setup();
patch = ::New<TerrainPatch>();
patch->Init(this, patchCoord.X, patchCoord.Y);
_patches.Add(patch);
CacheNeighbors();
if (IsDuringPlay())
{
patch->UpdateTransform();
patch->CreateCollision();
UpdateLayerBits();
}
UpdateBounds();
}
void Terrain::RemovePatch(const Int2& patchCoord)
{
const auto patch = GetPatch(patchCoord);
if (patch == nullptr)
{
LOG(Warning, "Cannot remove patch at {0}x{1}. It does not exist.", patchCoord.X, patchCoord.Y);
return;
}
::Delete(patch);
_patches.Remove(patch);
CacheNeighbors();
if (IsDuringPlay())
{
UpdateBounds();
}
}
#endif
void Terrain::Draw(RenderContext& renderContext)
{
const DrawPass drawModes = DrawModes & renderContext.View.Pass;
if (drawModes == DrawPass::None)
return;
PROFILE_CPU();
if (renderContext.View.Pass == DrawPass::GlobalSDF)
{
if ((DrawModes & DrawPass::GlobalSDF) == DrawPass::None)
return;
const float chunkSize = TERRAIN_UNITS_PER_VERTEX * (float)_chunkSize;
const float posToUV = 0.25f / chunkSize;
Float4 localToUV(posToUV, posToUV, 0.0f, 0.0f);
for (const TerrainPatch* patch : _patches)
{
if (!patch->Heightmap)
continue;
Transform patchTransform;
patchTransform.Translation = patch->_offset + Vector3(0, patch->_yOffset, 0);
patchTransform.Orientation = Quaternion::Identity;
patchTransform.Scale = Float3(1.0f, patch->_yHeight, 1.0f);
patchTransform = _transform.LocalToWorld(patchTransform);
GlobalSignDistanceFieldPass::Instance()->RasterizeHeightfield(this, patch->Heightmap->GetTexture(), patchTransform, patch->_bounds, localToUV);
}
return;
}
if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
{
if ((DrawModes & DrawPass::GlobalSurfaceAtlas) == DrawPass::None)
return;
for (TerrainPatch* patch : _patches)
{
if (!patch->Heightmap)
continue;
Matrix localToWorld, worldToLocal;
BoundingSphere chunkSphere;
BoundingBox localBounds;
for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
TerrainChunk* chunk = &patch->Chunks[chunkIndex];
chunk->GetTransform().GetWorld(localToWorld); // TODO: large-worlds
Matrix::Invert(localToWorld, worldToLocal);
BoundingBox::Transform(chunk->GetBounds(), worldToLocal, localBounds);
BoundingSphere::FromBox(chunk->GetBounds(), chunkSphere);
GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, chunk, chunkSphere, chunk->GetTransform(), localBounds, 1 << 2, false);
}
}
return;
}
// Collect chunks to render and calculate LOD/material for them (required to be done before to gather NeighborLOD)
_drawChunks.Clear();
// Frustum vs Box culling for patches
const BoundingFrustum frustum = renderContext.View.CullingFrustum;
const Vector3 origin = renderContext.View.Origin;
for (int32 patchIndex = 0; patchIndex < _patches.Count(); patchIndex++)
{
const auto patch = _patches[patchIndex];
BoundingBox bounds(patch->_bounds.Minimum - origin, patch->_bounds.Maximum - origin);
if (renderContext.View.IsCullingDisabled || frustum.Intersects(bounds))
{
// Skip if has no heightmap or it's not loaded
if (patch->Heightmap == nullptr || patch->Heightmap->GetTexture()->ResidentMipLevels() == 0)
continue;
// Frustum vs Box culling for chunks
for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
auto chunk = &patch->Chunks[chunkIndex];
chunk->_cachedDrawLOD = 0;
bounds = BoundingBox(chunk->_bounds.Minimum - origin, chunk->_bounds.Maximum - origin);
if (renderContext.View.IsCullingDisabled || frustum.Intersects(bounds))
{
if (chunk->PrepareDraw(renderContext))
{
// Add chunk for drawing
_drawChunks.Add(chunk);
}
}
}
}
else
{
// Reset cached LOD for chunks (prevent LOD transition from invisible chunks)
for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
auto chunk = &patch->Chunks[chunkIndex];
chunk->_cachedDrawLOD = 0;
}
}
}
// Draw all visible chunks
for (int32 i = 0; i < _drawChunks.Count(); i++)
{
_drawChunks.Get()[i]->Draw(renderContext);
}
}
#if USE_EDITOR
//#include "Engine/Debug/DebugDraw.h"
void Terrain::OnDebugDrawSelected()
{
Actor::OnDebugDrawSelected();
/*
// TODO: add editor option to draw selected terrain chunks bounds?
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
auto chunk = &patch->Chunks[chunkIndex];
DebugDraw::DrawBox(chunk->_bounds, Color(chunk->_x / (float)Terrain::ChunksCountEdge, 1.0f, chunk->_z / (float)Terrain::ChunksCountEdge));
}
}
*/
}
#endif
bool Terrain::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)
{
float minDistance = MAX_float;
Vector3 minDistanceNormal = Vector3::Up;
float tmpDistance;
Vector3 tmpNormal;
bool result = false;
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
if (patch->HasCollision() &&
patch->_bounds.Intersects(ray) &&
patch->RayCast(ray.Position, ray.Direction, tmpDistance, tmpNormal) &&
tmpDistance < minDistance)
{
minDistance = tmpDistance;
minDistanceNormal = tmpNormal;
result = true;
}
}
distance = minDistance;
normal = minDistanceNormal;
return result;
}
void Terrain::Serialize(SerializeStream& stream, const void* otherObj)
{
// Base
Actor::Serialize(stream, otherObj);
SERIALIZE_GET_OTHER_OBJ(Terrain);
SERIALIZE_MEMBER(LODBias, _lodBias);
SERIALIZE_MEMBER(ForcedLOD, _forcedLod);
SERIALIZE_MEMBER(LODDistribution, _lodDistribution);
SERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap);
SERIALIZE_MEMBER(BoundsExtent, _boundsExtent);
SERIALIZE_MEMBER(CollisionLOD, _collisionLod);
SERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials);
SERIALIZE(Material);
SERIALIZE(DrawModes);
SERIALIZE_MEMBER(LODCount, _lodCount);
SERIALIZE_MEMBER(ChunkSize, _chunkSize);
if (_patches.HasItems())
{
stream.JKEY("Patches");
stream.StartArray();
for (int32 patchIndex = 0; patchIndex < _patches.Count(); patchIndex++)
{
stream.StartObject();
_patches[patchIndex]->Serialize(stream, other && other->_patches.Count() == _patches.Count() ? other->_patches[patchIndex] : nullptr);
stream.EndObject();
}
stream.EndArray();
}
}
void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
// Base
Actor::Deserialize(stream, modifier);
auto member = stream.FindMember("LODBias");
if (member != stream.MemberEnd() && member->value.IsInt())
{
SetLODBias(member->value.GetInt());
}
member = stream.FindMember("ForcedLOD");
if (member != stream.MemberEnd() && member->value.IsInt())
{
SetForcedLOD(member->value.GetInt());
}
member = stream.FindMember("CollisionLOD");
if (member != stream.MemberEnd() && member->value.IsInt())
{
SetCollisionLOD(member->value.GetInt());
}
DESERIALIZE_MEMBER(LODDistribution, _lodDistribution);
DESERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap);
DESERIALIZE_MEMBER(BoundsExtent, _boundsExtent);
DESERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials);
DESERIALIZE(Material);
DESERIALIZE(DrawModes);
member = stream.FindMember("LODCount");
if (member != stream.MemberEnd() && member->value.IsInt())
{
_lodCount = member->value.GetInt();
}
member = stream.FindMember("ChunkSize");
if (member != stream.MemberEnd() && member->value.IsInt())
{
_chunkSize = member->value.GetInt();
}
member = stream.FindMember("Patches");
if (member != stream.MemberEnd() && member->value.IsArray())
{
auto& patchesData = member->value;
const auto patchesCount = (int32)patchesData.Size();
// Update patches if collection size is different
if (patchesCount != _patches.Count())
{
_patches.ClearDelete();
for (int32 i = 0; i < patchesCount; i++)
{
auto patch = ::New<TerrainPatch>();
patch->Init(this, 0, 0);
_patches.Add(patch);
}
}
// Load patches
for (int32 i = 0; i < patchesCount; i++)
{
auto patch = _patches[i];
auto& patchData = patchesData[i];
patch->Deserialize(patchData, modifier);
}
#if !BUILD_RELEASE
// Validate patches locations
for (int32 i = 0; i < patchesCount; i++)
{
const auto patch = _patches[i];
for (int32 j = i + 1; j < patchesCount; j++)
{
if (_patches[j]->_x == patch->_x && _patches[j]->_z == patch->_z)
{
LOG(Warning, "Invalid terrain data! Overlapping terrain patches.");
}
}
}
#endif
}
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
// [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
{
// Terrains are always static things
return nullptr;
}
void Terrain::OnEnable()
{
GetSceneRendering()->AddActor(this, _sceneRenderingKey);
#if TERRAIN_USE_PHYSICS_DEBUG
GetSceneRendering()->AddPhysicsDebug<Terrain, &Terrain::DrawPhysicsDebug>(this);
#endif
// Base
Actor::OnEnable();
}
void Terrain::OnDisable()
{
GetSceneRendering()->RemoveActor(this, _sceneRenderingKey);
#if TERRAIN_USE_PHYSICS_DEBUG
GetSceneRendering()->RemovePhysicsDebug<Terrain, &Terrain::DrawPhysicsDebug>(this);
#endif
// Base
Actor::OnDisable();
}
void Terrain::OnTransformChanged()
{
// Base
Actor::OnTransformChanged();
for (int32 i = 0; i < _patches.Count(); i++)
{
auto patch = _patches[i];
patch->UpdateTransform();
}
if (!Float3::NearEqual(_cachedScale, _transform.Scale))
{
_cachedScale = _transform.Scale;
for (int32 i = 0; i < _patches.Count(); i++)
{
auto patch = _patches[i];
if (patch->HasCollision())
{
patch->UpdateCollisionScale();
}
}
}
UpdateBounds();
}
void Terrain::OnLayerChanged()
{
// Base
Actor::OnLayerChanged();
UpdateLayerBits();
if (_sceneRenderingKey != -1)
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey);
}
void Terrain::OnActiveInTreeChanged()
{
// Base
Actor::OnActiveInTreeChanged();
// Update physics
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
if (patch->HasCollision())
{
PhysicsBackend::SetShapeState(patch->_physicsShape, IsActiveInHierarchy(), false);
}
}
}
void Terrain::OnPhysicsSceneChanged(PhysicsScene* previous)
{
PhysicsColliderActor::OnPhysicsSceneChanged(previous);
for (auto patch : _patches)
patch->OnPhysicsSceneChanged(previous);
}
void Terrain::BeginPlay(SceneBeginData* data)
{
CacheNeighbors();
_cachedScale = _transform.Scale;
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
if (!patch->HasCollision())
{
patch->CreateCollision();
}
}
UpdateLayerBits();
// Base
Actor::BeginPlay(data);
}
void Terrain::EndPlay()
{
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
const auto patch = _patches[pathIndex];
if (patch->HasCollision())
{
patch->DestroyCollision();
}
}
// Base
Actor::EndPlay();
}