From c7468ffe54294d697e032d2278efaf0bce1f9d84 Mon Sep 17 00:00:00 2001 From: luchu1993 Date: Sat, 20 Jun 2026 00:25:56 +0800 Subject: [PATCH 1/4] Fix Render2D line thickness scaling Use transformed 2D basis vector lengths instead of diagonal matrix entries when scaling line thickness. This keeps Render2D.DrawLine and rectangle outlines stable under rotation while preserving scale transforms. Fixes #4140. --- Source/Engine/Render2D/Render2D.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 70127bf94..58f6f208e 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -247,6 +247,13 @@ FORCE_INLINE void ApplyTransform(const Float2& value, Float2& result) Matrix3x3::Transform2DPoint(value, TransformCached, result); } +FORCE_INLINE float GetTransformScale() +{ + const Float2 axisX(TransformCached.M11, TransformCached.M12); + const Float2 axisY(TransformCached.M21, TransformCached.M22); + return (axisX.Length() + axisY.Length()) * 0.5f; +} + void ApplyTransform(const Rectangle& value, RotatedRectangle& result) { const RotatedRectangle rotated(value); @@ -1464,7 +1471,7 @@ void Render2D::DrawRectangle(const Rectangle& rect, const Color& color1, const C const auto& mask = ClipLayersStack.Peek().Mask; float thick = thickness; - thickness *= (TransformCached.M11 + TransformCached.M22 + TransformCached.M33) * 0.3333333f; + thickness *= GetTransformScale(); // When lines thickness is very large, don't use corner caps and place line ends to not overlap if (thickness > 4.0f) @@ -1840,7 +1847,7 @@ void DrawLines(const Float2* points, int32 pointsCount, const Color& color1, con ASSERT(points && pointsCount >= 2); const auto& mask = ClipLayersStack.Peek().Mask; - thickness *= (TransformCached.M11 + TransformCached.M22 + TransformCached.M33) * 0.3333333f; + thickness *= GetTransformScale(); Render2DDrawCall& drawCall = DrawCalls.AddOne(); drawCall.StartIB = IBIndex; From 68e3e459f01088a003b326f483313935893713e5 Mon Sep 17 00:00:00 2001 From: luchu1993 Date: Sat, 20 Jun 2026 10:12:31 +0800 Subject: [PATCH 2/4] Fix ProgressBar fill with large margins Apply BarMargin before calculating the filled progress area so low values cannot shrink the bar rectangle into a negative size. This keeps ProgressBar fills clamped within the inner drawable area when large margins are used.\n\nFixes #4076. --- Source/Engine/UI/GUI/Common/ProgressBar.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/ProgressBar.cs b/Source/Engine/UI/GUI/Common/ProgressBar.cs index d569c3f4c..322ce4880 100644 --- a/Source/Engine/UI/GUI/Common/ProgressBar.cs +++ b/Source/Engine/UI/GUI/Common/ProgressBar.cs @@ -228,19 +228,27 @@ namespace FlaxEngine.GUI float progressNormalized = Mathf.InverseLerp(_minimum, _maximum, _current); if (progressNormalized > 0.001f) { - Rectangle barRect = new Rectangle(0, 0, Width * progressNormalized, Height); + Rectangle rect = new Rectangle(0, 0, Width, Height); + BarMargin.ShrinkRectangle(ref rect); + if (rect.Width <= 0.0f || rect.Height <= 0.0f) + return; + + Rectangle barRect = rect; switch (Origin) { case BarOrigin.HorizontalLeft: + barRect.Width *= progressNormalized; break; case BarOrigin.HorizontalRight: - barRect = new Rectangle(Width - Width * progressNormalized, 0, Width * progressNormalized, Height); + barRect.Width *= progressNormalized; + barRect.X = rect.Right - barRect.Width; break; case BarOrigin.VerticalTop: - barRect = new Rectangle(0, 0, Width, Height * progressNormalized); + barRect.Height *= progressNormalized; break; case BarOrigin.VerticalBottom: - barRect = new Rectangle(0, Height - Height * progressNormalized, Width, Height * progressNormalized); + barRect.Height *= progressNormalized; + barRect.Y = rect.Bottom - barRect.Height; break; default: break; } @@ -248,15 +256,12 @@ namespace FlaxEngine.GUI switch (Method) { case BarMethod.Stretch: - BarMargin.ShrinkRectangle(ref barRect); if (BarBrush != null) BarBrush.Draw(barRect, BarColor); else Render2D.FillRectangle(barRect, BarColor); break; case BarMethod.Clip: - var rect = new Rectangle(0, 0, Width, Height); - BarMargin.ShrinkRectangle(ref rect); Render2D.PushClip(ref barRect); if (BarBrush != null) BarBrush.Draw(rect, BarColor); From 9943959e76b43632736fcde3a873cd8683299688 Mon Sep 17 00:00:00 2001 From: luchu1993 Date: Sat, 20 Jun 2026 10:22:51 +0800 Subject: [PATCH 3/4] Fix model material access with empty entries Allow model instance material APIs to fall back to asset material slots when instance entries have not been initialized yet. Lazily initialize entries before writing material overrides so imported prefab StaticModel instances can call GetMaterial and SetMaterial without assertion failures.\n\nFixes #3801. --- Source/Engine/Level/Actors/AnimatedModel.cpp | 4 ++-- Source/Engine/Level/Actors/ModelInstanceActor.cpp | 7 +++++++ Source/Engine/Level/Actors/SplineModel.cpp | 4 ++-- Source/Engine/Level/Actors/StaticModel.cpp | 8 ++++---- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index ba3a2025d..2f878f3d6 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -1317,8 +1317,8 @@ MaterialBase* AnimatedModel::GetMaterial(int32 entryIndex) SkinnedModel->WaitForLoaded(); else return nullptr; - CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr); - MaterialBase* material = Entries[entryIndex].Material.Get(); + CHECK_RETURN(entryIndex >= 0 && entryIndex < SkinnedModel->MaterialSlots.Count(), nullptr); + MaterialBase* material = entryIndex < Entries.Count() ? Entries[entryIndex].Material.Get() : nullptr; if (!material) { material = SkinnedModel->MaterialSlots[entryIndex].Material.Get(); diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.cpp b/Source/Engine/Level/Actors/ModelInstanceActor.cpp index b8dc3af1a..985e59a71 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.cpp +++ b/Source/Engine/Level/Actors/ModelInstanceActor.cpp @@ -39,6 +39,9 @@ void ModelInstanceActor::SetMaterial(int32 entryIndex, MaterialBase* material) WaitForModelLoad(); if (Entries.Count() == 0 && !material) return; + const int32 slotsCount = GetMaterialSlots().Length(); + if (Entries.Count() != slotsCount) + Entries.Setup(slotsCount); CHECK(entryIndex >= 0 && entryIndex < Entries.Count()); if (Entries[entryIndex].Material == material) return; @@ -50,6 +53,10 @@ void ModelInstanceActor::SetMaterial(int32 entryIndex, MaterialBase* material) MaterialInstance* ModelInstanceActor::CreateAndSetVirtualMaterialInstance(int32 entryIndex) { WaitForModelLoad(); + const int32 slotsCount = GetMaterialSlots().Length(); + CHECK_RETURN(entryIndex >= 0 && entryIndex < slotsCount, nullptr); + if (Entries.Count() != slotsCount) + Entries.Setup(slotsCount); MaterialBase* material = GetMaterial(entryIndex); CHECK_RETURN(material && !material->WaitForLoaded(), nullptr); MaterialInstance* result = material->CreateVirtualInstance(); diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index fdda6941e..5352bf56d 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -356,8 +356,8 @@ MaterialBase* SplineModel::GetMaterial(int32 entryIndex) Model->WaitForLoaded(); else return nullptr; - CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr); - MaterialBase* material = Entries[entryIndex].Material.Get(); + CHECK_RETURN(entryIndex >= 0 && entryIndex < Model->MaterialSlots.Count(), nullptr); + MaterialBase* material = entryIndex < Entries.Count() ? Entries[entryIndex].Material.Get() : nullptr; if (!material) { material = Model->MaterialSlots[entryIndex].Material.Get(); diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 82976b54c..4ffe1ea6f 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -128,7 +128,7 @@ MaterialBase* StaticModel::GetMaterial(int32 meshIndex, int32 lodIndex) const Math::IsInRange(meshIndex, 0, model->LODs[lodIndex].Meshes.Count())); const auto& mesh = model->LODs[lodIndex].Meshes[meshIndex]; const auto materialSlotIndex = mesh.GetMaterialSlotIndex(); - MaterialBase* material = Entries[materialSlotIndex].Material.Get(); + MaterialBase* material = materialSlotIndex < Entries.Count() ? Entries[materialSlotIndex].Material.Get() : nullptr; return material ? material : model->MaterialSlots[materialSlotIndex].Material.Get(); } @@ -586,9 +586,9 @@ MaterialBase* StaticModel::GetMaterial(int32 entryIndex) { if (!Model || Model->WaitForLoaded()) return nullptr; - CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr); - MaterialBase* material = Entries[entryIndex].Material.Get(); - if (!material && entryIndex < Model->MaterialSlots.Count()) + CHECK_RETURN(entryIndex >= 0 && entryIndex < Model->MaterialSlots.Count(), nullptr); + MaterialBase* material = entryIndex < Entries.Count() ? Entries[entryIndex].Material.Get() : nullptr; + if (!material) { material = Model->MaterialSlots[entryIndex].Material.Get(); if (!material) From 5636c9d85b2514bf96fd200d10371c07e98ae4bc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 23 Jun 2026 08:42:55 +0200 Subject: [PATCH 4/4] Add GPU Texture binding as Gameplay Global texture for runtime-textures --- Source/Engine/Graphics/Materials/MaterialParams.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 711fcdf2b..5e42f004b 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -478,6 +478,13 @@ void MaterialParameter::Bind(BindMeta& meta) const meta.Context->BindSR(_registerIndex, texture ? texture->GetTexture() : nullptr); break; } + case VariantType::Object: + { + // GPU Texture bind must match dimensions of the value archetype (eg. 2d or cube texture) + auto gpuTexture = Cast(e->Value.AsObject); + meta.Context->BindSR(_registerIndex, gpuTexture); + break; + } default: #if !BUILD_RELEASE LOG(Warning, "Invalid Gameplay Global '{}' ({}) value type '{}' to bind to material", _name, _asAsset->GetPath(), e->Value.Type.ToString());