diff --git a/Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs b/Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs index 30ffb6866..8aaf5b7d6 100644 --- a/Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/TerrainEditor.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +using FlaxEditor.Utilities; using FlaxEngine; namespace FlaxEditor.CustomEditors.Dedicated @@ -29,8 +30,8 @@ namespace FlaxEditor.CustomEditors.Dedicated chunkSize, 1.0f / (resolution.X + 1e-9f), 1.0f / (resolution.Z + 1e-9f), - totalSize.X * 0.00001f, - totalSize.Z * 0.00001f + totalSize.X / Units.Meters2Units * 0.001f, + totalSize.Z / Units.Meters2Units * 0.001f ); var label = layout.Label(text); label.Label.AutoHeight = true; diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index b2867c2df..31295253f 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -602,3 +602,13 @@ bool ManagedEditor::CreateAsset(const String& tag, String outputPath) FileSystem::NormalizePath(outputPath); return AssetsImportingManager::Create(tag, outputPath); } + +Array ManagedEditor::GetAssetReferences(const Guid& assetId) +{ + Array result; + if (auto* asset = Content::Load(assetId)) + { + asset->GetReferences(result); + } + return result; +} diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index 5c7907d17..eb1cceef8 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -218,6 +218,13 @@ public: /// Output asset path. API_FUNCTION() static bool CreateAsset(const String& tag, String outputPath); + /// + /// Gets a list of asset references of a given asset. + /// + /// The asset ID. + /// List of referenced assets. + API_FUNCTION() static Array GetAssetReferences(const Guid& assetId); + public: API_STRUCT(Internal, NoDefault) struct VisualScriptStackFrame { diff --git a/Source/Editor/SceneGraph/Actors/TerrainNode.cs b/Source/Editor/SceneGraph/Actors/TerrainNode.cs index 1f95981bd..099dd5347 100644 --- a/Source/Editor/SceneGraph/Actors/TerrainNode.cs +++ b/Source/Editor/SceneGraph/Actors/TerrainNode.cs @@ -1,5 +1,9 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using FlaxEngine; namespace FlaxEditor.SceneGraph.Actors @@ -10,6 +14,15 @@ namespace FlaxEditor.SceneGraph.Actors [HideInEditor] public sealed class TerrainNode : ActorNode { + private struct RemovedTerrain + { + public Guid SceneId; + public Guid TerrainId; + public string[] Files; + } + + private static List _cleanupFiles; + /// public TerrainNode(Actor actor) : base(actor) @@ -18,5 +31,68 @@ namespace FlaxEditor.SceneGraph.Actors /// public override bool AffectsNavigation => true; + + /// + public override void Delete() + { + // Schedule terrain data files for automatic cleanup + if (_cleanupFiles == null) + { + _cleanupFiles = new List(); + Engine.RequestingExit += OnRequestingExit; + } + if (Actor is Terrain terrain) + { + var removed = new RemovedTerrain + { + SceneId = terrain.Scene?.ID ?? Guid.Empty, + TerrainId = terrain.ID, + Files = new string[4], + }; + for (int i = 0; i < terrain.PatchesCount; i++) + { + var patch = terrain.GetPatch(i); + if (patch.Heightmap) + removed.Files[0] = patch.Heightmap.Path; + if (patch.Heightfield) + removed.Files[1] = patch.Heightfield.Path; + if (patch.GetSplatmap(0)) + removed.Files[2] = patch.GetSplatmap(0).Path; + if (patch.GetSplatmap(1)) + removed.Files[3] = patch.GetSplatmap(1).Path; + } + _cleanupFiles.Add(removed); + } + + base.Delete(); + } + + void OnRequestingExit() + { + foreach (var e in _cleanupFiles) + { + try + { + // Skip removing this terrain file sif it's still referenced + var sceneReferences = Editor.GetAssetReferences(e.SceneId); + if (sceneReferences != null && sceneReferences.Contains(e.TerrainId)) + continue; + + // Delete files + foreach (var file in e.Files) + { + if (file != null && File.Exists(file)) + File.Delete(file); + } + } + catch (Exception ex) + { + Editor.LogWarning(ex); + } + } + _cleanupFiles.Clear(); + _cleanupFiles = null; + Engine.RequestingExit -= OnRequestingExit; + } } } diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp index ce23b92a4..a06150875 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp @@ -92,7 +92,7 @@ void SceneAnimationPlayer::Pause() if (_state != PlayState::Playing) return; - if (IsActiveInHierarchy() && _state == PlayState::Playing) + if (IsActiveInHierarchy()) { UNREGISTER_TICK; } diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index 7281fa8d8..a17d8f686 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -1027,6 +1027,13 @@ public: return *this; } + Iterator& operator=(Iterator&& v) + { + _array = v._array; + _index = v._index; + return *this; + } + Iterator& operator++() { if (_index != _array->_count) diff --git a/Source/Engine/Core/Collections/ChunkedArray.h b/Source/Engine/Core/Collections/ChunkedArray.h index 157efcd0f..457778b26 100644 --- a/Source/Engine/Core/Collections/ChunkedArray.h +++ b/Source/Engine/Core/Collections/ChunkedArray.h @@ -173,6 +173,14 @@ public: return *this; } + Iterator& operator=(Iterator&& v) + { + _collection = v._collection; + _chunkIndex = v._chunkIndex; + _index = v._index; + return *this; + } + Iterator& operator++() { // Check if it is not at end diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index 7c180e56e..9e516dae7 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -348,6 +348,13 @@ public: return *this; } + Iterator& operator=(Iterator&& v) + { + _collection = v._collection; + _index = v._index; + return *this; + } + Iterator& operator++() { const int32 capacity = _collection->_size; diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index 12378f2ea..b53225044 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -329,6 +329,13 @@ public: return *this; } + Iterator& operator=(Iterator&& v) + { + _collection = v._collection; + _index = v._index; + return *this; + } + Iterator& operator++() { const int32 capacity = _collection->_size; diff --git a/Source/Engine/Core/Types/DataContainer.h b/Source/Engine/Core/Types/DataContainer.h index 7893eb1ca..9d506b1eb 100644 --- a/Source/Engine/Core/Types/DataContainer.h +++ b/Source/Engine/Core/Types/DataContainer.h @@ -338,7 +338,7 @@ public: Platform::MemoryCopy(Base::_data, prev, prevLength * sizeof(T)); Platform::MemoryCopy(Base::_data + prevLength * sizeof(T), data, length * sizeof(T)); - if (_isAllocated && prev) + if (_isAllocated) Allocator::Free(prev); _isAllocated = true; } diff --git a/Source/Engine/Core/Types/StringView.h b/Source/Engine/Core/Types/StringView.h index 2e9e9aaf5..bdcfbc50b 100644 --- a/Source/Engine/Core/Types/StringView.h +++ b/Source/Engine/Core/Types/StringView.h @@ -28,6 +28,12 @@ protected: { } + StringViewBase(const StringViewBase& other) + : _data(other._data) + , _length(other._length) + { + } + public: typedef T CharType; diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index 530c5c662..c3e395992 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -133,9 +133,10 @@ void Foliage::DrawInstance(RenderContext& renderContext, FoliageInstance& instan void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, const FoliageType& type, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const { // Skip clusters that around too far from view - const Vector3 viewOrigin = renderContext.View.Origin; - if (Float3::Distance(renderContext.View.Position, cluster->TotalBoundsSphere.Center - viewOrigin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance) + const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View); + if (Float3::Distance(lodView->Position, cluster->TotalBoundsSphere.Center - lodView->Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance) return; + const Vector3 viewOrigin = renderContext.View.Origin; //DebugDraw::DrawBox(cluster->Bounds, Color::Red); @@ -168,7 +169,7 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, auto& instance = *cluster->Instances.Get()[i]; BoundingSphere sphere = instance.Bounds; sphere.Center -= viewOrigin; - if (Float3::Distance(renderContext.View.Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance && + if (Float3::Distance(lodView->Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance && renderContext.View.CullingFrustum.Intersects(sphere)) { const auto modelFrame = instance.DrawState.PrevFrame + 1; @@ -262,9 +263,10 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, Mesh::DrawInfo& draw) { // Skip clusters that around too far from view - const Vector3 viewOrigin = renderContext.View.Origin; - if (Float3::Distance(renderContext.View.Position, cluster->TotalBoundsSphere.Center - viewOrigin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance) + const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View); + if (Float3::Distance(lodView->Position, cluster->TotalBoundsSphere.Center - lodView->Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance) return; + const Vector3 viewOrigin = renderContext.View.Origin; //DebugDraw::DrawBox(cluster->Bounds, Color::Red); @@ -300,7 +302,7 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, // Check if can draw this instance if (type._canDraw && - Float3::Distance(renderContext.View.Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance && + Float3::Distance(lodView->Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance && renderContext.View.CullingFrustum.Intersects(sphere)) { Matrix world; diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 559c916d8..205c87a87 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -289,9 +289,9 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa // Check if need to setup ribbon modules int32 ribbonModuleIndex = 0; int32 ribbonModulesDrawIndicesPos = 0; - int32 ribbonModulesDrawIndicesStart[PARTICLE_EMITTER_MAX_RIBBONS]; - int32 ribbonModulesDrawIndicesCount[PARTICLE_EMITTER_MAX_RIBBONS]; - int32 ribbonModulesSegmentCount[PARTICLE_EMITTER_MAX_RIBBONS]; + int32 ribbonModulesDrawIndicesStart[PARTICLE_EMITTER_MAX_RIBBONS] = {}; + int32 ribbonModulesDrawIndicesCount[PARTICLE_EMITTER_MAX_RIBBONS] = {}; + int32 ribbonModulesSegmentCount[PARTICLE_EMITTER_MAX_RIBBONS] = {}; if (emitter->Graph.RibbonRenderingModules.HasItems()) { // Prepare ribbon data diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index bd7beac76..63cfc8c16 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -481,7 +481,7 @@ bool CachedPSO::Init(GPUShader* shader, bool useDepth) // Create pipeline states GPUPipelineState::Description desc = GPUPipelineState::Description::DefaultFullscreenTriangle; - desc.DepthEnable = desc.DepthWriteEnable = useDepth; + desc.DepthEnable = useDepth; desc.DepthWriteEnable = false; desc.DepthClipEnable = false; desc.VS = shader->GetVS("VS"); diff --git a/Source/Engine/Scripting/ScriptingType.h b/Source/Engine/Scripting/ScriptingType.h index 01335b6cb..f53778401 100644 --- a/Source/Engine/Scripting/ScriptingType.h +++ b/Source/Engine/Scripting/ScriptingType.h @@ -40,6 +40,12 @@ struct FLAXENGINE_API ScriptingTypeHandle { } + FORCE_INLINE ScriptingTypeHandle(ScriptingTypeHandle&& other) + : Module(other.Module) + , TypeIndex(other.TypeIndex) + { + } + ScriptingTypeHandle(const ScriptingTypeInitializer& initializer); FORCE_INLINE operator bool() const diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 53881e210..10c137d33 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -291,7 +291,7 @@ void Terrain::SetPhysicalMaterials(const Array drawnChunks; + for (RenderContext& renderContext : renderContextBatch.Contexts) + { + const DrawPass drawModes = DrawModes & renderContext.View.Pass; + if (drawModes == DrawPass::None) + continue; + DrawImpl(renderContext, drawnChunks); + } +} + 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 (DrawSetup(renderContext)) + return; + HashSet drawnChunks; + DrawImpl(renderContext, drawnChunks); +} + +bool Terrain::DrawSetup(RenderContext& renderContext) +{ + // Special drawing modes + const DrawPass drawModes = DrawModes & renderContext.View.Pass; + if (drawModes == 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); @@ -533,12 +556,10 @@ void Terrain::Draw(RenderContext& renderContext) patchTransform = _transform.LocalToWorld(patchTransform); GlobalSignDistanceFieldPass::Instance()->RasterizeHeightfield(this, patch->Heightmap->GetTexture(), patchTransform, patch->_bounds, localToUV); } - return; + return true; } - if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) + if (drawModes == DrawPass::GlobalSurfaceAtlas) { - if ((DrawModes & DrawPass::GlobalSurfaceAtlas) == DrawPass::None) - return; for (TerrainPatch* patch : _patches) { if (!patch->Heightmap) @@ -556,11 +577,27 @@ void Terrain::Draw(RenderContext& renderContext) GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, chunk, chunkSphere, chunk->GetTransform(), localBounds, 1 << 2, false); } } - return; + return true; } + // Reset cached LOD for chunks (prevent LOD transition from invisible chunks) + for (int32 patchIndex = 0; patchIndex < _patches.Count(); patchIndex++) + { + const auto patch = _patches[patchIndex]; + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) + { + auto chunk = &patch->Chunks[chunkIndex]; + chunk->_cachedDrawLOD = 0; + } + } + + return false; +} + +void Terrain::DrawImpl(RenderContext& renderContext, HashSet& drawnChunks) +{ // Collect chunks to render and calculate LOD/material for them (required to be done before to gather NeighborLOD) - _drawChunks.Clear(); + Array drawChunks; // Frustum vs Box culling for patches const BoundingFrustum frustum = renderContext.View.CullingFrustum; @@ -579,33 +616,24 @@ void Terrain::Draw(RenderContext& renderContext) 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); - } + if (!drawnChunks.Contains(chunk) && !chunk->PrepareDraw(renderContext)) + continue; + + // Add chunk for drawing + drawChunks.Add(chunk); + drawnChunks.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++) + for (int32 i = 0; i < drawChunks.Count(); i++) { - _drawChunks.Get()[i]->Draw(renderContext); + drawChunks.Get()[i]->Draw(renderContext); } } diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index 17f4bdccd..d9ef89fb5 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -72,7 +72,6 @@ private: Vector3 _boundsExtent; Float3 _cachedScale; Array> _patches; - Array _drawChunks; Array, FixedAllocation<8>> _physicalMaterials; public: @@ -424,9 +423,12 @@ private: #if TERRAIN_USE_PHYSICS_DEBUG void DrawPhysicsDebug(RenderView& view); #endif + bool DrawSetup(RenderContext& renderContext); + void DrawImpl(RenderContext& renderContext, HashSet& drawnChunks); public: // [PhysicsColliderActor] + void Draw(RenderContextBatch& renderContextBatch) override; void Draw(RenderContext& renderContext) override; #if USE_EDITOR void OnDebugDrawSelected() override; diff --git a/Source/Engine/Terrain/TerrainChunk.h b/Source/Engine/Terrain/TerrainChunk.h index f73303ae1..b4d70ec6d 100644 --- a/Source/Engine/Terrain/TerrainChunk.h +++ b/Source/Engine/Terrain/TerrainChunk.h @@ -34,9 +34,9 @@ private: float _perInstanceRandom; float _yOffset, _yHeight; - TerrainChunk* _neighbors[4]; - byte _cachedDrawLOD; - IMaterial* _cachedDrawMaterial; + TerrainChunk* _neighbors[4] = {}; + byte _cachedDrawLOD = 0; + IMaterial* _cachedDrawMaterial = nullptr; void Init(TerrainPatch* patch, uint16 x, uint16 z); diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 93269269a..592c2e7f9 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -113,6 +113,11 @@ TerrainPatch::~TerrainPatch() #endif } +RawDataAsset* TerrainPatch::GetHeightfield() const +{ + return _heightfield.Get(); +} + void TerrainPatch::RemoveLightmap() { for (auto& chunk : Chunks) @@ -1215,7 +1220,8 @@ Color32* TerrainPatch::GetSplatMapData(int32 index) void TerrainPatch::ClearSplatMapCache() { PROFILE_CPU_NAMED("Terrain.ClearSplatMapCache"); - _cachedSplatMap->Clear(); + if (_cachedSplatMap) + _cachedSplatMap->Clear(); } void TerrainPatch::ClearCache() diff --git a/Source/Engine/Terrain/TerrainPatch.h b/Source/Engine/Terrain/TerrainPatch.h index 295f37144..05a7693fe 100644 --- a/Source/Engine/Terrain/TerrainPatch.h +++ b/Source/Engine/Terrain/TerrainPatch.h @@ -149,6 +149,12 @@ public: return GetChunk(z * Terrain::ChunksCountEdge + x); } + /// + /// Gets the heightfield collision data asset. + /// + /// The heightfield data asset. + API_PROPERTY() RawDataAsset* GetHeightfield() const; + /// /// Gets the splatmap assigned to this patch. ///