From c3aecc1a11eed5faef756685445654662e849a47 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 10 Mar 2026 19:47:09 -0500 Subject: [PATCH 1/5] Add allowable characters enum to text box --- Source/Engine/UI/GUI/Common/TextBoxBase.cs | 90 ++++++++++++++++++++-- 1 file changed, 83 insertions(+), 7 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index 489e4c93d..201e91c61 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -1,6 +1,8 @@ // Copyright (c) Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; +using System.Linq; #if FLAX_EDITOR using FlaxEditor.Options; #endif @@ -42,6 +44,38 @@ namespace FlaxEngine.GUI '<', }; + /// + /// The allowable characters to use for the text. + /// + [Flags] + public enum AllowableCharacters + { + /// + /// Wether to not allow any character in the text. + /// + None = 0, + + /// + /// Whether to use letters in the text. + /// + Letters = 1 << 0, + + /// + /// Whether to use numbers in the text. + /// + Numbers = 1 << 1, + + /// + /// Whether to use symbols in the text. + /// + Symbols = 1 << 2, + + /// + /// Whether to use all characters in the text. + /// + All = Letters | Numbers | Symbols, + } + /// /// Default height of the text box /// @@ -86,6 +120,11 @@ namespace FlaxEngine.GUI /// Flag used to indicate whenever text can contain multiple lines. /// protected bool _isMultiline; + + /// + /// The characters to allow in the text. + /// + protected AllowableCharacters _charactersToAllow = AllowableCharacters.All; /// /// Flag used to indicate whenever text is read-only and cannot be modified by the user. @@ -188,6 +227,16 @@ namespace FlaxEngine.GUI } } + /// + /// The character to allow in the text. + /// + [EditorOrder(41), Tooltip("The character to allow in the text.")] + public AllowableCharacters CharactersToAllow + { + get => _charactersToAllow; + set => _charactersToAllow = value; + } + /// /// Gets or sets the maximum number of characters the user can type into the text box control. /// @@ -395,15 +444,42 @@ namespace FlaxEngine.GUI value = value.GetLines()[0]; } - if (_text != value) + if (_text.Equals(value, StringComparison.Ordinal)) + return; + + if (CharactersToAllow != AllowableCharacters.All) { - Deselect(); - ResetViewOffset(); - - _text = value; - - OnTextChanged(); + if (CharactersToAllow == AllowableCharacters.None) + { + value = string.Empty; + } + else + { + if (!CharactersToAllow.HasFlag(AllowableCharacters.Letters)) + { + if (value != null) + value = new string(value.Where(c => !char.IsLetter(c)).ToArray()); + } + if (!CharactersToAllow.HasFlag(AllowableCharacters.Numbers)) + { + if (value != null) + value = new string(value.Where(c => !char.IsNumber(c)).ToArray()); + } + if (!CharactersToAllow.HasFlag(AllowableCharacters.Symbols)) + { + if (value != null) + value = new string(value.Where(c => !char.IsSymbol(c)).ToArray()); + } + value ??= string.Empty; + } } + + Deselect(); + ResetViewOffset(); + + _text = value; + + OnTextChanged(); } /// From 62492c7607e9637a12dd383801f38f18318ae8cc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Mar 2026 09:38:49 +0100 Subject: [PATCH 2/5] Fix importing file when target location already exists #3579 --- .../AssetsImportingManager.cpp | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp index 9cad287dc..696f43a9e 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp +++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp @@ -36,6 +36,30 @@ #include "CreateAnimation.h" #include "CreateBehaviorTree.h" #include "CreateJson.h" +#include "Engine/Content/Assets/Model.h" + +namespace +{ + bool IsAssetTypeNameTextureFile(const String& typeName) + { + return typeName == Texture::TypeName || typeName == SpriteAtlas::TypeName; + } + + bool IsAssetTypeNameModelFile(const String& typeName) + { + return typeName == Model::TypeName || typeName == SkinnedModel::TypeName || typeName == Animation::TypeName; + } + + bool IsAssetTypeNameMatch(const String& a, const String& b) + { + // Special case when reimporting model/texture but different type + if (IsAssetTypeNameTextureFile(a) && IsAssetTypeNameTextureFile(b)) + return true; + if (IsAssetTypeNameModelFile(a) && IsAssetTypeNameModelFile(b)) + return true; + return a == b; + } +} // Tags used to detect asset creation mode const String AssetsImportingManager::CreateTextureTag(TEXT("Texture")); @@ -84,8 +108,6 @@ CreateAssetContext::CreateAssetContext(const StringView& inputPath, const String CustomArg = arg; Data.Header.ID = id; SkipMetadata = false; - - // TODO: we should use ASNI only chars path (Assimp can use only that kind) OutputPath = Content::CreateTemporaryAssetPath(); } @@ -122,6 +144,24 @@ CreateAssetResult CreateAssetContext::Run(const CreateAssetFunction& callback) Data.Metadata.Copy((const byte*)buffer.GetString(), (uint32)buffer.GetSize()); } + // Check if target asset already exists but has different type + AssetInfo targetAssetInfo; + if (Content::GetAssetInfo(TargetAssetPath, targetAssetInfo) && !IsAssetTypeNameMatch(targetAssetInfo.TypeName, Data.Header.TypeName)) + { + // Change path + int32 index = 0; + String newTargetAssetPath; + do + { + newTargetAssetPath = StringUtils::GetDirectoryName(TargetAssetPath); + newTargetAssetPath /= StringUtils::GetFileNameWithoutExtension(TargetAssetPath) + String::Format(TEXT(" ({})."), index++) + FileSystem::GetExtension(TargetAssetPath); + } while (index < 100 && FileSystem::FileExists(newTargetAssetPath)); + TargetAssetPath = newTargetAssetPath; + + // Change id + Data.Header.ID = Guid::New(); + } + // Save file result = FlaxStorage::Create(OutputPath, Data) ? CreateAssetResult::CannotSaveFile : CreateAssetResult::Ok; if (result == CreateAssetResult::Ok) From 479c5f896cf6a7839040732624f63264074b24ea Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Mar 2026 13:43:26 +0100 Subject: [PATCH 3/5] Fix `MeshAccelerationStructure` to use `MeshAccessor` for proper mesh format access #3984 --- Source/Engine/Graphics/Models/MeshBase.cpp | 8 +++++ .../ModelTool/MeshAccelerationStructure.cpp | 32 ++++++++----------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp index eadbfcba9..bbec523a2 100644 --- a/Source/Engine/Graphics/Models/MeshBase.cpp +++ b/Source/Engine/Graphics/Models/MeshBase.cpp @@ -158,6 +158,14 @@ void MeshAccessor::Stream::CopyTo(Span dst) const { Platform::MemoryCopy(dst.Get(), _data.Get(), _data.Length()); } + else if (IsLinear(PixelFormat::R16G16B16A16_Float)) + { + for (int32 i = 0; i < count; i++) + { + auto v = *(Half4*)(_data.Get() + i * _stride); + dst.Get()[i] = Float3(Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y), Float16Compressor::Decompress(v.Z)); + } + } else { for (int32 i = 0; i < count; i++) diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp index 6e1b5ae6f..297a6f868 100644 --- a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp @@ -9,6 +9,7 @@ #include "Engine/Content/Assets/Model.h" #include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/Models/ModelData.h" +#include "Engine/Graphics/Models/MeshAccessor.h" #include "Engine/Profiler/ProfilerCPU.h" PACK_STRUCT(struct GPUBVH { @@ -321,7 +322,6 @@ void MeshAccelerationStructure::Add(Model* model, int32 lodIndex) lodIndex = Math::Clamp(lodIndex, model->HighestResidentLODIndex(), model->LODs.Count() - 1); ModelLOD& lod = model->LODs[lodIndex]; _meshes.EnsureCapacity(_meshes.Count() + lod.Meshes.Count()); - bool failed = false; for (int32 i = 0; i < lod.Meshes.Count(); i++) { auto& mesh = lod.Meshes[i]; @@ -336,25 +336,19 @@ void MeshAccelerationStructure::Add(Model* model, int32 lodIndex) auto& meshData = _meshes.AddOne(); meshData.Asset = model; model->AddReference(); - if (model->IsVirtual()) - { - meshData.Indices = mesh.GetTriangleCount() * 3; - meshData.Vertices = mesh.GetVertexCount(); - failed |= mesh.DownloadDataGPU(MeshBufferType::Index, meshData.IndexBuffer); - failed |= mesh.DownloadDataGPU(MeshBufferType::Vertex0, meshData.VertexBuffer); - } - else - { - failed |= mesh.DownloadDataCPU(MeshBufferType::Index, meshData.IndexBuffer, meshData.Indices); - failed |= mesh.DownloadDataCPU(MeshBufferType::Vertex0, meshData.VertexBuffer, meshData.Vertices); - } - if (failed) + MeshAccessor accessor; + MeshBufferType bufferTypes[2] = { MeshBufferType::Index, MeshBufferType::Vertex0 }; + if (accessor.LoadMesh(&mesh, false, ToSpan(bufferTypes, 2))) return; - if (!meshData.IndexBuffer.IsAllocated() && meshData.IndexBuffer.Length() != 0) - { - // BVH nodes modifies index buffer (sorts data in-place) so clone it - meshData.IndexBuffer.Copy(meshData.IndexBuffer.Get(), meshData.IndexBuffer.Length()); - } + auto indexStream = accessor.Index(); + auto positionStream = accessor.Position(); + if (!indexStream.IsValid() || !positionStream.IsValid()) + return; + meshData.Indices = indexStream.GetCount(); + meshData.Vertices = positionStream.GetCount(); + meshData.IndexBuffer.Copy(indexStream.GetData()); + meshData.VertexBuffer.Allocate(meshData.Vertices * sizeof(Float3)); + positionStream.CopyTo(ToSpan(meshData.VertexBuffer.Get(), meshData.Vertices)); meshData.Use16BitIndexBuffer = mesh.Use16BitIndexBuffer(); meshData.Bounds = mesh.GetBox(); } From 96bbae8e28712f24b3969e5220703635caed1f97 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Mar 2026 16:01:56 +0100 Subject: [PATCH 4/5] Fix Global SDF update when changing Draw Modes of the model Refactor `DrawModes` field in `StaticModel` and `FoliageType` into property with getter/setter #3949 --- Source/Engine/Foliage/Foliage.cpp | 16 ++++++--- Source/Engine/Foliage/FoliageType.cpp | 20 +++++++++-- Source/Engine/Foliage/FoliageType.h | 14 ++++++-- Source/Engine/Level/Actors/StaticModel.cpp | 34 +++++++++++++------ Source/Engine/Level/Actors/StaticModel.h | 18 ++++++---- Source/Engine/Level/Scene/SceneRendering.h | 1 + .../Renderer/GlobalSignDistanceFieldPass.cpp | 3 +- 7 files changed, 79 insertions(+), 27 deletions(-) diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index efe89bf5c..459f1b662 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -22,7 +22,7 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Utilities/Encryption.h" -#define FOLIAGE_GET_DRAW_MODES(renderContext, type) (type.DrawModes & renderContext.View.Pass & renderContext.View.GetShadowsDrawPassMask(type.ShadowsMode)) +#define FOLIAGE_GET_DRAW_MODES(renderContext, type) (type._drawModes & renderContext.View.Pass & renderContext.View.GetShadowsDrawPassMask(type.ShadowsMode)) #define FOLIAGE_CAN_DRAW(renderContext, type) (type.IsReady() && FOLIAGE_GET_DRAW_MODES(renderContext, type) != DrawPass::None && type.Model->CanBeRendered()) Foliage::Foliage(const SpawnParams& params) @@ -360,7 +360,7 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::D draw.DrawState = &instance.DrawState; draw.Bounds = sphere; draw.PerInstanceRandom = instance.Random; - draw.DrawModes = type.DrawModes; + draw.DrawModes = type._drawModes; draw.SetStencilValue(_layer); type.Model->Draw(context.RenderContext, draw); @@ -597,14 +597,22 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Me void Foliage::InitType(const RenderView& view, FoliageType& type) { - const DrawPass drawModes = type.DrawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode); + const DrawPass drawModes = type._drawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode); type._canDraw = type.IsReady() && drawModes != DrawPass::None && type.Model && type.Model->CanBeRendered(); + bool drawModesDirty = false; for (int32 j = 0; j < type.Entries.Count(); j++) { auto& e = type.Entries[j]; e.ReceiveDecals = type.ReceiveDecals != 0; e.ShadowsMode = type.ShadowsMode; + if (type._drawModesDirty) + { + type._drawModesDirty = 0; + drawModesDirty = true; + } } + if (drawModesDirty) + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::DrawModes); } int32 Foliage::GetInstancesCount() const @@ -1250,7 +1258,7 @@ void Foliage::Draw(RenderContext& renderContext) draw.Deformation = nullptr; draw.Bounds = instance.Bounds; draw.PerInstanceRandom = instance.Random; - draw.DrawModes = type.DrawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode); + draw.DrawModes = type._drawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode); draw.SetStencilValue(_layer); type.Model->Draw(renderContext, draw); return; diff --git a/Source/Engine/Foliage/FoliageType.cpp b/Source/Engine/Foliage/FoliageType.cpp index 8b8c84420..e9eb63b2d 100644 --- a/Source/Engine/Foliage/FoliageType.cpp +++ b/Source/Engine/Foliage/FoliageType.cpp @@ -13,6 +13,7 @@ FoliageType::FoliageType() , Index(-1) { _isReady = 0; + _drawModesDirty = 0; ReceiveDecals = true; UseDensityScaling = false; @@ -32,7 +33,7 @@ FoliageType& FoliageType::operator=(const FoliageType& other) CullDistance = other.CullDistance; CullDistanceRandomRange = other.CullDistanceRandomRange; ScaleInLightmap = other.ScaleInLightmap; - DrawModes = other.DrawModes; + SetDrawModes(other._drawModes); ShadowsMode = other.ShadowsMode; PaintDensity = other.PaintDensity; PaintRadius = other.PaintRadius; @@ -69,6 +70,19 @@ void FoliageType::SetMaterials(const Array& value) Entries[i].Material = value[i]; } +DrawPass FoliageType::GetDrawModes() const +{ + return _drawModes; +} + +void FoliageType::SetDrawModes(DrawPass value) +{ + if (_drawModes == value) + return; + _drawModes = value; + _drawModesDirty = 1; +} + Float3 FoliageType::GetRandomScale() const { Float3 result; @@ -150,7 +164,7 @@ void FoliageType::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE(CullDistance); SERIALIZE(CullDistanceRandomRange); SERIALIZE(ScaleInLightmap); - SERIALIZE(DrawModes); + SERIALIZE_MEMBER(DrawModes, _drawModes); SERIALIZE(ShadowsMode); SERIALIZE_BIT(ReceiveDecals); SERIALIZE_BIT(UseDensityScaling); @@ -191,7 +205,7 @@ void FoliageType::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE(CullDistance); DESERIALIZE(CullDistanceRandomRange); DESERIALIZE(ScaleInLightmap); - DESERIALIZE(DrawModes); + DESERIALIZE_MEMBER(DrawModes, _drawModes); DESERIALIZE(ShadowsMode); DESERIALIZE_BIT(ReceiveDecals); DESERIALIZE_BIT(UseDensityScaling); diff --git a/Source/Engine/Foliage/FoliageType.h b/Source/Engine/Foliage/FoliageType.h index 224ed0bd8..8b36f618b 100644 --- a/Source/Engine/Foliage/FoliageType.h +++ b/Source/Engine/Foliage/FoliageType.h @@ -48,6 +48,8 @@ API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FoliageType : public ScriptingOb private: uint8 _isReady : 1; uint8 _canDraw : 1; + uint8 _drawModesDirty : 1; + DrawPass _drawModes = DrawPass::Depth | DrawPass::GBuffer | DrawPass::Forward; public: /// @@ -124,9 +126,15 @@ public: API_FIELD() float ScaleInLightmap = 1.0f; /// - /// The draw passes to use for rendering this foliage type. + /// Gets the draw passes to use for rendering this foliage type. /// - API_FIELD() DrawPass DrawModes = DrawPass::Depth | DrawPass::GBuffer | DrawPass::Forward; + API_PROPERTY(Attributes="DefaultValue(DrawPass.Depth | DrawPass.GBuffer | DrawPass.Forward)") + DrawPass GetDrawModes() const; + + /// + /// Sets the draw passes to use for rendering this foliage type. + /// + API_PROPERTY() void SetDrawModes(DrawPass value); /// /// The shadows casting mode. @@ -184,7 +192,7 @@ public: API_FIELD() float PlacementRandomRollAngle = 0.0f; /// - /// The density scaling scale applied to the global scale for the foliage instances of this type. Can be used to boost or reduce density scaling effect on this foliage type. Default is 1. + /// The density scale factor applied to the global scale for the foliage instances of this type. Can be used to boost or reduce density scaling effect on this foliage type. Default is 1. Lower to reduce density scaling effect when downscaling foliage via global quality/scalability. /// API_FIELD() float DensityScalingScale = 1.0f; diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index f41e4a805..19e4e3f34 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -66,6 +66,20 @@ void StaticModel::SetBoundsScale(float value) UpdateBounds(); } +DrawPass StaticModel::GetDrawModes() const +{ + return _drawModes; +} + +void StaticModel::SetDrawModes(DrawPass value) +{ + if (_drawModes == value) + return; + _drawModes = value; + if (_sceneRenderingKey != -1) + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::DrawModes); +} + int32 StaticModel::GetLODBias() const { return _lodBias; @@ -330,13 +344,13 @@ void StaticModel::Draw(RenderContext& renderContext) return; if (renderContext.View.Pass == DrawPass::GlobalSDF) { - if (EnumHasAnyFlags(DrawModes, DrawPass::GlobalSDF) && Model->SDF.Texture) + if (EnumHasAnyFlags(_drawModes, DrawPass::GlobalSDF) && Model->SDF.Texture) GlobalSignDistanceFieldPass::Instance()->RasterizeModelSDF(this, Model->SDF, _transform, _box); return; } if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) { - if (EnumHasAnyFlags(DrawModes, DrawPass::GlobalSurfaceAtlas) && Model->SDF.Texture) + if (EnumHasAnyFlags(_drawModes, DrawPass::GlobalSurfaceAtlas) && Model->SDF.Texture) GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, this, _sphere, _transform, Model->LODs.Last().GetBox()); return; } @@ -353,7 +367,7 @@ void StaticModel::Draw(RenderContext& renderContext) draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex) : nullptr; draw.LightmapUVs = &Lightmap.UVsArea; draw.Flags = _staticFlags; - draw.DrawModes = DrawModes; + draw.DrawModes = _drawModes; draw.Bounds = _sphere; draw.Bounds.Center -= renderContext.View.Origin; draw.PerInstanceRandom = GetPerInstanceRandom(); @@ -390,7 +404,7 @@ void StaticModel::Draw(RenderContextBatch& renderContextBatch) draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex) : nullptr; draw.LightmapUVs = &Lightmap.UVsArea; draw.Flags = _staticFlags; - draw.DrawModes = DrawModes; + draw.DrawModes = _drawModes; draw.Bounds = _sphere; draw.Bounds.Center -= renderContext.View.Origin; draw.PerInstanceRandom = GetPerInstanceRandom(); @@ -435,7 +449,7 @@ void StaticModel::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(LODBias, _lodBias); SERIALIZE_MEMBER(ForcedLOD, _forcedLod); SERIALIZE_MEMBER(SortOrder, _sortOrder); - SERIALIZE(DrawModes); + SERIALIZE_MEMBER(DrawModes, _drawModes); if (HasLightmap() #if USE_EDITOR @@ -487,7 +501,7 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE_MEMBER(LODBias, _lodBias); DESERIALIZE_MEMBER(ForcedLOD, _forcedLod); DESERIALIZE_MEMBER(SortOrder, _sortOrder); - DESERIALIZE(DrawModes); + DESERIALIZE_MEMBER(DrawModes, _drawModes); DESERIALIZE_MEMBER(LightmapIndex, Lightmap.TextureIndex); DESERIALIZE_MEMBER(LightmapArea, Lightmap.UVsArea); @@ -537,27 +551,27 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod if (member != stream.MemberEnd() && member->value.IsBool() && member->value.GetBool()) { MARK_CONTENT_DEPRECATED(); - DrawModes = DrawPass::Depth; + _drawModes = DrawPass::Depth; } } // [Deprecated on 07.02.2022, expires on 07.02.2024] if (modifier->EngineBuild <= 6330) { MARK_CONTENT_DEPRECATED(); - DrawModes |= DrawPass::GlobalSDF; + _drawModes |= DrawPass::GlobalSDF; } // [Deprecated on 27.04.2022, expires on 27.04.2024] if (modifier->EngineBuild <= 6331) { MARK_CONTENT_DEPRECATED(); - DrawModes |= DrawPass::GlobalSurfaceAtlas; + _drawModes |= DrawPass::GlobalSurfaceAtlas; } { const auto member = stream.FindMember("RenderPasses"); if (member != stream.MemberEnd() && member->value.IsInt()) { - DrawModes = (DrawPass)member->value.GetInt(); + _drawModes = (DrawPass)member->value.GetInt(); } } } diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index e6ed701cc..4b575a0ed 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -23,6 +23,7 @@ private: bool _vertexColorsDirty; byte _vertexColorsCount; int8 _sortOrder; + DrawPass _drawModes = DrawPass::Default; Array _vertexColorsData[MODEL_MAX_LODS]; GPUBuffer* _vertexColorsBuffer[MODEL_MAX_LODS]; Model* _residencyChangedModel = nullptr; @@ -40,12 +41,6 @@ public: API_FIELD(Attributes="EditorOrder(20), DefaultValue(null), EditorDisplay(\"Model\")") AssetReference Model; - /// - /// The draw passes to use for rendering this object. - /// - API_FIELD(Attributes="EditorOrder(15), DefaultValue(DrawPass.Default), EditorDisplay(\"Model\")") - DrawPass DrawModes = DrawPass::Default; - /// /// The baked lightmap entry. /// @@ -74,6 +69,17 @@ public: /// API_PROPERTY() void SetBoundsScale(float value); + /// + /// Gets the draw passes to use for rendering this object. + /// + API_PROPERTY(Attributes="EditorOrder(15), DefaultValue(DrawPass.Default), EditorDisplay(\"Model\")") + DrawPass GetDrawModes() const; + + /// + /// Sets the draw passes to use for rendering this object. + /// + API_PROPERTY() void SetDrawModes(DrawPass value); + /// /// Gets the model Level Of Detail bias value. Allows to increase or decrease rendered model quality. /// diff --git a/Source/Engine/Level/Scene/SceneRendering.h b/Source/Engine/Level/Scene/SceneRendering.h index 25dfc63fa..d522454a6 100644 --- a/Source/Engine/Level/Scene/SceneRendering.h +++ b/Source/Engine/Level/Scene/SceneRendering.h @@ -56,6 +56,7 @@ public: Layer = 4, StaticFlags = 8, AutoDelayDuringRendering = 16, // Conditionally allow updating data during rendering when writes are locked + DrawModes = 32, Auto = Visual | Bounds | Layer, }; diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 010e14bf9..ae6e8a9a8 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -408,7 +408,8 @@ public: if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) { ScopeWriteLock lock(Locker); - OnSceneRenderingDirty(BoundingBox::FromSphere(prevBounds)); + if (flags != DrawModes && flags != Layer && flags != StaticFlags) + OnSceneRenderingDirty(BoundingBox::FromSphere(prevBounds)); OnSceneRenderingDirty(a->GetBox()); } } From 22c88eb59d818e1bf8404a012ce65cc9310bfc42 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Mar 2026 19:25:18 +0100 Subject: [PATCH 5/5] Fix blocky terrain SDF #3975 --- Content/Shaders/GlobalSignDistanceField.flax | 4 ++-- Source/Engine/Terrain/Terrain.cpp | 11 +++++++---- Source/Shaders/GlobalSignDistanceField.shader | 10 +++++++--- Source/Shaders/TerrainCommon.hlsl | 4 ++-- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax index 5afcb4bf4..01dae76b1 100644 --- a/Content/Shaders/GlobalSignDistanceField.flax +++ b/Content/Shaders/GlobalSignDistanceField.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f07ebb16820897e8598ae7a0627cb75b3d28e9dceea3ad4bd9ff543d5cdd01c -size 13979 +oid sha256:0506a5485a0107f67714ceb8c1714f18cb5718bacfde36fad91d53ea3cb60de9 +size 14044 diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 77b4ee8a6..f0ad65894 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -549,19 +549,22 @@ bool Terrain::DrawSetup(RenderContext& renderContext) const DrawPass drawModes = DrawModes & renderContext.View.Pass; if (drawModes == DrawPass::GlobalSDF) { - const float chunkSize = TERRAIN_UNITS_PER_VERTEX * (float)_chunkSize; - const float posToUV = 0.25f / chunkSize; - Float4 localToUV(posToUV, posToUV, 0.0f, 0.0f); + const float chunkScale = 0.25f / (TERRAIN_UNITS_PER_VERTEX * (float)_chunkSize); // Patch heightfield is divided into 4x4 chunks for (const TerrainPatch* patch : _patches) { if (!patch->Heightmap) continue; + GPUTexture* heightfield = patch->Heightmap->GetTexture(); + float size = (float)heightfield->Width(); + Float4 localToUV; + localToUV.X = localToUV.Y = chunkScale * (size - 1) / size; // Skip the last edge texel + localToUV.Z = localToUV.W = 0.5f / size; // Include half-texel offset 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); + GlobalSignDistanceFieldPass::Instance()->RasterizeHeightfield(this, heightfield, patchTransform, patch->_bounds, localToUV); } return true; } diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader index fe4bafda5..e8bb1b1f7 100644 --- a/Source/Shaders/GlobalSignDistanceField.shader +++ b/Source/Shaders/GlobalSignDistanceField.shader @@ -170,15 +170,14 @@ void CS_RasterizeHeightfield(uint3 DispatchThreadId : SV_DispatchThreadID) // Convert voxel world-space position into heightfield local-space position and get heightfield UV float4x4 worldToLocal = ToMatrix4x4(objectData.WorldToVolume); float3 volumePos = mul(float4(voxelWorldPos, 1), worldToLocal).xyz; - float3 volumeUV = volumePos * objectData.VolumeToUVWMul + objectData.VolumeToUVWAdd; - float2 heightfieldUV = float2(volumeUV.x, volumeUV.z); // Sample heightfield around the voxel location (heightmap uses point sampler) Texture2D heightmap = ObjectsTextures[i]; float4 localToUV = float4(objectData.VolumeToUVWMul.xz, objectData.VolumeToUVWAdd.xz); +#if 1 float3 n00, n10, n01, n11; bool h00, h10, h01, h11; - float offset = CascadeVoxelSize * 2; + float offset = CascadeVoxelSize; float3 p00 = SampleHeightmap(heightmap, volumePos + float3(-offset, 0, 0), localToUV, n00, h00, objectData.MipOffset); float3 p10 = SampleHeightmap(heightmap, volumePos + float3(+offset, 0, 0), localToUV, n10, h10, objectData.MipOffset); float3 p01 = SampleHeightmap(heightmap, volumePos + float3(0, 0, -offset), localToUV, n01, h01, objectData.MipOffset); @@ -189,6 +188,11 @@ void CS_RasterizeHeightfield(uint3 DispatchThreadId : SV_DispatchThreadID) float3 heightfieldNormal = (n00 + n10 + n01 + n11) * 0.25f; heightfieldNormal = normalize(heightfieldNormal); bool isHole = h00 || h10 || h01 || h11; +#else + float3 heightfieldNormal; + bool isHole; + float3 heightfieldPosition = SampleHeightmap(heightmap, volumePos, localToUV, heightfieldNormal, isHole, objectData.MipOffset); +#endif // Skip holes and pixels outside the heightfield if (isHole) diff --git a/Source/Shaders/TerrainCommon.hlsl b/Source/Shaders/TerrainCommon.hlsl index 0c2f57168..f883e8033 100644 --- a/Source/Shaders/TerrainCommon.hlsl +++ b/Source/Shaders/TerrainCommon.hlsl @@ -35,11 +35,11 @@ float3 SampleHeightmap(Texture2D heightmap, float3 localPosition, float4 { // Sample heightmap float2 uv = localPosition.xz * localToUV.xy + localToUV.zw; - float4 value = heightmap.SampleLevel(SamplerPointClamp, uv, mipOffset); + float4 value = heightmap.SampleLevel(SamplerLinearClamp, uv, mipOffset); // Decode heightmap normal = DecodeHeightmapNormal(value, isHole); - float height = DecodeHeightmapHeight(value);; + float height = DecodeHeightmapHeight(value); float3 position = float3(localPosition.x, height, localPosition.z); // UVs outside the heightmap are empty