diff --git a/Content/Editor/Particles/Constant Burst.flax b/Content/Editor/Particles/Constant Burst.flax
index bb90199fe..59c68065c 100644
--- a/Content/Editor/Particles/Constant Burst.flax
+++ b/Content/Editor/Particles/Constant Burst.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:fc1e46708152005dc92532e940b3ce19ec527b595fb18365b41ebdcd338ad2c4
-size 2705
+oid sha256:5876bad52753b665a608a15d060087ce2f963bed8cb792bb2b0fdbf256f461be
+size 2825
diff --git a/Content/Editor/Particles/Particle Material Preview.flax b/Content/Editor/Particles/Particle Material Preview.flax
index 1f18aaeaa..409664367 100644
--- a/Content/Editor/Particles/Particle Material Preview.flax
+++ b/Content/Editor/Particles/Particle Material Preview.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:88de448e023b35296531fff817d4b95200bac97fbd5c927ee6a5e5a815323978
-size 2110
+oid sha256:4fe07c97b27512fc284a18952fe52031be6077f52cea0f584d44b5fd1cfaef5b
+size 1970
diff --git a/Content/Editor/Particles/Periodic Burst.flax b/Content/Editor/Particles/Periodic Burst.flax
index 784d456c5..ce0444d3e 100644
--- a/Content/Editor/Particles/Periodic Burst.flax
+++ b/Content/Editor/Particles/Periodic Burst.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:94bf88983e3cf9fe555141a867af3c20e5bd58d13a527a9b4e02d4d1be157be9
-size 3664
+oid sha256:9d83eac50915cf1995559ea152c47136a08c174a551074a73628e2dbe1994214
+size 3797
diff --git a/Content/Editor/Particles/Ribbon Spiral.flax b/Content/Editor/Particles/Ribbon Spiral.flax
index 4e06db822..c48c7c768 100644
--- a/Content/Editor/Particles/Ribbon Spiral.flax
+++ b/Content/Editor/Particles/Ribbon Spiral.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f9016e074d23649b91b9eb2438c4f48d0dbd5bed0c7d29b58fdaea5a8530e905
-size 2365
+oid sha256:c673d1a044b7e0ad70b9bfbad171b2ceb0da2451100d3e3672eb48232f6f230a
+size 2485
diff --git a/Content/Editor/Particles/Smoke.flax b/Content/Editor/Particles/Smoke.flax
index 400f89257..1549cd14d 100644
--- a/Content/Editor/Particles/Smoke.flax
+++ b/Content/Editor/Particles/Smoke.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:661f4dfadadedb9b5830db059949938d63e3b4db727496c86a04aa008bba12e6
-size 14660
+oid sha256:d0e663c8424630e82c1d42a62ff5daf0807237a50a9f376e678b2b9db08b999a
+size 4616
diff --git a/Content/Editor/Particles/Sparks.flax b/Content/Editor/Particles/Sparks.flax
index f32b22c84..4c5da4e81 100644
--- a/Content/Editor/Particles/Sparks.flax
+++ b/Content/Editor/Particles/Sparks.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:93a9342516ef1a13cfadaa5b9e8ff9b9328f13569b3fff7ec51bb9e103ed623c
-size 13698
+oid sha256:f0c29ed8547987e54cc7cbce9e36f6386db9e8443f24527deb5e68219685c5d4
+size 13816
diff --git a/Content/Shaders/Fog.flax b/Content/Shaders/Fog.flax
index 10e6f56c5..2cf1eb131 100644
--- a/Content/Shaders/Fog.flax
+++ b/Content/Shaders/Fog.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b1571139183579891b51fde139e0ac9767251a8265a1c5b98f50475c229f3378
-size 2196
+oid sha256:5471e1f7c6352b3e514a6c1de72751b8eee88ed8c8ec6f99f35391bd570261e8
+size 2193
diff --git a/Content/Shaders/PostProcessing.flax b/Content/Shaders/PostProcessing.flax
index bec24f8c0..37d834717 100644
--- a/Content/Shaders/PostProcessing.flax
+++ b/Content/Shaders/PostProcessing.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:17fc5977b0e82aea17310dad1a93745fd923ebc920b74ee29a55afb909275e56
-size 23340
+oid sha256:5c9a661a83ce1120c203347ac149fc7626bcef66386c797f4c89bc46cf42bb39
+size 23321
diff --git a/Content/Shaders/Quad.flax b/Content/Shaders/Quad.flax
index d4906d999..f73292191 100644
--- a/Content/Shaders/Quad.flax
+++ b/Content/Shaders/Quad.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8f4cfb3320b1f07f864aa8a7759883b4b0a2f31f97b40de3856ba73561d1868c
-size 4060
+oid sha256:e7be75cc2c4dc8b815ca3dca0828b7181d3fb7874cddac97e1ca7a563832b018
+size 4059
diff --git a/Content/Shaders/SSAO.flax b/Content/Shaders/SSAO.flax
index 6da2efd41..cfe92b71d 100644
--- a/Content/Shaders/SSAO.flax
+++ b/Content/Shaders/SSAO.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:bdeb8672975efe6858402b7a19a90851328406abf073059f9000e9a9eb2a8c38
-size 36980
+oid sha256:73d13eacaf02ba27354b817cc639bde59e2afdc3a8d848de6d17981b5bb48cbe
+size 36979
diff --git a/Source/Editor/Content/Proxy/CubeTextureProxy.cs b/Source/Editor/Content/Proxy/CubeTextureProxy.cs
index 137376673..efccfdbe5 100644
--- a/Source/Editor/Content/Proxy/CubeTextureProxy.cs
+++ b/Source/Editor/Content/Proxy/CubeTextureProxy.cs
@@ -47,8 +47,6 @@ namespace FlaxEditor.Content
_preview = new CubeTexturePreview(false);
InitAssetPreview(_preview);
}
-
- // TODO: disable streaming for asset during thumbnail rendering (and restore it after)
}
///
diff --git a/Source/Editor/Content/Proxy/IESProfileProxy.cs b/Source/Editor/Content/Proxy/IESProfileProxy.cs
index 4a9320a9f..6b6918567 100644
--- a/Source/Editor/Content/Proxy/IESProfileProxy.cs
+++ b/Source/Editor/Content/Proxy/IESProfileProxy.cs
@@ -50,16 +50,12 @@ namespace FlaxEditor.Content
Offsets = Margin.Zero,
};
}
-
- // TODO: disable streaming for asset during thumbnail rendering (and restore it after)
}
///
public override bool CanDrawThumbnail(ThumbnailRequest request)
{
- // Check if asset is streamed enough
- var asset = (IESProfile)request.Asset;
- return asset.ResidentMipLevels >= Mathf.Max(1, (int)(asset.MipLevels * ThumbnailsModule.MinimumRequiredResourcesQuality));
+ return ThumbnailsModule.HasMinimumQuality((IESProfile)request.Asset);
}
///
diff --git a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs
index 672b02701..fc4fcdbc1 100644
--- a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs
+++ b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs
@@ -55,8 +55,6 @@ namespace FlaxEditor.Content
_preview = new MaterialPreview(false);
InitAssetPreview(_preview);
}
-
- // TODO: disable streaming for dependant assets during thumbnail rendering (and restore it after)
}
///
diff --git a/Source/Editor/Content/Proxy/MaterialProxy.cs b/Source/Editor/Content/Proxy/MaterialProxy.cs
index db0f9c810..4769ca548 100644
--- a/Source/Editor/Content/Proxy/MaterialProxy.cs
+++ b/Source/Editor/Content/Proxy/MaterialProxy.cs
@@ -99,8 +99,6 @@ namespace FlaxEditor.Content
_preview = new MaterialPreview(false);
InitAssetPreview(_preview);
}
-
- // TODO: disable streaming for dependant assets during thumbnail rendering (and restore it after)
}
///
diff --git a/Source/Editor/Content/Proxy/ModelProxy.cs b/Source/Editor/Content/Proxy/ModelProxy.cs
index 6dd9ea193..a57ddbf61 100644
--- a/Source/Editor/Content/Proxy/ModelProxy.cs
+++ b/Source/Editor/Content/Proxy/ModelProxy.cs
@@ -78,8 +78,6 @@ namespace FlaxEditor.Content
};
InitAssetPreview(_preview);
}
-
- // TODO: disable streaming for asset during thumbnail rendering (and restore it after)
}
///
@@ -97,6 +95,7 @@ namespace FlaxEditor.Content
var bounds = _preview.Model.GetBox();
var maxSize = Math.Max(0.001f, (float)bounds.Size.MaxValue);
_preview.ViewportCamera.SetArcBallView(bounds);
+ _preview.NearPlane = Mathf.Min(10.0f, maxSize * 0.5f);
_preview.FarPlane = Mathf.Max(1000.0f, maxSize * 2 + 100.0f);
_preview.Task.OnDraw();
diff --git a/Source/Editor/Content/Proxy/PrefabProxy.cs b/Source/Editor/Content/Proxy/PrefabProxy.cs
index 191193c66..410df23b0 100644
--- a/Source/Editor/Content/Proxy/PrefabProxy.cs
+++ b/Source/Editor/Content/Proxy/PrefabProxy.cs
@@ -113,8 +113,6 @@ namespace FlaxEditor.Content
_preview = new PrefabPreview(false);
InitAssetPreview(_preview);
}
-
- // TODO: disable streaming for asset during thumbnail rendering (and restore it after)
}
///
@@ -124,20 +122,37 @@ namespace FlaxEditor.Content
return false;
// Check if asset is streamed enough
- var asset = (Prefab)request.Asset;
- return asset.IsLoaded;
+ return ThumbnailsModule.HasMinimumQuality((Prefab)request.Asset);
}
- private void Prepare(Actor actor)
+ private void Prepare(Actor actor, ref BoundingBox bounds)
{
+ if (!actor.IsActive)
+ return;
if (actor is TextRender textRender)
{
textRender.UpdateLayout();
}
+ // Use bounds from visual-only elements
+ var actorBounds = BoundingBox.Empty;
+ if (actor is ModelInstanceActor ||
+ actor is SpriteRender ||
+ actor is TextRender)
+ {
+ actorBounds = actor.EditorBox;
+ }
+ if (actorBounds != BoundingBox.Empty)
+ {
+ if (bounds == BoundingBox.Empty)
+ bounds = actorBounds;
+ else
+ BoundingBox.Merge(ref actorBounds, ref bounds, out bounds);
+ }
+
for (int i = 0; i < actor.ChildrenCount; i++)
{
- Prepare(actor.GetChild(i));
+ Prepare(actor.GetChild(i), ref bounds);
}
}
@@ -165,14 +180,24 @@ namespace FlaxEditor.Content
else
{
// Update some actors data (some actor types update bounds/data later but its required to be done before rendering)
- Prepare(_preview.Instance);
+ var bounds = BoundingBox.Empty;
+ Prepare(_preview.Instance, ref bounds);
+ //bounds = _preview.Instance.EditorBoxChildren;
// Auto fit actor to camera
- float targetSize = 30.0f;
- var bounds = _preview.Instance.EditorBoxChildren;
- var maxSize = Math.Max(0.001f, (float)bounds.Size.MaxValue);
- _preview.Instance.Scale = new Float3(targetSize / maxSize);
- _preview.Instance.Position = Vector3.Zero;
+ if (bounds != BoundingBox.Empty)
+ {
+ float targetSize = 38.0f;
+ float maxSize = Math.Max(0.001f, (float)bounds.Size.MaxValue);
+ float scale = targetSize / maxSize;
+ _preview.Instance.Scale = new Float3(scale);
+ _preview.Instance.Position = -bounds.Center * scale;
+ }
+ else
+ {
+ _preview.Instance.Scale = Float3.One;
+ _preview.Instance.Position = Vector3.Zero;
+ }
}
_preview.Task.OnDraw();
diff --git a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
index f9d043a21..95f2f7510 100644
--- a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
+++ b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
@@ -101,8 +101,6 @@ namespace FlaxEditor.Content
_preview = new AnimatedModelPreview(false);
InitAssetPreview(_preview);
}
-
- // TODO: disable streaming for asset during thumbnail rendering (and restore it after)
}
///
diff --git a/Source/Editor/Content/Proxy/SpriteAtlasProxy.cs b/Source/Editor/Content/Proxy/SpriteAtlasProxy.cs
index 2217be358..03a3ef147 100644
--- a/Source/Editor/Content/Proxy/SpriteAtlasProxy.cs
+++ b/Source/Editor/Content/Proxy/SpriteAtlasProxy.cs
@@ -50,16 +50,12 @@ namespace FlaxEditor.Content
Offsets = Margin.Zero,
};
}
-
- // TODO: disable streaming for asset during thumbnail rendering (and restore it after)
}
///
public override bool CanDrawThumbnail(ThumbnailRequest request)
{
- // Check if asset is streamed enough
- var asset = (SpriteAtlas)request.Asset;
- return asset.ResidentMipLevels >= Mathf.Max(1, (int)(asset.MipLevels * ThumbnailsModule.MinimumRequiredResourcesQuality));
+ return ThumbnailsModule.HasMinimumQuality((SpriteAtlas)request.Asset);
}
///
diff --git a/Source/Editor/Content/Proxy/TextureProxy.cs b/Source/Editor/Content/Proxy/TextureProxy.cs
index 361f4c3f6..45deb0b39 100644
--- a/Source/Editor/Content/Proxy/TextureProxy.cs
+++ b/Source/Editor/Content/Proxy/TextureProxy.cs
@@ -50,8 +50,6 @@ namespace FlaxEditor.Content
Offsets = Margin.Zero,
};
}
-
- // TODO: disable streaming for asset during thumbnail rendering (and restore it after)
}
///
diff --git a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs
index 2fca96ba2..dbc970b05 100644
--- a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs
+++ b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs
@@ -122,39 +122,43 @@ namespace FlaxEditor.Content.Thumbnails
internal static bool HasMinimumQuality(TextureBase asset)
{
+ // Don't block thumbnails queue when texture fails to stream in (eg. unsupported format)
if (asset.HasStreamingError)
- return true; // Don't block thumbnails queue when texture fails to stream in (eg. unsupported format)
+ return true;
+
+ // Check if enough mip levels are loaded
var mipLevels = asset.MipLevels;
var minMipLevels = Mathf.Min(mipLevels, 7);
- return asset.IsLoaded && asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * MinimumRequiredResourcesQuality));
+ if (asset.IsLoaded && asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * MinimumRequiredResourcesQuality)))
+ return true;
+
+ // Inform streaming about resource usage to stream it in for the thumbnail
+ asset.SetStreamingVisible();
+
+ return false;
}
- internal static bool HasMinimumQuality(Model asset)
+ internal static bool HasMinimumQuality(ModelBase asset)
{
if (!asset.IsLoaded)
return false;
- var lods = asset.LODs.Length;
+ var lods = asset.LODsCount;
var slots = asset.MaterialSlots;
- foreach (var slot in slots)
- {
- if (slot.Material && !HasMinimumQuality(slot.Material))
- return false;
- }
- return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * MinimumRequiredResourcesQuality));
- }
- internal static bool HasMinimumQuality(SkinnedModel asset)
- {
- var lods = asset.LODs.Length;
- if (asset.IsLoaded && lods == 0)
- return true; // Skeleton-only model
- var slots = asset.MaterialSlots;
+ // Check if all materials are loaded (incl. dependent resources)
foreach (var slot in slots)
{
if (slot.Material && !HasMinimumQuality(slot.Material))
return false;
}
- return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * MinimumRequiredResourcesQuality));
+
+ // Check if enough LODs are loaded
+ if (asset.LoadedLODs >= Mathf.Max(1, (int)(lods * MinimumRequiredResourcesQuality)))
+ return true;
+
+ // TODO: impl SetStreamingVisible for models similar to textures (ModelsStreamingHandler needs to use it)
+
+ return false;
}
internal static bool HasMinimumQuality(MaterialBase asset)
@@ -177,6 +181,14 @@ namespace FlaxEditor.Content.Thumbnails
return baseMaterial == null || HasMinimumQualityInternal(baseMaterial);
}
+ internal static bool HasMinimumQuality(Prefab asset)
+ {
+ if (!asset.IsLoaded)
+ return false;
+ var defaultInstance = asset.GetDefaultInstance();
+ return defaultInstance == null || HasMinimumQualityInternal(defaultInstance);
+ }
+
private static bool HasMinimumQualityInternal(MaterialBase asset)
{
if (!asset.IsLoaded)
@@ -190,6 +202,43 @@ namespace FlaxEditor.Content.Thumbnails
return true;
}
+ private static bool HasMinimumQualityInternal(Actor actor)
+ {
+ if (!actor.IsActive)
+ return true;
+
+ if (actor is ModelInstanceActor modelInstance)
+ {
+ var model = modelInstance.GetModel();
+ if (model && !HasMinimumQuality(model))
+ return false;
+ var slots = modelInstance.MaterialSlots;
+ foreach (var slot in slots)
+ {
+ if (slot.Material && !HasMinimumQuality(slot.Material))
+ return false;
+ }
+ }
+ if (actor is SpriteRender spriteRender)
+ {
+ if (spriteRender.Material && !HasMinimumQuality(spriteRender.Material))
+ return false;
+ }
+ if (actor is TextRender textRender)
+ {
+ if (textRender.Material && !HasMinimumQuality(textRender.Material))
+ return false;
+ }
+
+ for (int i = 0; i < actor.ChildrenCount; i++)
+ {
+ if (!HasMinimumQualityInternal(actor.GetChild(i)))
+ return false;
+ }
+
+ return true;
+ }
+
#region IContentItemOwner
///
diff --git a/Source/Editor/Content/Tree/ContentItemTreeNode.cs b/Source/Editor/Content/Tree/ContentItemTreeNode.cs
index 09d1ec01b..780bff376 100644
--- a/Source/Editor/Content/Tree/ContentItemTreeNode.cs
+++ b/Source/Editor/Content/Tree/ContentItemTreeNode.cs
@@ -128,34 +128,15 @@ public sealed class ContentItemTreeNode : TreeNode, IContentItemOwner
return base.OnMouseDoubleClickHeader(ref location, button);
}
- ///
- public override bool OnKeyDown(KeyboardKeys key)
- {
- if (IsFocused)
- {
- switch (key)
- {
- case KeyboardKeys.Return:
- Editor.Instance.Windows.ContentWin.Open(Item);
- return true;
- case KeyboardKeys.F2:
- Editor.Instance.Windows.ContentWin.Rename(Item);
- return true;
- case KeyboardKeys.Delete:
- Editor.Instance.Windows.ContentWin.Delete(Item);
- return true;
- }
- }
-
- return base.OnKeyDown(key);
- }
-
///
protected override void DoDragDrop()
{
DoDragDrop(DragItems.GetDragData(Item));
}
+ ///
+ protected override bool ShowTooltip => true;
+
///
public override bool OnShowTooltip(out string text, out Float2 location, out Rectangle area)
{
diff --git a/Source/Editor/Content/Tree/TreeViewPanel.cs b/Source/Editor/Content/Tree/TreeViewPanel.cs
new file mode 100644
index 000000000..07af1e2d1
--- /dev/null
+++ b/Source/Editor/Content/Tree/TreeViewPanel.cs
@@ -0,0 +1,210 @@
+using System.Collections.Generic;
+using FlaxEditor.GUI.Tree;
+using FlaxEditor.Options;
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.Content;
+
+///
+/// The content tree view panel.
+///
+public class TreeViewPanel : Panel
+{
+ ///
+ /// The content tree assigned to this panel.
+ ///
+ public Tree ContentTree;
+
+ private InputActionsContainer _inputActions;
+ private bool _isCutting;
+ private List _cutItems = new List();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TreeViewPanel()
+ : base(ScrollBars.None)
+ {
+ // Setup input actions
+ _inputActions = new InputActionsContainer(new[]
+ {
+ new InputActionsContainer.Binding(options => options.Rename, Rename),
+ new InputActionsContainer.Binding(options => options.Delete, Delete),
+ new InputActionsContainer.Binding(options => options.Duplicate, Duplicate),
+ new InputActionsContainer.Binding(options => options.Copy, Copy),
+ new InputActionsContainer.Binding(options => options.Paste, Paste),
+ new InputActionsContainer.Binding(options => options.Cut, Cut),
+ });
+ }
+
+ ///
+ /// Renames the selected item.
+ ///
+ public void Rename()
+ {
+ if (ContentTree == null || !Visible)
+ return;
+
+ var selection = ContentTree.Selection;
+ if (selection.Count > 0)
+ {
+ var node = selection[0];
+ if (node is ContentItemTreeNode contentNode)
+ {
+ Editor.Instance.Windows.ContentWin.Rename(contentNode.Item);
+ }
+ }
+ }
+
+ ///
+ /// Deletes the selected items.
+ ///
+ public void Delete()
+ {
+ if (ContentTree == null || !Visible)
+ return;
+
+ var selection = ContentTree.Selection;
+ if (selection.Count > 0)
+ {
+ var items = new List();
+ foreach (var node in selection)
+ {
+ if (node is ContentItemTreeNode fileNode)
+ {
+ items.Add(fileNode.Item);
+ } else if (node is ContentFolderTreeNode folderNode)
+ {
+ items.Add(folderNode.Folder);
+ }
+ }
+
+ Editor.Instance.Windows.ContentWin.Delete(items);
+ }
+ }
+
+ ///
+ /// Duplicates the selected items.
+ ///
+ public void Duplicate()
+ {
+ if (ContentTree == null || !Visible)
+ return;
+
+ var selection = ContentTree.Selection;
+ if (selection.Count > 0)
+ {
+ var items = new List();
+ foreach (var node in selection)
+ {
+ if (node is ContentItemTreeNode contentNode)
+ {
+ items.Add(contentNode.Item);
+ }
+ }
+
+ Editor.Instance.Windows.ContentWin.Duplicate(items);
+ }
+ }
+
+ ///
+ /// Copies the items.
+ ///
+ public void Copy()
+ {
+ if (ContentTree == null || !Visible)
+ return;
+
+ var selection = ContentTree.Selection;
+ if (selection.Count == 0)
+ return;
+ var filePaths = new List();
+ foreach (var node in selection)
+ if (node is ContentItemTreeNode contentNode)
+ filePaths.Add(contentNode.Item.Path);
+
+ Clipboard.Files = filePaths.ToArray();
+ UpdateContentItemCut(false);
+ }
+
+ ///
+ /// Pastes the items.
+ ///
+ public void Paste()
+ {
+ if (ContentTree == null || !Visible)
+ return;
+
+ var files = Clipboard.Files;
+ if (files == null || files.Length == 0)
+ return;
+
+ Editor.Instance.Windows.ContentWin.Paste(files, _isCutting);
+ UpdateContentItemCut(false);
+ }
+
+ ///
+ /// Cuts the items.
+ ///
+ public void Cut()
+ {
+ if (ContentTree == null || !Visible)
+ return;
+
+ Copy();
+ UpdateContentItemCut(true);
+ }
+
+ private void UpdateContentItemCut(bool cut)
+ {
+ _isCutting = cut;
+
+ // Add selection to cut list
+ if (cut)
+ {
+ var selection = ContentTree.Selection;
+ foreach (var node in selection)
+ {
+ if (node is ContentItemTreeNode contentNode)
+ _cutItems.Add(contentNode.Item);
+ }
+ }
+
+ // Update item with if it is being cut.
+ foreach (var item in _cutItems)
+ {
+ item.IsBeingCut = cut;
+ }
+
+ // Clean up cut items
+ if (!cut)
+ _cutItems.Clear();
+ }
+
+ ///
+ public override bool OnKeyDown(KeyboardKeys key)
+ {
+ if (!Visible)
+ return false;
+ if (ContentTree == null)
+ return base.OnKeyDown(key);
+
+ if (_inputActions.Process(Editor.Instance, this, key))
+ return true;
+
+ var selection = ContentTree.Selection;
+ if (selection.Count > 0)
+ {
+ if (key == KeyboardKeys.Return)
+ {
+ foreach (var node in selection)
+ {
+ if (node is ContentItemTreeNode contentNode)
+ Editor.Instance.Windows.ContentWin.Open(contentNode.Item);
+ }
+ }
+ }
+ return base.OnKeyDown(key);
+ }
+}
diff --git a/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp
index e8956cec1..a2c35f38f 100644
--- a/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp
@@ -183,16 +183,33 @@ bool WebPlatformTools::OnPostProcess(CookingData& data)
String emscriptenSdk = TEXT("EMSDK");
Platform::GetEnvironmentVariable(emscriptenSdk, emscriptenSdk);
procSettings.FileName = emscriptenSdk / TEXT("upstream/emscripten/tools/file_packager");
-#if PLATFORM_WIN32
- procSettings.FileName += TEXT(".bat");
-#endif
procSettings.Arguments = String::Format(TEXT("files.data --preload \"{}@/\" --lz4 --js-output=files.js"), data.DataOutputPath);
procSettings.WorkingDirectory = data.OriginalOutputPath;
+#if PLATFORM_MAC || PLATFORM_WINDOWS
+ // Use python bundled with SDK (python from min-spec XCode 16.4 is 3.9.6 which is < 3.10 needed by SDK 5.0)
+ Array pythons;
+ FileSystem::GetChildDirectories(pythons, emscriptenSdk / TEXT("/python"));
+ if (pythons.HasItems())
+ {
+ procSettings.Arguments = procSettings.FileName + TEXT(".py ") + procSettings.Arguments;
+#if PLATFORM_WINDOWS
+ procSettings.FileName = pythons[0] / TEXT("/python.exe");
+#else
+ procSettings.FileName = pythons[0] / TEXT("/bin/python3");
+#endif
+ }
+#if PLATFORM_WINDOWS
+ else
+ {
+ procSettings.FileName += TEXT(".bat");
+ }
+#endif
+#endif
const int32 result = Platform::CreateProcess(procSettings);
if (result != 0)
{
if (!FileSystem::FileExists(procSettings.FileName))
- data.Error(TEXT("Missing file_packager.bat. Ensure Emscripten SDK installation is valid and 'EMSDK' environment variable points to it."));
+ data.Error(TEXT("Missing file_packager tool. Ensure Emscripten SDK installation is valid and 'EMSDK' environment variable points to it."));
data.Error(String::Format(TEXT("Failed to package project files (result code: {0}). See log for more info."), result));
return true;
}
diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs
index 40085fd91..fadcab3be 100644
--- a/Source/Editor/Editor.Build.cs
+++ b/Source/Editor/Editor.Build.cs
@@ -73,21 +73,19 @@ public class Editor : EditorModule
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "PS4", "PLATFORM_TOOLS_PS4");
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "PS5", "PLATFORM_TOOLS_PS5");
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "XboxScarlett", "PLATFORM_TOOLS_XBOX_SCARLETT", "PLATFORM_TOOLS_GDK");
- AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Android", "PLATFORM_TOOLS_ANDROID");
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Switch", "PLATFORM_TOOLS_SWITCH");
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Linux", "PLATFORM_TOOLS_LINUX");
}
else if (options.Platform.Target == TargetPlatform.Linux)
{
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Linux", "PLATFORM_TOOLS_LINUX");
- AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Android", "PLATFORM_TOOLS_ANDROID");
}
else if (options.Platform.Target == TargetPlatform.Mac)
{
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Mac", "PLATFORM_TOOLS_MAC");
- AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Android", "PLATFORM_TOOLS_ANDROID");
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "iOS", "PLATFORM_TOOLS_IOS");
}
+ AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Android", "PLATFORM_TOOLS_ANDROID");
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Web", "PLATFORM_TOOLS_WEB");
// Visual Studio integration
@@ -118,11 +116,11 @@ public class Editor : EditorModule
{
files.Add(Path.Combine(FolderPath, "Editor.h"));
files.Add(Path.Combine(FolderPath, "ProjectInfo.h"));
-
files.Add(Path.Combine(FolderPath, "Cooker/CookingData.h"));
files.Add(Path.Combine(FolderPath, "Cooker/GameCooker.h"));
files.Add(Path.Combine(FolderPath, "Cooker/PlatformTools.h"));
files.Add(Path.Combine(FolderPath, "Cooker/Steps/CookAssetsStep.h"));
files.Add(Path.Combine(FolderPath, "Utilities/ViewportIconsRenderer.h"));
+ files.Add(Path.Combine(FolderPath, "Managed/ManagedEditor.h"));
}
}
diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs
index bf04aa068..56c358c2f 100644
--- a/Source/Editor/GUI/AssetPicker.cs
+++ b/Source/Editor/GUI/AssetPicker.cs
@@ -38,11 +38,6 @@ namespace FlaxEditor.GUI
///
public event Action SelectedItemChanged;
- ///
- /// The custom callback for assets validation. Cane be used to implement a rule for assets to pick.
- ///
- public Func CheckValid;
-
///
/// False if changing selected item is disabled.
///
diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs
index 96e01f8b1..8cde79ce8 100644
--- a/Source/Editor/GUI/Docking/DockWindow.cs
+++ b/Source/Editor/GUI/Docking/DockWindow.cs
@@ -89,7 +89,7 @@ namespace FlaxEditor.GUI.Docking
// Check if is docked to the floating window and is selected so update window title
if (IsSelected && _dockedTo is FloatWindowDockPanel floatPanel)
{
- floatPanel.Window.Title = Title;
+ floatPanel.UpdateTitle(_title);
}
}
}
diff --git a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
index f0504db3d..b47d76bdd 100644
--- a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
+++ b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
@@ -200,6 +200,14 @@ namespace FlaxEditor.GUI.Docking
Dispose();
}
+ internal void UpdateTitle(string title)
+ {
+ _window.Title = title;
+ var decorations = Parent.GetChild();
+ if (decorations != null)
+ decorations.PerformLayout();
+ }
+
///
public override bool IsFloating => true;
@@ -227,10 +235,7 @@ namespace FlaxEditor.GUI.Docking
if (_window != null && SelectedTab != null)
{
- _window.Title = SelectedTab.Title;
- var decorations = Parent.GetChild();
- if (decorations != null)
- decorations.PerformLayout();
+ UpdateTitle(SelectedTab.Title);
}
}
diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs
index 03c18268d..8ab597952 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs
@@ -81,7 +81,8 @@ namespace FlaxEditor.Gizmo
_isDisabled = ShouldGizmoBeLocked();
float brightness = _isDisabled ? options.Visual.TransformGizmoBrightnessDisabled : options.Visual.TransformGizmoBrightness;
- if (Mathf.NearEqual(brightness, (float)_materialAxisX.GetParameterValue(_brightnessParamName)))
+ var currentValue = _materialAxisX.GetParameterValue(_brightnessParamName);
+ if (currentValue is not float currentValueFloat || Mathf.NearEqual(brightness, currentValueFloat))
return;
_materialAxisX.SetParameterValue(_brightnessParamName, brightness);
_materialAxisY.SetParameterValue(_brightnessParamName, brightness);
diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h
index e320e00cf..4df4658b6 100644
--- a/Source/Editor/Managed/ManagedEditor.h
+++ b/Source/Editor/Managed/ManagedEditor.h
@@ -4,7 +4,6 @@
#include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Platform/Window.h"
-#include "Engine/ShadowsOfMordor/Types.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Tools/ModelTool/ModelTool.h"
#include "Engine/Tools/AudioTool/AudioTool.h"
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index 1ecab7089..1a9abe23c 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -499,6 +499,7 @@ namespace FlaxEditor.Modules
InitWindowDecorations(mainWindow);
Editor.Options.OptionsChanged += OnOptionsChanged;
+ Editor.CodeEditing.SelectedEditorChanged += OnSelectedCodeEditorChanged;
mainWindow.PerformLayout(true);
}
@@ -870,6 +871,11 @@ namespace FlaxEditor.Modules
UpdateToolstrip();
}
+ private void OnSelectedCodeEditorChanged(SourceCodeEditing.ISourceCodeEditor codeEditor)
+ {
+ _menuFileOpenScriptsProject.Text = $"Open scripts project ({(codeEditor?.Name ?? "Default")})";
+ }
+
private void InitToolstrip(RootControl mainWindow)
{
var inputOptions = Editor.Options.Options.Input;
diff --git a/Source/Editor/States/PlayingState.cs b/Source/Editor/States/PlayingState.cs
index 50cde2212..51dfd3677 100644
--- a/Source/Editor/States/PlayingState.cs
+++ b/Source/Editor/States/PlayingState.cs
@@ -163,7 +163,7 @@ namespace FlaxEditor.States
IsPlayModeStarting = false;
Profiler.EndEvent();
- Time.Synchronize();
+ Time.Synchronize(true);
}
private void SetupEditorEnvOptions()
@@ -213,7 +213,7 @@ namespace FlaxEditor.States
IsPlayModeEnding = false;
Profiler.EndEvent();
- Time.Synchronize();
+ Time.Synchronize(true);
}
}
}
diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs
index e376c3e43..e3d1d9d36 100644
--- a/Source/Editor/Surface/Archetypes/Animation.cs
+++ b/Source/Editor/Surface/Archetypes/Animation.cs
@@ -1138,6 +1138,21 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(0, "", typeof(float), 0),
}
},
+ new NodeArchetype
+ {
+ TypeID = 37,
+ Title = "Instance Transform",
+ Description = "Animated model transformation (in world space).",
+ Flags = NodeFlags.AnimGraph,
+ Size = new Float2(200, 80),
+ Elements = new[]
+ {
+ NodeElementArchetype.Factory.Output(0, "", typeof(Transform), 0),
+ NodeElementArchetype.Factory.Output(1, "Position", typeof(Vector3), 1),
+ NodeElementArchetype.Factory.Output(2, "Rotation", typeof(Quaternion), 2),
+ NodeElementArchetype.Factory.Output(3, "Scale", typeof(Float3), 3),
+ }
+ },
};
}
}
diff --git a/Source/Editor/Surface/SurfaceParameter.cs b/Source/Editor/Surface/SurfaceParameter.cs
index c76cb058e..be69f69b2 100644
--- a/Source/Editor/Surface/SurfaceParameter.cs
+++ b/Source/Editor/Surface/SurfaceParameter.cs
@@ -48,5 +48,11 @@ namespace FlaxEditor.Surface
///
[NoSerialize, HideInEditor]
public readonly SurfaceMeta Meta = new SurfaceMeta();
+
+ ///
+ public override string ToString()
+ {
+ return $"{Type} {Name} = {Value?.ToString() ?? "null"}";
+ }
}
}
diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs
index 60cd714c4..638d8338c 100644
--- a/Source/Editor/Surface/SurfaceUtils.cs
+++ b/Source/Editor/Surface/SurfaceUtils.cs
@@ -21,6 +21,7 @@ namespace FlaxEditor.Surface
{
public GraphParameter Parameter;
public bool IsPublic;
+ public int Index;
public Type Type;
public object Tag;
public Attribute[] Attributes;
@@ -31,15 +32,16 @@ namespace FlaxEditor.Surface
public HeaderAttribute Header;
public string DisplayName;
- public GraphParameterData(GraphParameter parameter, object tag = null)
- : this(parameter, null, parameter.IsPublic, parameter.Type, SurfaceMeta.GetAttributes(parameter), tag)
+ public GraphParameterData(GraphParameter parameter, int index, object tag = null)
+ : this(parameter, index, null, parameter.IsPublic, parameter.Type, SurfaceMeta.GetAttributes(parameter), tag)
{
}
- public GraphParameterData(GraphParameter parameter, string name, bool isPublic, Type type, Attribute[] attributes, object tag)
+ public GraphParameterData(GraphParameter parameter, int index, string name, bool isPublic, Type type, Attribute[] attributes, object tag)
{
Parameter = parameter;
IsPublic = isPublic;
+ Index = index;
Type = type;
Tag = tag;
Attributes = attributes;
@@ -74,8 +76,14 @@ namespace FlaxEditor.Surface
if (Editor.Instance.Options.Options.General.ScriptMembersOrder == GeneralOptions.MembersOrder.Alphabetical)
return string.Compare(x.DisplayName, y.DisplayName, StringComparison.InvariantCulture);
- // Keep same order
- return 0;
+ // Keep same order (from input indices)
+ return x.Index - y.Index;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{Type} {DisplayName}";
}
}
@@ -206,6 +214,7 @@ namespace FlaxEditor.Surface
Profiler.EndEvent();
}
+ int index = 0;
foreach (var parameter in parameters)
{
var parameterId = parameter.ParameterID;
@@ -229,7 +238,7 @@ namespace FlaxEditor.Surface
surfaceParameters.Add(surfaceParameter);
}
var attributes = surfaceParameter?.Meta.GetAttributes() ?? FlaxEngine.Utils.GetEmptyArray();
- data[i] = new GraphParameterData(null, parameter.Name, parameter.IsPublic, ToType(parameter.ParameterType), attributes, parameter);
+ data[i] = new GraphParameterData(null, index++, parameter.Name, parameter.IsPublic, ToType(parameter.ParameterType), attributes, parameter);
i++;
}
Array.Sort(data, GraphParameterData.Compare);
@@ -243,7 +252,7 @@ namespace FlaxEditor.Surface
int i = 0;
foreach (var parameter in parameters)
{
- data[i] = new GraphParameterData(parameter.EmitterParameter, parameter);
+ data[i] = new GraphParameterData(parameter.EmitterParameter, i, parameter);
i++;
}
Array.Sort(data, GraphParameterData.Compare);
@@ -257,7 +266,7 @@ namespace FlaxEditor.Surface
int i = 0;
foreach (var parameter in parameters)
{
- data[i] = new GraphParameterData(parameter);
+ data[i] = new GraphParameterData(parameter, i);
i++;
}
Array.Sort(data, GraphParameterData.Compare);
diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
index a678e868a..10dcdf671 100644
--- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
+++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
@@ -17,7 +17,7 @@ namespace FlaxEditor.Viewport.Previews
private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton;
private bool _showNodes, _showBounds, _showFloor, _showNodesNames;
private StaticModel _floorModel;
- private bool _playAnimation, _playAnimationOnce;
+ private bool _playAnimation, _playAnimationOnce, _autoAdjustCamera = true;
private float _playSpeed = 1.0f;
///
@@ -291,6 +291,13 @@ namespace FlaxEditor.Viewport.Previews
private void OnBegin(RenderTask task, GPUContext context)
{
+ if (_autoAdjustCamera && SkinnedModel && SkinnedModel.IsLoaded)
+ {
+ // Control camera's near/far planes to properly cover object
+ _autoAdjustCamera = false;
+ ModelPreview.AdjustCamera(this, SkinnedModel.GetBox());
+ }
+
if (!ScaleToFit)
{
if (_snapToOrigin)
diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs
index ae0424da3..aff9ebb4c 100644
--- a/Source/Editor/Viewport/Previews/ModelPreview.cs
+++ b/Source/Editor/Viewport/Previews/ModelPreview.cs
@@ -18,7 +18,7 @@ namespace FlaxEditor.Viewport.Previews
private ContextMenuButton _showBoundsButton, _showCurrentLODButton, _showNormalsButton, _showTangentsButton, _showBitangentsButton, _showFloorButton;
private ContextMenu _previewLODsWidgetButtonMenu;
private StaticModel _previewModel, _floorModel;
- private bool _showBounds, _showCurrentLOD, _showNormals, _showTangents, _showBitangents, _showFloor;
+ private bool _showBounds, _showCurrentLOD, _showNormals, _showTangents, _showBitangents, _showFloor, _autoAdjustCamera = true;
private MeshDataCache _meshDatas;
///
@@ -221,8 +221,8 @@ namespace FlaxEditor.Viewport.Previews
_showCurrentLODButton.IndexInParent = 2;
_showCurrentLODButton.CloseMenuOnClick = false;
- // Preview LODs mode widget
- var PreviewLODsMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
+ // Preview LOD mode widget
+ var previewLODsMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_previewLODsWidgetButtonMenu = new ContextMenu();
_previewLODsWidgetButtonMenu.VisibleChanged += control =>
{
@@ -248,14 +248,32 @@ namespace FlaxEditor.Viewport.Previews
new ViewportWidgetButton("Preview LOD", SpriteHandle.Invalid, _previewLODsWidgetButtonMenu)
{
TooltipText = "Preview LOD properties",
- Parent = PreviewLODsMode,
+ Parent = previewLODsMode,
};
- PreviewLODsMode.Parent = this;
+ previewLODsMode.Parent = this;
+ }
+ }
+
+ internal static void AdjustCamera(AssetPreview preview, BoundingBox box)
+ {
+ if (box.Size.MaxValue < preview.NearPlane)
+ {
+ // Very small object
+ preview.NearPlane = Mathf.Min(preview.NearPlane, (float)box.Size.MaxValue * 0.5f + 0.01f);
+ preview.FarPlane *= 0.5f;
+ preview.MovementSpeed = 0.1f;
}
}
private void OnBegin(RenderTask task, GPUContext context)
{
+ if (_autoAdjustCamera && Model && Model.IsLoaded)
+ {
+ // Control camera's near/far planes to properly cover object
+ _autoAdjustCamera = false;
+ AdjustCamera(this, Model.GetBox());
+ }
+
if (!ScaleToFit)
{
_previewModel.Scale = Float3.One;
diff --git a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs
index 3048d0c78..d394190ce 100644
--- a/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs
+++ b/Source/Editor/Viewport/Previews/SkinnedModelPreview.cs
@@ -51,8 +51,8 @@ namespace FlaxEditor.Viewport.Previews
_showCurrentLODButton.IndexInParent = 2;
_showCurrentLODButton.CloseMenuOnClick = false;
- // PreviewLODS mode widget
- var PreviewLODSMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
+ // Preview LOD mode widget
+ var previewLODMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_previewLODsWidgetButtonMenu = new ContextMenu();
_previewLODsWidgetButtonMenu.VisibleChanged += control =>
{
@@ -78,9 +78,9 @@ namespace FlaxEditor.Viewport.Previews
new ViewportWidgetButton("Preview LOD", SpriteHandle.Invalid, _previewLODsWidgetButtonMenu)
{
TooltipText = "Preview LOD properties",
- Parent = PreviewLODSMode,
+ Parent = previewLODMode,
};
- PreviewLODSMode.Parent = this;
+ previewLODMode.Parent = this;
}
}
diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
index 03ba26fc8..1317300c3 100644
--- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
+++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
@@ -509,10 +509,25 @@ namespace FlaxEditor.Windows.Assets
// Check if don't have valid behavior picked
if (!_behaviorPicker.Value)
{
- // Try to reassign the debug behavior
var id = _cachedBehaviorId;
+ if (id == Guid.Empty && Editor.IsPlayMode && Editor.Options.Options.General.AutoAttachDebugPreviewActor)
+ {
+ // Auto-attach preview
+ var behaviorTree = Asset;
+ var behaviors = Level.GetScripts();
+ foreach (var behavior in behaviors)
+ {
+ if (behavior.Tree == behaviorTree &&
+ behavior.IsEnabledInHierarchy)
+ {
+ id = behavior.ID;
+ break;
+ }
+ }
+ }
if (id != Guid.Empty)
{
+ // Try to reassign the debug behavior
var obj = FlaxEngine.Object.TryFind(ref id);
if (obj && obj.Tree == Asset)
_behaviorPicker.Value = obj;
diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs
index 934cd6594..b051542a1 100644
--- a/Source/Editor/Windows/Assets/MaterialWindow.cs
+++ b/Source/Editor/Windows/Assets/MaterialWindow.cs
@@ -57,7 +57,7 @@ namespace FlaxEditor.Windows.Assets
[EditorOrder(20), VisibleIf(nameof(IsStandard)), EditorDisplay("General"), Tooltip("Defines how material inputs and properties are combined to result the final surface color.")]
public MaterialShadingModel ShadingModel;
- [EditorOrder(30), VisibleIf(nameof(IsStandard)), EditorDisplay("General"), Tooltip("Determinates how materials' color should be blended with the background colors.")]
+ [EditorOrder(30), VisibleIf(nameof(ShowBlendMode)), EditorDisplay("General"), Tooltip("Determinates how materials' color should be blended with the background colors.")]
public MaterialBlendMode BlendMode;
// Rendering
@@ -145,6 +145,7 @@ namespace FlaxEditor.Windows.Assets
// Visibility conditionals
+ private bool ShowBlendMode => Domain != MaterialDomain.Terrain;
private bool IsPostProcess => Domain == MaterialDomain.PostProcess;
private bool IsDecal => Domain == MaterialDomain.Decal;
private bool IsGUI => Domain == MaterialDomain.GUI;
@@ -187,6 +188,14 @@ namespace FlaxEditor.Windows.Assets
// Link
Window = window;
+
+ // [Deprecated in 1.12]
+ // Fix old terrain materials to go back into opaque
+ if (Domain == MaterialDomain.Terrain && BlendMode != MaterialBlendMode.Opaque)
+ {
+ BlendMode = MaterialBlendMode.Opaque;
+ FlaxEngine.Scripting.InvokeOnUpdate(Window.MarkAsEdited);
+ }
}
///
diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs
index 09b4606c4..3046ea43b 100644
--- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs
+++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs
@@ -112,12 +112,14 @@ namespace FlaxEditor.Windows.Assets
Window._isolateIndex = -1;
Window._highlightIndex = -1;
+ Window._meshProxy = this;
}
public override void OnClean()
{
Window._isolateIndex = -1;
Window._highlightIndex = -1;
+ Window._meshProxy = null;
base.OnClean();
}
@@ -402,40 +404,12 @@ namespace FlaxEditor.Windows.Assets
slots[i].ShadowsMode = shadowsModes[i];
}
- UpdateMaterialSlotsUI();
+ Window?._meshProxy?.UpdateMaterialSlotsUI();
}
}
}
}
- private readonly List _materialSlotComboBoxes = new List();
-
- ///
- /// Updates the material slots UI parts. Should be called after material slot rename.
- ///
- public void UpdateMaterialSlotsUI()
- {
- Window._skipEffectsGuiEvents = true;
-
- // Generate material slots labels (with index prefix)
- var slots = Asset.MaterialSlots;
- var slotsLabels = new string[slots.Length];
- for (int i = 0; i < slots.Length; i++)
- {
- slotsLabels[i] = string.Format("[{0}] {1}", i, slots[i].Name);
- }
-
- // Update comboboxes
- for (int i = 0; i < _materialSlotComboBoxes.Count; i++)
- {
- var comboBox = _materialSlotComboBoxes[i];
- comboBox.SetItems(slotsLabels);
- comboBox.SelectedIndex = ((Mesh)comboBox.Tag).MaterialSlotIndex;
- }
-
- Window._skipEffectsGuiEvents = false;
- }
-
protected class ProxyEditor : ProxyEditorBase
{
public override void Initialize(LayoutElementsContainer layout)
@@ -776,6 +750,7 @@ namespace FlaxEditor.Windows.Assets
protected readonly Tabs _tabs;
protected readonly ToolStripButton _saveButton;
+ private MeshesPropertiesProxyBase _meshProxy;
protected ModelImportSettings _importSettings = new ModelImportSettings();
protected bool _refreshOnLODsLoaded;
protected bool _skipEffectsGuiEvents;
diff --git a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs
index 358618f80..83be4b43c 100644
--- a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs
+++ b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs
@@ -338,6 +338,8 @@ namespace FlaxEditor.Windows.Assets
PlaySimulation = true,
Parent = _split2.Panel1
};
+ _preview.PreviewActor.ShowDebugDraw = true;
+ _preview.ShowDebugDraw = true;
// Timeline
_timeline = new ParticleSystemTimeline(_preview, _undo)
diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
index 75fa87dfe..839905f40 100644
--- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
+++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
@@ -272,7 +272,7 @@ namespace FlaxEditor.Windows.Assets
infoLabel.AutoHeight = true;
var sourceAssetPicker = setupGroup.AddPropertyItem("Source Asset").Custom().CustomControl;
sourceAssetPicker.Height = 48;
- sourceAssetPicker.CheckValid = CheckSourceAssetValid;
+ sourceAssetPicker.Validator.CheckValid = CheckSourceAssetValid;
sourceAssetPicker.SelectedItemChanged += () =>
{
proxy.Setups.Add(sourceAssetPicker.Validator.SelectedAsset, new SetupProxy());
diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
index ad5a8caf2..f4985eb7a 100644
--- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
@@ -67,12 +67,28 @@ namespace FlaxEditor.Windows
b = cm.AddButton("Open", () => Open(item));
b.Enabled = proxy != null || isFolder;
- if (_view.SelectedCount > 1)
- b = cm.AddButton("Open (all selected)", () =>
+ if (_showAllContentInTree)
+ {
+ var selection = _tree.Selection;
+ if (selection.Count > 0)
{
- foreach (var e in _view.Selection)
- Open(e);
- });
+ b = cm.AddButton("Open (all selected)", () =>
+ {
+ foreach (var e in _tree.Selection)
+ if (e is ContentItemTreeNode contentNode)
+ Open(contentNode.Item);
+ });
+ }
+ }
+ else
+ {
+ if (_view.SelectedCount > 1)
+ b = cm.AddButton("Open (all selected)", () =>
+ {
+ foreach (var e in _view.Selection)
+ Open(e);
+ });
+ }
cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(item.Path)));
@@ -137,12 +153,21 @@ namespace FlaxEditor.Windows
{
cm.AddButton("Delete", () => Delete(item));
cm.AddSeparator();
- cm.AddButton("Duplicate", _view.Duplicate);
- cm.AddButton("Cut", _view.Cut);
- cm.AddButton("Copy", _view.Copy);
+ if (_showAllContentInTree)
+ {
+ cm.AddButton("Duplicate", _treeOnlyPanel.Duplicate);
+ cm.AddButton("Cut", _treeOnlyPanel.Cut);
+ cm.AddButton("Copy", _treeOnlyPanel.Copy);
+ }
+ else
+ {
+ cm.AddButton("Duplicate", _view.Duplicate);
+ cm.AddButton("Cut", _view.Cut);
+ cm.AddButton("Copy", _view.Copy);
+ }
}
- b = cm.AddButton("Paste", _view.Paste);
+ b = _showAllContentInTree ? cm.AddButton("Paste", _treeOnlyPanel.Paste) : cm.AddButton("Paste", _view.Paste);
b.Enabled = _view.CanPaste();
if (isFolder && folder.Node is MainContentFolderTreeNode)
diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs
index 2141b283e..5a73530bc 100644
--- a/Source/Editor/Windows/ContentWindow.cs
+++ b/Source/Editor/Windows/ContentWindow.cs
@@ -32,7 +32,7 @@ namespace FlaxEditor.Windows
private string _workspaceRebuildLocation;
private string _lastViewedFolderBeforeReload;
private SplitPanel _split;
- private Panel _treeOnlyPanel;
+ private TreeViewPanel _treeOnlyPanel;
private ContainerControl _treePanelRoot;
private ContainerControl _treeHeaderPanel;
private Panel _contentItemsSearchPanel;
@@ -186,7 +186,7 @@ namespace FlaxEditor.Windows
};
// Tree-only panel (used when showing all content in the tree)
- _treeOnlyPanel = new Panel(ScrollBars.None)
+ _treeOnlyPanel = new TreeViewPanel
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = new Margin(0, 0, _toolStrip.Bottom, 0),
@@ -237,6 +237,7 @@ namespace FlaxEditor.Windows
Parent = _contentTreePanel,
};
_tree.SelectedChanged += OnTreeSelectionChanged;
+ _treeOnlyPanel.ContentTree = _tree;
// Content items searching query input box and filters selector
_contentItemsSearchPanel = new Panel
@@ -632,11 +633,7 @@ namespace FlaxEditor.Windows
var area = node.TextRect;
const float minRenameWidth = 220.0f;
if (area.Width < minRenameWidth)
- {
- float expand = minRenameWidth - area.Width;
- area.X -= expand * 0.5f;
area.Width = minRenameWidth;
- }
area.Y -= 2;
area.Height += 4.0f;
popup = RenamePopup.Show(node, area, item.ShortName, true);
@@ -889,6 +886,7 @@ namespace FlaxEditor.Windows
// Refresh this folder now and try to find duplicated item
Editor.ContentDatabase.RefreshFolder(item.ParentFolder, true);
RefreshView();
+ RefreshTreeItems();
var targetItem = item.ParentFolder.FindChild(targetPath);
// Start renaming it
diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp
index 5eee07dbf..55d96e415 100644
--- a/Source/Engine/AI/Behavior.cpp
+++ b/Source/Engine/AI/Behavior.cpp
@@ -113,8 +113,6 @@ void Behavior::UpdateAsync()
{
// Reset State
_result = BehaviorUpdateResult::Running;
- _accumulatedTime = 0.0f;
- _totalTime = 0;
}
else if (_result != BehaviorUpdateResult::Running)
{
diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
index e06f0deaa..b07b5a132 100644
--- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
+++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
@@ -2660,6 +2660,28 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
value = actor ? actor->GetPerInstanceRandom() : 0.0f;
break;
}
+ // Instance Transform
+ case 37:
+ {
+ auto* actor = ScriptingObject::Cast(context.Data->Object);
+ const auto& transform = actor ? actor->GetTransform() : Transform::Identity;
+ switch (box->ID)
+ {
+ case 0:
+ value = Value(transform);
+ break;
+ case 1:
+ value = transform.Translation;
+ break;
+ case 2:
+ value = transform.Orientation;
+ break;
+ case 3:
+ value = transform.Scale;
+ break;
+ }
+ break;
+ }
default:
break;
}
diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp
index 4d7c21b91..da6b7329a 100644
--- a/Source/Engine/Content/Asset.cpp
+++ b/Source/Engine/Content/Asset.cpp
@@ -585,7 +585,8 @@ void Asset::startLoading()
{
PROFILE_MEM(ContentAssets);
ASSERT(!IsLoaded());
- ASSERT(Platform::AtomicRead(&_loadingTask) == 0);
+ auto task = (Task*)Platform::AtomicRead(&_loadingTask);
+ ASSERT(task == nullptr || task->IsFinished() || task->IsCanceled());
auto loadingTask = createLoadingTask();
ASSERT(loadingTask != nullptr);
Platform::AtomicStore(&_loadingTask, (intptr)loadingTask);
diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp
index d7172e091..340726644 100644
--- a/Source/Engine/Content/Assets/Model.cpp
+++ b/Source/Engine/Content/Assets/Model.cpp
@@ -16,6 +16,7 @@
#include "Engine/Graphics/Async/GPUTask.h"
#include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h"
#include "Engine/Graphics/Models/MeshDeformation.h"
+#include "Engine/Graphics/Models/ModelDraw.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
@@ -136,105 +137,6 @@ void Model::Draw(const RenderContext& renderContext, MaterialBase* material, con
LODs[lodIndex].Draw(renderContext, material, world, flags, receiveDecals, DrawPass::Default, 0, sortOrder);
}
-template
-FORCE_INLINE void ModelDraw(Model* model, const RenderContext& renderContext, const ContextType& context, const Mesh::DrawInfo& info)
-{
- ASSERT(info.Buffer);
- if (!model->CanBeRendered())
- return;
- if (!info.Buffer->IsValidFor(model))
- info.Buffer->Setup(model);
- const auto frame = Engine::FrameCount;
- const auto modelFrame = info.DrawState->PrevFrame + 1;
-
- // Select a proper LOD index (model may be culled)
- int32 lodIndex;
- if (info.ForcedLOD != -1)
- {
- lodIndex = info.ForcedLOD;
- }
- else
- {
- lodIndex = RenderTools::ComputeModelLOD(model, info.Bounds.Center, (float)info.Bounds.Radius, renderContext);
- if (lodIndex == -1)
- {
- // Handling model fade-out transition
- if (modelFrame == frame && info.DrawState->PrevLOD != -1 && !renderContext.View.IsSingleFrame)
- {
- // Check if start transition
- if (info.DrawState->LODTransition == 255)
- {
- info.DrawState->LODTransition = 0;
- }
-
- RenderTools::UpdateModelLODTransition(info.DrawState->LODTransition);
-
- // Check if end transition
- if (info.DrawState->LODTransition == 255)
- {
- info.DrawState->PrevLOD = lodIndex;
- }
- else
- {
- const auto prevLOD = model->ClampLODIndex(info.DrawState->PrevLOD);
- const float normalizedProgress = static_cast(info.DrawState->LODTransition) * (1.0f / 255.0f);
- model->LODs.Get()[prevLOD].Draw(renderContext, info, normalizedProgress);
- }
- }
-
- return;
- }
- }
- lodIndex += info.LODBias + renderContext.View.ModelLODBias;
- lodIndex = model->ClampLODIndex(lodIndex);
-
- if (renderContext.View.IsSingleFrame)
- {
- }
- // Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports)
- else if (modelFrame == frame)
- {
- // Check if start transition
- if (info.DrawState->PrevLOD != lodIndex && info.DrawState->LODTransition == 255)
- {
- info.DrawState->LODTransition = 0;
- }
-
- RenderTools::UpdateModelLODTransition(info.DrawState->LODTransition);
-
- // Check if end transition
- if (info.DrawState->LODTransition == 255)
- {
- info.DrawState->PrevLOD = lodIndex;
- }
- }
- // Check if there was a gap between frames in drawing this model instance
- else if (modelFrame < frame || info.DrawState->PrevLOD == -1)
- {
- // Reset state
- info.DrawState->PrevLOD = lodIndex;
- info.DrawState->LODTransition = 255;
- }
-
- // Draw
- if (info.DrawState->PrevLOD == lodIndex || renderContext.View.IsSingleFrame)
- {
- model->LODs.Get()[lodIndex].Draw(context, info, 0.0f);
- }
- else if (info.DrawState->PrevLOD == -1)
- {
- const float normalizedProgress = static_cast(info.DrawState->LODTransition) * (1.0f / 255.0f);
- model->LODs.Get()[lodIndex].Draw(context, info, 1.0f - normalizedProgress);
- }
- else
- {
- const auto prevLOD = model->ClampLODIndex(info.DrawState->PrevLOD);
- const float normalizedProgress = static_cast(info.DrawState->LODTransition) * (1.0f / 255.0f);
- model->LODs.Get()[prevLOD].Draw(context, info, normalizedProgress);
- model->LODs.Get()[lodIndex].Draw(context, info, normalizedProgress - 1.0f);
- }
-}
-
void Model::Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info)
{
ModelDraw(this, renderContext, renderContext, info);
diff --git a/Source/Engine/Content/Assets/SkeletonMask.cpp b/Source/Engine/Content/Assets/SkeletonMask.cpp
index 2776ba02c..37b543f8f 100644
--- a/Source/Engine/Content/Assets/SkeletonMask.cpp
+++ b/Source/Engine/Content/Assets/SkeletonMask.cpp
@@ -51,13 +51,14 @@ AssetChunksFlag SkeletonMask::getChunksToPreload() const
const BitArray<>& SkeletonMask::GetNodesMask()
{
- if (_mask.IsEmpty() && Skeleton && !Skeleton->WaitForLoaded())
+ if (!Skeleton || Skeleton->WaitForLoaded())
+ return _mask;
+ ScopeLock lock(Locker);
+ if (_mask.IsEmpty())
{
_mask.Resize(Skeleton->Skeleton.Nodes.Count());
for (int32 i = 0; i < _mask.Count(); i++)
- {
_mask.Set(i, _maskedNodes.Contains(Skeleton->Skeleton.Nodes[i].Name));
- }
}
return _mask;
diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp
index c0355ea0e..965df12f0 100644
--- a/Source/Engine/Content/Assets/SkinnedModel.cpp
+++ b/Source/Engine/Content/Assets/SkinnedModel.cpp
@@ -12,6 +12,7 @@
#include "Engine/Graphics/Models/Config.h"
#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Graphics/Models/ModelInstanceEntry.h"
+#include "Engine/Graphics/Models/ModelDraw.h"
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
@@ -234,113 +235,14 @@ BoundingBox SkinnedModel::GetBox(int32 lodIndex) const
return LODs[lodIndex].GetBox();
}
-template
-FORCE_INLINE void SkinnedModelDraw(SkinnedModel* model, const RenderContext& renderContext, const ContextType& context, const SkinnedMesh::DrawInfo& info)
-{
- ASSERT(info.Buffer);
- if (!model->CanBeRendered())
- return;
- if (!info.Buffer->IsValidFor(model))
- info.Buffer->Setup(model);
- const auto frame = Engine::FrameCount;
- const auto modelFrame = info.DrawState->PrevFrame + 1;
-
- // Select a proper LOD index (model may be culled)
- int32 lodIndex;
- if (info.ForcedLOD != -1)
- {
- lodIndex = info.ForcedLOD;
- }
- else
- {
- lodIndex = RenderTools::ComputeSkinnedModelLOD(model, info.Bounds.Center, (float)info.Bounds.Radius, renderContext);
- if (lodIndex == -1)
- {
- // Handling model fade-out transition
- if (modelFrame == frame && info.DrawState->PrevLOD != -1 && !renderContext.View.IsSingleFrame)
- {
- // Check if start transition
- if (info.DrawState->LODTransition == 255)
- {
- info.DrawState->LODTransition = 0;
- }
-
- RenderTools::UpdateModelLODTransition(info.DrawState->LODTransition);
-
- // Check if end transition
- if (info.DrawState->LODTransition == 255)
- {
- info.DrawState->PrevLOD = lodIndex;
- }
- else
- {
- const auto prevLOD = model->ClampLODIndex(info.DrawState->PrevLOD);
- const float normalizedProgress = static_cast(info.DrawState->LODTransition) * (1.0f / 255.0f);
- model->LODs.Get()[prevLOD].Draw(renderContext, info, normalizedProgress);
- }
- }
-
- return;
- }
- }
- lodIndex += info.LODBias + renderContext.View.ModelLODBias;
- lodIndex = model->ClampLODIndex(lodIndex);
-
- if (renderContext.View.IsSingleFrame)
- {
- }
- // Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports)
- else if (modelFrame == frame)
- {
- // Check if start transition
- if (info.DrawState->PrevLOD != lodIndex && info.DrawState->LODTransition == 255)
- {
- info.DrawState->LODTransition = 0;
- }
-
- RenderTools::UpdateModelLODTransition(info.DrawState->LODTransition);
-
- // Check if end transition
- if (info.DrawState->LODTransition == 255)
- {
- info.DrawState->PrevLOD = lodIndex;
- }
- }
- // Check if there was a gap between frames in drawing this model instance
- else if (modelFrame < frame || info.DrawState->PrevLOD == -1)
- {
- // Reset state
- info.DrawState->PrevLOD = lodIndex;
- info.DrawState->LODTransition = 255;
- }
-
- // Draw
- if (info.DrawState->PrevLOD == lodIndex || renderContext.View.IsSingleFrame)
- {
- model->LODs.Get()[lodIndex].Draw(context, info, 0.0f);
- }
- else if (info.DrawState->PrevLOD == -1)
- {
- const float normalizedProgress = static_cast(info.DrawState->LODTransition) * (1.0f / 255.0f);
- model->LODs.Get()[lodIndex].Draw(context, info, 1.0f - normalizedProgress);
- }
- else
- {
- const auto prevLOD = model->ClampLODIndex(info.DrawState->PrevLOD);
- const float normalizedProgress = static_cast(info.DrawState->LODTransition) * (1.0f / 255.0f);
- model->LODs.Get()[prevLOD].Draw(context, info, normalizedProgress);
- model->LODs.Get()[lodIndex].Draw(context, info, normalizedProgress - 1.0f);
- }
-}
-
void SkinnedModel::Draw(const RenderContext& renderContext, const SkinnedMesh::DrawInfo& info)
{
- SkinnedModelDraw(this, renderContext, renderContext, info);
+ ModelDraw(this, renderContext, renderContext, info);
}
void SkinnedModel::Draw(const RenderContextBatch& renderContextBatch, const SkinnedMesh::DrawInfo& info)
{
- SkinnedModelDraw(this, renderContextBatch.GetMainContext(), renderContextBatch, info);
+ ModelDraw(this, renderContextBatch.GetMainContext(), renderContextBatch, info);
}
bool SkinnedModel::SetupLODs(const Span& meshesCountPerLod)
diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp
index 3d709499b..23768f6b0 100644
--- a/Source/Engine/Engine/Engine.cpp
+++ b/Source/Engine/Engine/Engine.cpp
@@ -157,7 +157,7 @@ int32 Engine::OnInit(const Char* cmdLine)
Application::BeforeRun();
LOG_FLOOR();
LOG_FLUSH();
- Time::Synchronize();
+ Time::Synchronize(true);
EngineImpl::IsReady = true;
PROFILE_MEM_END();
diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs
index 564c53801..304270b34 100644
--- a/Source/Engine/Engine/NativeInterop.cs
+++ b/Source/Engine/Engine/NativeInterop.cs
@@ -1626,7 +1626,9 @@ namespace FlaxEngine.Interop
// We need private types of this assembly too, DefinedTypes contains a lot of types from other assemblies...
var types = referencedTypes.Any() ? assembly.DefinedTypes.Where(x => !referencedTypes.Contains(x.FullName)).ToArray() : assembly.DefinedTypes.ToArray();
+#if FLAX_EDITOR
Assert.IsTrue(Utils.GetAssemblies().Count(x => x.GetName().Name == "FlaxEngine.CSharp") == 1);
+#endif
return types;
}
diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp
index 7d268cf73..4ca9f7ecd 100644
--- a/Source/Engine/Engine/Time.cpp
+++ b/Source/Engine/Engine/Time.cpp
@@ -75,10 +75,11 @@ void TimeSettings::Apply()
#endif
}
-void Time::TickData::Synchronize(float targetFps, double currentTime)
+void Time::TickData::Synchronize(float targetFps, double currentTime, bool resetTotalTime)
{
OnReset(targetFps, currentTime);
- Time = UnscaledTime = TimeSpan::Zero();
+ if (resetTotalTime)
+ Time = UnscaledTime = TimeSpan::Zero();
NextBegin = targetFps > ZeroTolerance ? LastBegin + (1.0f / targetFps) : 0.0;
}
@@ -258,13 +259,13 @@ void Time::SetFixedDeltaTime(bool enable, float value)
FixedDeltaTimeValue = value;
}
-void Time::Synchronize()
+void Time::Synchronize(bool resetTotalTime)
{
// Initialize tick data (based on a time settings)
const double time = Platform::GetTimeSeconds();
- Update.Synchronize(UpdateFPS, time);
- Physics.Synchronize(PhysicsFPS, time);
- Draw.Synchronize(DrawFPS, time);
+ Update.Synchronize(UpdateFPS, time, resetTotalTime);
+ Physics.Synchronize(PhysicsFPS, time, resetTotalTime);
+ Draw.Synchronize(DrawFPS, time, resetTotalTime);
}
bool Time::OnBeginUpdate(double time)
diff --git a/Source/Engine/Engine/Time.h b/Source/Engine/Engine/Time.h
index bda7a7537..ee6802f67 100644
--- a/Source/Engine/Engine/Time.h
+++ b/Source/Engine/Engine/Time.h
@@ -75,7 +75,7 @@ public:
TimeSpan UnscaledTime;
public:
- virtual void Synchronize(float targetFps, double currentTime);
+ virtual void Synchronize(float targetFps, double currentTime, bool resetTotalTime);
virtual void OnReset(float targetFps, double currentTime);
virtual bool OnTickBegin(double time, float targetFps, float maxDeltaTime);
virtual void OnTickEnd();
@@ -220,7 +220,8 @@ public:
///
/// Synchronizes update, fixed update and draw. Resets any pending deltas for fresh ticking in sync.
///
- API_FUNCTION() static void Synchronize();
+ /// True if reset total time, otherwise only delta time and ticking is reset.
+ API_FUNCTION() static void Synchronize(bool resetTotalTime = false);
private:
// Methods used by the Engine class
diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp
index 1fe9e39a9..7428a5a8b 100644
--- a/Source/Engine/Foliage/Foliage.cpp
+++ b/Source/Engine/Foliage/Foliage.cpp
@@ -168,9 +168,6 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCal
// Draw visible children
if (cluster->Children[0])
{
- // Don't store instances in non-leaf nodes
- ASSERT_LOW_LAYER(cluster->Instances.IsEmpty());
-
BoundingBox box;
#define DRAW_CLUSTER(idx) \
box = cluster->Children[idx]->TotalBounds; \
@@ -184,7 +181,7 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCal
DRAW_CLUSTER(3);
#undef DRAW_CLUSTER
}
- else
+ //else // Minor clusters can be subdivided and contain instances
{
// Draw visible instances
const auto frame = Engine::FrameCount;
@@ -311,9 +308,6 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::D
// Draw visible children
if (cluster->Children[0])
{
- // Don't store instances in non-leaf nodes
- ASSERT_LOW_LAYER(cluster->Instances.IsEmpty());
-
BoundingBox box;
#define DRAW_CLUSTER(idx) \
box = cluster->Children[idx]->TotalBounds; \
@@ -327,7 +321,7 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::D
DRAW_CLUSTER(3);
#undef DRAW_CLUSTER
}
- else
+ //else // Minor clusters can be subdivided and contain instances
{
// Draw visible instances
const auto frame = Engine::FrameCount;
@@ -390,7 +384,7 @@ void Foliage::DrawClusterGlobalSDF(class GlobalSignDistanceFieldPass* globalSDF,
DRAW_CLUSTER(3);
#undef DRAW_CLUSTER
}
- else
+ //else // Minor clusters can be subdivided and contain instances
{
// Draw visible instances
for (int32 i = 0; i < cluster->Instances.Count(); i++)
@@ -421,7 +415,7 @@ void Foliage::DrawClusterGlobalSA(GlobalSurfaceAtlasPass* globalSA, const Vector
DRAW_CLUSTER(3);
#undef DRAW_CLUSTER
}
- else
+ //else // Minor clusters can be subdivided and contain instances
{
// Draw visible instances
for (int32 i = 0; i < cluster->Instances.Count(); i++)
diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp
index 6aaee8d01..9a1935228 100644
--- a/Source/Engine/Graphics/GPUDevice.cpp
+++ b/Source/Engine/Graphics/GPUDevice.cpp
@@ -19,6 +19,7 @@
#include "Engine/Content/Assets/Material.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/SoftAssetReference.h"
+#include "Engine/Core/Collections/Sorting.h"
#include "Engine/Render2D/Render2D.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Engine/Engine.h"
@@ -511,7 +512,7 @@ void GPUDevice::DumpResourcesToLog() const
true, // CubeTexture
true, // VolumeTexture
true, // Buffer
- true, // Shader
+ false, // Shader
false, // PipelineState
false, // Descriptor
false, // Query
@@ -522,29 +523,90 @@ void GPUDevice::DumpResourcesToLog() const
const auto type = static_cast(typeIndex);
const auto printType = printTypes[typeIndex];
- output.AppendFormat(TEXT("Group: {0}s"), ScriptingEnum::ToString(type));
- output.AppendLine();
-
- int32 count = 0;
+ // Get resource sof a given type
uint64 memUsage = 0;
+ struct Resource
+ {
+ const GPUResource* Object;
+ uint64 MemoryUsage;
+
+ bool operator<(const Resource& other) const
+ {
+ if (MemoryUsage != other.MemoryUsage)
+ return MemoryUsage > other.MemoryUsage;
+#if GPU_ENABLE_RESOURCE_NAMING
+ return Object->GetName().Compare(other.Object->GetName()) > 0;
+#else
+ return (uintptr)Object < (uintptr)other.Object;
+#endif
+ }
+ };
+ Array resources;
for (int32 i = 0; i < _resources.Count(); i++)
{
const GPUResource* resource = _resources[i];
if (resource->GetResourceType() == type && resource->GetMemoryUsage() != 0)
{
- count++;
- memUsage += resource->GetMemoryUsage();
- auto str = resource->ToString();
- if (str.HasChars() && printType)
- {
- output.Append(TEXT('\t'));
- output.Append(str);
- output.AppendLine();
- }
+ resources.Add({ resource, resource->GetMemoryUsage() });
}
}
+ if (resources.IsEmpty())
+ continue;
+ output.AppendFormat(TEXT("> {0}:"), ScriptingEnum::ToString(type));
+ output.AppendLine();
- output.AppendFormat(TEXT("Total count: {0}, memory usage: {1}"), count, Utilities::BytesToText(memUsage));
+ // Sort them by size
+ Sorting::QuickSort(resources);
+
+ // Print resources
+ for (auto e : resources)
+ {
+ memUsage += e.MemoryUsage;
+ if (!printType)
+ continue;
+ output.Append(TEXT(" "));
+ output.Append(Utilities::BytesToText(e.MemoryUsage));
+ output.Append(TEXT(", "));
+ if (e.Object->Is())
+ {
+ auto texture = (GPUTexture*)e.Object;
+ auto& desc = texture->GetDescription();
+ output.AppendFormat(TEXT("Size: {}x{}x{}[{}], "), desc.Width, desc.Height, desc.Depth, desc.ArraySize);
+ if (texture->ResidentMipLevels() == desc.MipLevels)
+ output.AppendFormat(TEXT("Mips: {}, "), desc.MipLevels);
+ else
+ output.AppendFormat(TEXT("Mips: {}/{}, "), texture->ResidentMipLevels(), desc.MipLevels);
+#if GPU_ENABLE_RESOURCE_NAMING
+ auto name = texture->GetName();
+#else
+ StringView name;
+#endif
+ output.AppendFormat(TEXT("Format: {}, Flags: {}, {}"), ScriptingEnum::ToString(desc.Format), ScriptingEnum::ToStringFlags(desc.Flags), name);
+ }
+ else if (e.Object->Is())
+ {
+ auto buffer = (GPUBuffer*)e.Object;
+ auto& desc = buffer->GetDescription();
+ output.AppendFormat(TEXT("Stride: {} bytes, "), desc.Stride);
+ if (desc.Format != PixelFormat::Unknown)
+ output.AppendFormat(TEXT("Format: {}, "), ScriptingEnum::ToString(desc.Format));
+ if (desc.Usage != GPUResourceUsage::Default)
+ output.AppendFormat(TEXT("Usage: {}, "), ScriptingEnum::ToString(desc.Usage));
+#if GPU_ENABLE_RESOURCE_NAMING
+ auto name = buffer->GetName();
+#else
+ StringView name;
+#endif
+ output.AppendFormat(TEXT("Flags: {}, {}"), ScriptingEnum::ToStringFlags(desc.Flags), name);
+ }
+ else
+ {
+ output.Append(e.Object->ToString());
+ }
+ output.AppendLine();
+ }
+
+ output.AppendFormat(TEXT("Total count: {0}, memory usage: {1}"), resources.Count(), Utilities::BytesToText(memUsage));
output.AppendLine();
output.AppendLine();
}
@@ -553,6 +615,12 @@ void GPUDevice::DumpResourcesToLog() const
LOG_STR(Info, output.ToStringView());
}
+void GPUDevice::DumpResources()
+{
+ if (GPUDevice::Instance)
+ GPUDevice::Instance->DumpResourcesToLog();
+}
+
extern void ClearVertexLayoutCache();
void GPUDevice::preDispose()
diff --git a/Source/Engine/Graphics/GPUDevice.h b/Source/Engine/Graphics/GPUDevice.h
index b9f681b5c..e6df74886 100644
--- a/Source/Engine/Graphics/GPUDevice.h
+++ b/Source/Engine/Graphics/GPUDevice.h
@@ -389,6 +389,11 @@ public:
///
void DumpResourcesToLog() const;
+ ///
+ /// Dumps all GPU resources information to the log.
+ ///
+ API_FUNCTION(Attributes="DebugCommand") static void DumpResources();
+
protected:
virtual void preDispose();
diff --git a/Source/Engine/Graphics/Materials/MaterialInfo.cs b/Source/Engine/Graphics/Materials/MaterialInfo.cs
index a8ff82665..4ff56ea63 100644
--- a/Source/Engine/Graphics/Materials/MaterialInfo.cs
+++ b/Source/Engine/Graphics/Materials/MaterialInfo.cs
@@ -97,4 +97,13 @@ namespace FlaxEngine
}
}
}
+
+ partial class MaterialParameter
+ {
+ ///
+ public override string ToString()
+ {
+ return $"{ParameterType} {Name} = {Value?.ToString() ?? "null"}";
+ }
+ }
}
diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h
index f03266f19..6cfb28e88 100644
--- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h
+++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h
@@ -3,7 +3,6 @@
#pragma once
#include "MaterialShader.h"
-#include "Engine/Core/Math/Rectangle.h"
#include "Engine/Core/Types/Span.h"
#include "Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h"
#include "Engine/Renderer/GlobalSignDistanceFieldPass.h"
diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp
index bb0796234..4db4acda1 100644
--- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp
+++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp
@@ -150,21 +150,6 @@ bool TerrainMaterialShader::Load()
}
#endif
- // Support blending but then use only emissive channel
- switch (_info.BlendMode)
- {
- case MaterialBlendMode::Transparent:
- psDesc.BlendMode = BlendingMode::AlphaBlend;
- break;
- case MaterialBlendMode::Additive:
- psDesc.BlendMode = BlendingMode::Additive;
- break;
- case MaterialBlendMode::Multiply:
- psDesc.BlendMode = BlendingMode::Multiply;
- break;
- default: ;
- }
-
// GBuffer Pass
psDesc.VS = _shader->GetVS("VS");
psDesc.PS = _shader->GetPS("PS_GBuffer");
@@ -185,7 +170,6 @@ bool TerrainMaterialShader::Load()
// Depth Pass
psDesc.CullMode = CullMode::TwoSided;
- psDesc.BlendMode = BlendingMode::Opaque;
psDesc.DepthClipEnable = false;
psDesc.DepthWriteEnable = true;
psDesc.DepthEnable = true;
diff --git a/Source/Engine/Graphics/Models/ModelDraw.h b/Source/Engine/Graphics/Models/ModelDraw.h
new file mode 100644
index 000000000..2867499f3
--- /dev/null
+++ b/Source/Engine/Graphics/Models/ModelDraw.h
@@ -0,0 +1,130 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Engine/Engine.h"
+#include "Engine/Graphics/RenderTask.h"
+#include "Engine/Graphics/RenderTools.h"
+#include "Engine/Graphics/Materials/MaterialInfo.h"
+
+template
+FORCE_INLINE bool ModelDrawTransition(ModelType* model, const DrawInfoType& info)
+{
+ for (auto& e : *info.Buffer)
+ {
+ if (e.Material && EnumHasAllFlags(e.Material->GetInfo().FeaturesFlags, MaterialFeaturesFlags::DitheredLODTransition))
+ return true;
+ }
+ for (auto& e : model->MaterialSlots)
+ {
+ if (e.Material && EnumHasAllFlags(e.Material->GetInfo().FeaturesFlags, MaterialFeaturesFlags::DitheredLODTransition))
+ return true;
+ }
+ return false;
+}
+
+template
+FORCE_INLINE void ModelDraw(ModelType* model, const RenderContext& renderContext, const ContextType& context, const DrawInfoType& info)
+{
+ ASSERT(info.Buffer);
+ if (!model->CanBeRendered())
+ return;
+ if (!info.Buffer->IsValidFor(model))
+ info.Buffer->Setup(model);
+ const auto frame = Engine::FrameCount;
+ const auto modelFrame = info.DrawState->PrevFrame + 1;
+
+ // Select a proper LOD index (model may be culled)
+ int32 lodIndex;
+ if (info.ForcedLOD != -1)
+ {
+ lodIndex = info.ForcedLOD;
+ }
+ else
+ {
+ lodIndex = RenderTools::ComputeModelLOD(model, info.Bounds.Center, (float)info.Bounds.Radius, renderContext);
+ if (lodIndex == -1)
+ {
+ // Handling model fade-out transition
+ if (modelFrame == frame && info.DrawState->PrevLOD != -1 && !renderContext.View.IsSingleFrame && ModelDrawTransition(model, info))
+ {
+ // Check if start transition
+ if (info.DrawState->LODTransition == 255)
+ {
+ info.DrawState->LODTransition = 0;
+ }
+
+ RenderTools::UpdateModelLODTransition(info.DrawState->LODTransition);
+
+ // Check if end transition
+ if (info.DrawState->LODTransition == 255)
+ {
+ info.DrawState->PrevLOD = lodIndex;
+ }
+ else
+ {
+ const auto prevLOD = model->ClampLODIndex(info.DrawState->PrevLOD);
+ const float normalizedProgress = static_cast(info.DrawState->LODTransition) * (1.0f / 255.0f);
+ model->LODs.Get()[prevLOD].Draw(renderContext, info, normalizedProgress);
+ }
+ }
+
+ return;
+ }
+ }
+ lodIndex += info.LODBias + renderContext.View.ModelLODBias;
+ lodIndex = model->ClampLODIndex(lodIndex);
+
+ if (renderContext.View.IsSingleFrame)
+ {
+ }
+ // Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports)
+ else if (modelFrame == frame)
+ {
+ // Check if materials use transition
+ if (!ModelDrawTransition(model, info))
+ {
+ info.DrawState->PrevLOD = lodIndex;
+ info.DrawState->LODTransition = 255;
+ }
+
+ // Check if start transition
+ if (info.DrawState->PrevLOD != lodIndex && info.DrawState->LODTransition == 255)
+ {
+ info.DrawState->LODTransition = 0;
+ }
+
+ RenderTools::UpdateModelLODTransition(info.DrawState->LODTransition);
+
+ // Check if end transition
+ if (info.DrawState->LODTransition == 255)
+ {
+ info.DrawState->PrevLOD = lodIndex;
+ }
+ }
+ // Check if there was a gap between frames in drawing this model instance
+ else if (modelFrame < frame || info.DrawState->PrevLOD == -1)
+ {
+ // Reset state
+ info.DrawState->PrevLOD = lodIndex;
+ info.DrawState->LODTransition = 255;
+ }
+
+ // Draw
+ if (info.DrawState->PrevLOD == lodIndex || info.DrawState->LODTransition == 255 || renderContext.View.IsSingleFrame)
+ {
+ model->LODs.Get()[lodIndex].Draw(context, info, 0.0f);
+ }
+ else if (info.DrawState->PrevLOD == -1)
+ {
+ const float normalizedProgress = static_cast(info.DrawState->LODTransition) * (1.0f / 255.0f);
+ model->LODs.Get()[lodIndex].Draw(context, info, 1.0f - normalizedProgress);
+ }
+ else
+ {
+ const auto prevLOD = model->ClampLODIndex(info.DrawState->PrevLOD);
+ const float normalizedProgress = static_cast(info.DrawState->LODTransition) * (1.0f / 255.0f);
+ model->LODs.Get()[prevLOD].Draw(context, info, normalizedProgress);
+ model->LODs.Get()[lodIndex].Draw(context, info, normalizedProgress - 1.0f);
+ }
+}
diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp
index 0a98f1401..5a7ca9079 100644
--- a/Source/Engine/Graphics/RenderTools.cpp
+++ b/Source/Engine/Graphics/RenderTools.cpp
@@ -461,7 +461,7 @@ int32 RenderTools::ComputeModelLOD(const Model* model, const Float3& origin, flo
return 0;
}
-int32 RenderTools::ComputeSkinnedModelLOD(const SkinnedModel* model, const Float3& origin, float radius, const RenderContext& renderContext)
+int32 RenderTools::ComputeModelLOD(const SkinnedModel* model, const Float3& origin, float radius, const RenderContext& renderContext)
{
const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View);
const float screenRadiusSquared = ComputeBoundsScreenRadiusSquared(origin, radius, *lodView) * renderContext.View.ModelLODDistanceFactorSqrt;
@@ -486,6 +486,11 @@ int32 RenderTools::ComputeSkinnedModelLOD(const SkinnedModel* model, const Float
return 0;
}
+int32 RenderTools::ComputeSkinnedModelLOD(const SkinnedModel* model, const Float3& origin, float radius, const RenderContext& renderContext)
+{
+ return ComputeModelLOD(model, origin, radius, renderContext);
+}
+
void RenderTools::ComputeCascadeUpdateFrequency(int32 cascadeIndex, int32 cascadeCount, int32& updateFrequency, int32& updatePhrase, int32 updateMaxCountPerFrame)
{
switch (updateMaxCountPerFrame)
diff --git a/Source/Engine/Graphics/RenderTools.h b/Source/Engine/Graphics/RenderTools.h
index 7be73c912..8c565eed5 100644
--- a/Source/Engine/Graphics/RenderTools.h
+++ b/Source/Engine/Graphics/RenderTools.h
@@ -109,15 +109,26 @@ public:
/// The zero-based LOD index. Returns -1 if model should not be rendered.
API_FUNCTION() static int32 ComputeModelLOD(const Model* model, API_PARAM(Ref) const Float3& origin, float radius, API_PARAM(Ref) const RenderContext& renderContext);
+ ///
+ /// Computes the model LOD index to use during rendering.
+ ///
+ /// The model.
+ /// The bounds origin.
+ /// The bounds radius.
+ /// The rendering context.
+ /// The zero-based LOD index. Returns -1 if model should not be rendered.
+ API_FUNCTION() static int32 ComputeModelLOD(const SkinnedModel* model, API_PARAM(Ref) const Float3& origin, float radius, API_PARAM(Ref) const RenderContext& renderContext);
+
///
/// Computes the skinned model LOD index to use during rendering.
+ /// [Deprecated in v1.12]
///
/// The skinned model.
/// The bounds origin.
/// The bounds radius.
/// The rendering context.
/// The zero-based LOD index. Returns -1 if model should not be rendered.
- API_FUNCTION() static int32 ComputeSkinnedModelLOD(const SkinnedModel* model, API_PARAM(Ref) const Float3& origin, float radius, API_PARAM(Ref) const RenderContext& renderContext);
+ API_FUNCTION() DEPRECATED("Use ComputeModelLOD instead.") static int32 ComputeSkinnedModelLOD(const SkinnedModel* model, API_PARAM(Ref) const Float3& origin, float radius, API_PARAM(Ref) const RenderContext& renderContext);
///
/// Computes the sorting key for depth value (quantized)
diff --git a/Source/Engine/Graphics/Textures/TextureBase.cpp b/Source/Engine/Graphics/Textures/TextureBase.cpp
index b5f020611..ddd0498bc 100644
--- a/Source/Engine/Graphics/Textures/TextureBase.cpp
+++ b/Source/Engine/Graphics/Textures/TextureBase.cpp
@@ -302,6 +302,12 @@ bool TextureBase::HasStreamingError() const
return _texture.Streaming.Error;
}
+void TextureBase::SetStreamingVisible() const
+{
+ if (_texture.GetTexture())
+ _texture.GetTexture()->LastRenderTime = Platform::GetTimeSeconds();
+}
+
BytesContainer TextureBase::GetMipData(int32 mipIndex, int32& rowPitch, int32& slicePitch)
{
BytesContainer result;
diff --git a/Source/Engine/Graphics/Textures/TextureBase.h b/Source/Engine/Graphics/Textures/TextureBase.h
index b8ae267c8..a461a1018 100644
--- a/Source/Engine/Graphics/Textures/TextureBase.h
+++ b/Source/Engine/Graphics/Textures/TextureBase.h
@@ -153,6 +153,11 @@ public:
///
API_PROPERTY() bool HasStreamingError() const;
+ ///
+ /// Sets the texture as visible this frame to inform streaming about usage which will stream boost its priority for the streaming.
+ ///
+ API_FUNCTION() void SetStreamingVisible() const;
+
public:
///
/// Gets the mip data.
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
index 760662a93..112a64bf3 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
@@ -2257,6 +2257,7 @@ bool FenceManagerVulkan::WaitForFence(FenceVulkan* fence, float timeoutSeconds)
fence->IsSignaled = true;
return false;
}
+ LOG(Warning, "vkWaitForFences failed with timeout: {}s", timeoutSeconds);
return true;
}
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.cpp
index 4eb807f18..2b5a2d6ac 100644
--- a/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.cpp
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.cpp
@@ -321,17 +321,19 @@ bool GPUDeviceWebGPU::Init()
FormatSupport::Buffer |
FormatSupport::InputAssemblyIndexBuffer |
FormatSupport::InputAssemblyVertexBuffer;
- auto supportsTexture =
+ auto supportsTextureUnfilterable =
FormatSupport::Texture1D |
FormatSupport::Texture2D |
FormatSupport::Texture3D |
FormatSupport::TextureCube |
FormatSupport::ShaderLoad |
+ FormatSupport::Mip;
+ auto supportsTexture =
+ supportsTextureUnfilterable |
FormatSupport::ShaderSample |
FormatSupport::ShaderSampleComparison |
FormatSupport::ShaderGather |
- FormatSupport::ShaderGatherComparison |
- FormatSupport::Mip;
+ FormatSupport::ShaderGatherComparison;
auto supportsRender =
FormatSupport::RenderTarget |
FormatSupport::Blendable;
@@ -414,18 +416,18 @@ bool GPUDeviceWebGPU::Init()
FeaturesPerFormat[(int32)PixelFormat::R8_SNorm].Support |= supportsBuffer | supportsTexture | supportsBasicStorage;
FeaturesPerFormat[(int32)PixelFormat::R8_UInt].Support |= supportsBasicStorage;
FeaturesPerFormat[(int32)PixelFormat::R8_SInt].Support |= supportsBasicStorage;
- FeaturesPerFormat[(int32)PixelFormat::R16_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA | supportsBasicStorage;
- FeaturesPerFormat[(int32)PixelFormat::R16_SNorm].Support |= supportsBuffer | supportsTexture | supportsBasicStorage;
+ FeaturesPerFormat[(int32)PixelFormat::R16_UNorm].Support |= supportsBuffer | supportsTextureUnfilterable | supportsRender | supportsMSAA | supportsBasicStorage;
+ FeaturesPerFormat[(int32)PixelFormat::R16_SNorm].Support |= supportsBuffer | supportsTextureUnfilterable | supportsBasicStorage;
//FeaturesPerFormat[(int32)PixelFormat::R16_UInt].Support |= supportsBasicStorage; // TODO: fix issues with particle indices buffer that could use it
FeaturesPerFormat[(int32)PixelFormat::R16_SInt].Support |= supportsBasicStorage;
FeaturesPerFormat[(int32)PixelFormat::R16_Float].Support |= supportsBasicStorage;
- FeaturesPerFormat[(int32)PixelFormat::R16G16_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMultisampling | supportsBasicStorage;
- FeaturesPerFormat[(int32)PixelFormat::R16G16_SNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMultisampling | supportsBasicStorage;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16_UNorm].Support |= supportsBuffer | supportsTextureUnfilterable | supportsRender | supportsMultisampling | supportsBasicStorage;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16_SNorm].Support |= supportsBuffer | supportsTextureUnfilterable | supportsRender | supportsMultisampling | supportsBasicStorage;
FeaturesPerFormat[(int32)PixelFormat::R16G16_UInt].Support |= supportsBasicStorage;
FeaturesPerFormat[(int32)PixelFormat::R16G16_SInt].Support |= supportsBasicStorage;
FeaturesPerFormat[(int32)PixelFormat::R16G16_Float].Support |= supportsBasicStorage;
- FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMultisampling | supportsBasicStorage;
- FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_SNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMultisampling | supportsBasicStorage;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_UNorm].Support |= supportsBuffer | supportsTextureUnfilterable | supportsRender | supportsMultisampling | supportsBasicStorage;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_SNorm].Support |= supportsBuffer | supportsTextureUnfilterable | supportsRender | supportsMultisampling | supportsBasicStorage;
FeaturesPerFormat[(int32)PixelFormat::R10G10B10A2_UNorm].Support |= supportsBasicStorage;
FeaturesPerFormat[(int32)PixelFormat::R11G11B10_Float].Support |= supportsBasicStorage;
}
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GraphicsDeviceWebGPU.Build.cs b/Source/Engine/GraphicsDevice/WebGPU/GraphicsDeviceWebGPU.Build.cs
index 0a56770a4..59c241664 100644
--- a/Source/Engine/GraphicsDevice/WebGPU/GraphicsDeviceWebGPU.Build.cs
+++ b/Source/Engine/GraphicsDevice/WebGPU/GraphicsDeviceWebGPU.Build.cs
@@ -1,5 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved.
+using System;
using System.IO;
using Flax.Build.NativeCpp;
using Flax.Build.Platforms;
@@ -23,6 +24,9 @@ public class GraphicsDeviceWebGPU : GraphicsDeviceBaseModule
{
base.Setup(options);
+ if (!EmscriptenSdk.Instance.IsValid)
+ throw new Exception("Cannot build WebGPU for Web without Emscripten SDK. Check environment variable 'EMSDK'.");
+
var port = "--use-port=emdawnwebgpu:cpp_bindings=false";
options.OutputFiles.Add(port);
options.CompileEnv.CustomArgs.Add(port);
diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp
index 1f441b6b0..ce106dc83 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.cpp
+++ b/Source/Engine/Level/Actors/AnimatedModel.cpp
@@ -1332,6 +1332,11 @@ MaterialBase* AnimatedModel::GetMaterial(int32 entryIndex)
return material;
}
+ModelBase* AnimatedModel::GetModel()
+{
+ return SkinnedModel.Get();
+}
+
bool AnimatedModel::IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal)
{
auto model = SkinnedModel.Get();
diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h
index f9182d115..5e549e4a8 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.h
+++ b/Source/Engine/Level/Actors/AnimatedModel.h
@@ -476,6 +476,7 @@ public:
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
const Span GetMaterialSlots() const override;
MaterialBase* GetMaterial(int32 entryIndex) override;
+ ModelBase* GetModel() override;
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;
bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const override;
diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h
index d553d132a..05b6c7f91 100644
--- a/Source/Engine/Level/Actors/ModelInstanceActor.h
+++ b/Source/Engine/Level/Actors/ModelInstanceActor.h
@@ -66,6 +66,11 @@ public:
/// The material slot entry index.
API_FUNCTION(Sealed) virtual MaterialBase* GetMaterial(int32 entryIndex) = 0;
+ ///
+ /// Gets the model (base class).
+ ///
+ API_FUNCTION(Sealed) virtual ModelBase* GetModel() = 0;
+
///
/// Sets the material to the entry slot. Can be used to override the material of the meshes using this slot.
///
diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp
index 42b2dc620..0da4a3d70 100644
--- a/Source/Engine/Level/Actors/SplineModel.cpp
+++ b/Source/Engine/Level/Actors/SplineModel.cpp
@@ -367,6 +367,11 @@ MaterialBase* SplineModel::GetMaterial(int32 entryIndex)
return material;
}
+ModelBase* SplineModel::GetModel()
+{
+ return Model.Get();
+}
+
void SplineModel::UpdateBounds()
{
OnSplineUpdated();
diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h
index a63d4f721..a9b105a8e 100644
--- a/Source/Engine/Level/Actors/SplineModel.h
+++ b/Source/Engine/Level/Actors/SplineModel.h
@@ -117,6 +117,7 @@ public:
void OnParentChanged() override;
const Span GetMaterialSlots() const override;
MaterialBase* GetMaterial(int32 entryIndex) override;
+ ModelBase* GetModel() override;
void UpdateBounds() override;
protected:
diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp
index fad068bf0..1fba1ba2e 100644
--- a/Source/Engine/Level/Actors/StaticModel.cpp
+++ b/Source/Engine/Level/Actors/StaticModel.cpp
@@ -599,6 +599,11 @@ MaterialBase* StaticModel::GetMaterial(int32 entryIndex)
return material;
}
+ModelBase* StaticModel::GetModel()
+{
+ return Model.Get();
+}
+
bool StaticModel::IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal)
{
auto model = Model.Get();
diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h
index 063638e14..3a8c391f6 100644
--- a/Source/Engine/Level/Actors/StaticModel.h
+++ b/Source/Engine/Level/Actors/StaticModel.h
@@ -178,6 +178,7 @@ public:
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
const Span GetMaterialSlots() const override;
MaterialBase* GetMaterial(int32 entryIndex) override;
+ ModelBase* GetModel() override;
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;
bool GetMeshData(const MeshReference& ref, MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const override;
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp
index bb1e977e4..f5e53503c 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp
@@ -12,6 +12,7 @@
#include "Engine/Core/Math/OrientedBoundingBox.h"
#include "Engine/Utilities/Noise.h"
#include "Engine/Debug/DebugDraw.h"
+#include "Engine/Engine/Units.h"
// ReSharper disable CppCStyleCast
// ReSharper disable CppClangTidyClangDiagnosticCastAlign
@@ -1485,7 +1486,9 @@ void ParticleEmitterGraphCPUExecutor::DebugDrawModule(ParticleEmitterGraphCPUNod
if (node->UsePerParticleDataResolve())
return;
- const Color color = Color::White;
+ const Color colorPosition = Color::White;
+ const Color colorCollision = Color::GreenYellow;
+ const Color colorCollisionFill = colorCollision.AlphaMultiplied(0.1f);
switch (node->TypeID)
{
case 202: // Position (sphere surface)
@@ -1493,7 +1496,7 @@ void ParticleEmitterGraphCPUExecutor::DebugDrawModule(ParticleEmitterGraphCPUNod
{
const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
const float radius = (float)GetValue(node->GetBox(1), 3);
- DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(center, radius), color, 0.0f, true);
+ DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(center, radius), colorPosition, 0.0f, true);
break;
}
case 203: // Position (plane)
@@ -1503,7 +1506,7 @@ void ParticleEmitterGraphCPUExecutor::DebugDrawModule(ParticleEmitterGraphCPUNod
const Float3 halfExtent = Float3(size.X * 0.5f, 0.0f, size.Y * 0.5f);
OrientedBoundingBox box(halfExtent, Transform(center));
box.Transform(transform);
- DEBUG_DRAW_WIRE_BOX(box, color, 0.0f, true);
+ DEBUG_DRAW_WIRE_BOX(box, colorPosition, 0.0f, true);
break;
}
case 204: // Position (circle)
@@ -1511,7 +1514,7 @@ void ParticleEmitterGraphCPUExecutor::DebugDrawModule(ParticleEmitterGraphCPUNod
{
const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
const float radius = (float)GetValue(node->GetBox(1), 3);
- DEBUG_DRAW_WIRE_CYLINDER(center, transform.Orientation * Quaternion::Euler(90, 0, 0), radius, 0.0f, color, 0.0f, true);
+ DEBUG_DRAW_WIRE_CYLINDER(center, transform.Orientation * Quaternion::Euler(90, 0, 0), radius, 0.0f, colorPosition, 0.0f, true);
break;
}
case 206: // Position (box surface)
@@ -1521,7 +1524,7 @@ void ParticleEmitterGraphCPUExecutor::DebugDrawModule(ParticleEmitterGraphCPUNod
const Float3 size = (Float3)GetValue(node->GetBox(1), 3);
OrientedBoundingBox box(size * 0.5f, Transform(center));
box.Transform(transform);
- DEBUG_DRAW_WIRE_BOX(box, color, 0.0f, true);
+ DEBUG_DRAW_WIRE_BOX(box, colorPosition, 0.0f, true);
break;
}
// Position (cylinder)
@@ -1530,7 +1533,7 @@ void ParticleEmitterGraphCPUExecutor::DebugDrawModule(ParticleEmitterGraphCPUNod
const float height = (float)GetValue(node->GetBox(2), 4);
const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2) + Float3(0, 0, height * 0.5f));
const float radius = (float)GetValue(node->GetBox(1), 3);
- DEBUG_DRAW_WIRE_CYLINDER(center, transform.Orientation * Quaternion::Euler(90, 0, 0), radius, height, color, 0.0f, true);
+ DEBUG_DRAW_WIRE_CYLINDER(center, transform.Orientation * Quaternion::Euler(90, 0, 0), radius, height, colorPosition, 0.0f, true);
break;
}
// Position (line)
@@ -1538,7 +1541,7 @@ void ParticleEmitterGraphCPUExecutor::DebugDrawModule(ParticleEmitterGraphCPUNod
{
const Float3 start = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
const Float3 end = transform.LocalToWorld((Float3)GetValue(node->GetBox(1), 3));
- DEBUG_DRAW_LINE(start, end, color, 0.0f, true);
+ DEBUG_DRAW_LINE(start, end, colorPosition, 0.0f, true);
break;
}
// Position (torus)
@@ -1547,15 +1550,54 @@ void ParticleEmitterGraphCPUExecutor::DebugDrawModule(ParticleEmitterGraphCPUNod
const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
const float radius = Math::Max((float)GetValue(node->GetBox(1), 3), ZeroTolerance);
const float thickness = (float)GetValue(node->GetBox(2), 4);
- DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(center, radius + thickness), color, 0.0f, true);
+ DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(center, radius + thickness), colorPosition, 0.0f, true);
break;
}
-
// Position (spiral)
case 214:
{
const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
- DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(center, 5.0f), color, 0.0f, true);
+ DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(center, 5.0f), colorPosition, 0.0f, true);
+ break;
+ }
+ // Collision (plane)
+ case 330:
+ {
+ const Float3 position = transform.LocalToWorld((Float3)GetValue(node->GetBox(5), 8));
+ //const Float3 normal = transform.Orientation * (Float3)GetValue(node->GetBox(6), 9) * ((bool)node->Values[2] ? -1.0f : 1.0f);
+ const Float3 extent(METERS_TO_UNITS(10), 0.001f, METERS_TO_UNITS(10));
+ DEBUG_DRAW_BOX(BoundingBox(position - extent, position + extent), colorCollisionFill, 0.0f, true);
+ DEBUG_DRAW_WIRE_BOX(BoundingBox(position - extent, position + extent), colorCollision, 0.0f, true);
+ break;
+ }
+ // Collision (sphere)
+ case 331:
+ {
+ const Float3 position = transform.LocalToWorld((Float3)GetValue(node->GetBox(5), 8));
+ const float radius = (float)GetValue(node->GetBox(6), 9);
+ DEBUG_DRAW_SPHERE(BoundingSphere(position, radius), colorCollisionFill, 0.0f, true);
+ DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(position, radius), colorCollision, 0.0f, true);
+ break;
+ }
+ // Collision (box)
+ case 332:
+ {
+ const Float3 center = (Float3)GetValue(node->GetBox(5), 8);
+ const Float3 size = (Float3)GetValue(node->GetBox(6), 9);
+ OrientedBoundingBox box(size * 0.5f, Transform(center));
+ box.Transform(transform);
+ DEBUG_DRAW_BOX(box, colorCollisionFill, 0.0f, true);
+ DEBUG_DRAW_WIRE_BOX(box, colorCollision, 0.0f, true);
+ break;
+ }
+ // Collision (cylinder)
+ case 333:
+ {
+ const float height = (float)GetValue(node->GetBox(6), 9);
+ const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(5), 8) + Float3(0, 0, height * 0.5f));
+ const float radius = (float)GetValue(node->GetBox(7), 10);
+ DEBUG_DRAW_CYLINDER(center, transform.Orientation * Quaternion::Euler(90, 0, 0), radius, height, colorCollisionFill, 0.0f, true);
+ DEBUG_DRAW_WIRE_CYLINDER(center, transform.Orientation * Quaternion::Euler(90, 0, 0), radius, height, colorCollision, 0.0f, true);
break;
}
}
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
index bdfdf1956..54b2ea0cc 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
@@ -437,6 +437,8 @@ void ParticleEmitterGraphCPUExecutor::DrawDebug(ParticleEmitter* emitter, Partic
DebugDrawModule(module, transform);
for (auto module : emitter->Graph.InitModules)
DebugDrawModule(module, transform);
+ for (auto module : emitter->Graph.UpdateModules)
+ DebugDrawModule(module, transform);
}
#endif
diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp
index 63c59ff70..b2e6e8b75 100644
--- a/Source/Engine/Render2D/Font.cpp
+++ b/Source/Engine/Render2D/Font.cpp
@@ -52,6 +52,10 @@ void Font::GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback)
for (int32 fallbackIndex = 0; fallbackIndex < FallbackFonts.Count(); fallbackIndex++)
{
FontAsset* fallbackFont = FallbackFonts.Get()[fallbackIndex].Get();
+ if (fallbackFont && _asset->GetOptions().RasterMode == FontRasterMode::MSDF)
+ {
+ fallbackFont = fallbackFont->GetMSDF();
+ }
if (fallbackFont && fallbackFont->ContainsChar(c))
{
fallbackFont->CreateFont(GetSize())->GetCharacter(c, result, enableFallback);
diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp
index 813eb0c66..34ffe7330 100644
--- a/Source/Engine/Render2D/FontAsset.cpp
+++ b/Source/Engine/Render2D/FontAsset.cpp
@@ -69,6 +69,7 @@ void FontAsset::unload(bool isReloading)
_fontFile.Release();
_virtualBold = nullptr;
_virtualItalic = nullptr;
+ _virtualMSDF = nullptr;
}
AssetChunksFlag FontAsset::getChunksToPreload() const
@@ -155,6 +156,22 @@ FontAsset* FontAsset::GetItalic()
return _virtualItalic;
}
+FontAsset* FontAsset::GetMSDF()
+{
+ ScopeLock lock(Locker);
+ if (_options.RasterMode == FontRasterMode::MSDF)
+ return this;
+ if (!_virtualMSDF)
+ {
+ _virtualMSDF = Content::CreateVirtualAsset();
+ _virtualMSDF->Init(_fontFile);
+ auto options = _options;
+ options.RasterMode = FontRasterMode::MSDF;
+ _virtualMSDF->SetOptions(options);
+ }
+ return _virtualMSDF;
+}
+
bool FontAsset::Init(const BytesContainer& fontFile)
{
ScopeLock lock(Locker);
diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h
index 56cc3d655..c3aa141a8 100644
--- a/Source/Engine/Render2D/FontAsset.h
+++ b/Source/Engine/Render2D/FontAsset.h
@@ -122,6 +122,7 @@ private:
Array> _fonts;
AssetReference _virtualBold;
AssetReference _virtualItalic;
+ AssetReference _virtualMSDF;
public:
///
@@ -180,6 +181,12 @@ public:
/// The virtual font or this.
API_FUNCTION() FontAsset* GetItalic();
+ ///
+ /// Gets the MSDF version of the font. Returns itself or creates a new virtual font asset using this font but rasterized with Multi-channel Signed Distance Field (MSDF).
+ ///
+ /// The virtual font or this.
+ API_FUNCTION() FontAsset* GetMSDF();
+
///
/// Initializes the font with a custom font file data.
///
diff --git a/Source/Engine/Renderer/AmbientOcclusionPass.cpp b/Source/Engine/Renderer/AmbientOcclusionPass.cpp
index 07a22e8f0..341270a61 100644
--- a/Source/Engine/Renderer/AmbientOcclusionPass.cpp
+++ b/Source/Engine/Renderer/AmbientOcclusionPass.cpp
@@ -98,7 +98,7 @@ bool AmbientOcclusionPass::Init()
_psNonSmartBlur = GPUDevice::Instance->CreatePipelineState();
_psApply = GPUDevice::Instance->CreatePipelineState();
_psApplyHalf = GPUDevice::Instance->CreatePipelineState();
- _depthBounds = GPUDevice::Instance->Limits.HasDepthBounds;
+ _depthBounds = GPUDevice::Instance->Limits.HasDepthBounds && GPUDevice::Instance->Limits.HasReadOnlyDepth;
// Load shader
_shader = Content::LoadAsyncInternal(TEXT("Shaders/SSAO"));
diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp
index 7c7ea48d2..206087303 100644
--- a/Source/Engine/Renderer/PostProcessingPass.cpp
+++ b/Source/Engine/Renderer/PostProcessingPass.cpp
@@ -546,7 +546,10 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input,
// - 5 - LensStar - lens star texture
// - 7 - ColorGradingLUT
context->BindSR(0, input->View());
- context->BindSR(4, GetCustomOrDefault(settings.LensFlares.LensDirt, _defaultLensDirt, TEXT("Engine/Textures/DefaultLensDirt")));
+ if ((useLensFlares || useBloom) && settings.LensFlares.LensDirtIntensity > 0)
+ context->BindSR(4, GetCustomOrDefault(settings.LensFlares.LensDirt, _defaultLensDirt, TEXT("Engine/Textures/DefaultLensDirt")));
+ else
+ context->BindSR(4, device->GetDefaultBlackTexture());
context->BindSR(7, colorGradingLutView);
// Composite final frame during single pass (done in full resolution)
diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.h b/Source/Engine/Scripting/ManagedCLR/MAssembly.h
index 0a785c06a..59d6d5360 100644
--- a/Source/Engine/Scripting/ManagedCLR/MAssembly.h
+++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.h
@@ -38,6 +38,7 @@ private:
mutable int32 _hasCachedClasses : 1;
mutable ClassesDictionary _classes;
+ mutable ClassesDictionary _typeClasses;
int32 _reloadCount;
StringAnsi _name;
@@ -234,6 +235,11 @@ public:
///
const ClassesDictionary& GetClasses() const;
+ ///
+ /// Gets the classes lookup cache that includes runtime-cached types. Non-stable to iterate over due to dynamic types caching.
+ ///
+ ClassesDictionary& GetTypeClasses() const;
+
private:
bool LoadCorlib();
bool LoadImage(const String& assemblyPath, const StringView& nativePath);
diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp
index 6fa499002..5f20b3881 100644
--- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp
+++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp
@@ -142,7 +142,8 @@ void MAssembly::Unload(bool isReloading)
_isLoaded = false;
_hasCachedClasses = false;
#if USE_NETCORE
- ArenaAllocator::ClearDelete(_classes);
+ ArenaAllocator::ClearDelete(_typeClasses);
+ _classes.Clear();
#else
_classes.ClearDelete();
#endif
diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp
index 65dd09b44..0c1901e4b 100644
--- a/Source/Engine/Scripting/Runtime/DotNet.cpp
+++ b/Source/Engine/Scripting/Runtime/DotNet.cpp
@@ -355,7 +355,7 @@ void MCore::UnloadScriptingAssemblyLoadContext()
MAssembly* a = e.Value;
if (!a->IsLoaded() || !a->_hasCachedClasses)
continue;
- for (const auto& q : a->GetClasses())
+ for (const auto& q : a->GetTypeClasses())
{
MClass* c = q.Value;
c->_hasCachedAttributes = false;
@@ -781,6 +781,7 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const
MCore::GC::FreeMemory((void*)managedClasses[i].fullname);
MCore::GC::FreeMemory((void*)managedClasses[i].namespace_);
}
+ _typeClasses = _classes;
static void* RegisterManagedClassNativePointersPtr = GetStaticMethodPointer(TEXT("RegisterManagedClassNativePointers"));
CallStaticMethod(RegisterManagedClassNativePointersPtr, &managedClasses, classCount);
@@ -799,6 +800,12 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const
return _classes;
}
+MAssembly::ClassesDictionary& MAssembly::GetTypeClasses() const
+{
+ GetClasses();
+ return _typeClasses;
+}
+
void GetAssemblyName(void* assemblyHandle, StringAnsi& name, StringAnsi& fullname)
{
static void* GetAssemblyNamePtr = GetStaticMethodPointer(TEXT("GetAssemblyName"));
@@ -828,7 +835,7 @@ DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* man
MClass* klass = assembly->Memory.New(assembly, managedClass->typeHandle, managedClass->name, managedClass->fullname, managedClass->namespace_, managedClass->typeAttributes);
if (assembly != nullptr)
{
- auto& classes = const_cast(assembly->GetClasses());
+ auto& classes = assembly->GetTypeClasses();
MClass* oldKlass;
if (classes.TryGet(klass->GetFullName(), oldKlass))
{
@@ -1746,7 +1753,7 @@ MClass* GetOrCreateClass(MType* typeHandle)
klass = assembly->Memory.New(assembly, classInfo.typeHandle, classInfo.name, classInfo.fullname, classInfo.namespace_, classInfo.typeAttributes);
if (assembly != nullptr)
{
- auto& classes = const_cast(assembly->GetClasses());
+ auto& classes = assembly->GetTypeClasses();
if (classes.ContainsKey(klass->GetFullName()))
{
LOG(Warning, "Class '{0}' was already added to assembly '{1}'", String(klass->GetFullName()), String(assembly->GetName()));
diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp
index 1449d405f..2c924a874 100644
--- a/Source/Engine/Scripting/Runtime/Mono.cpp
+++ b/Source/Engine/Scripting/Runtime/Mono.cpp
@@ -1091,6 +1091,11 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const
return _classes;
}
+MAssembly::ClassesDictionary& MAssembly::GetTypeClasses() const
+{
+ return const_cast(GetClasses());
+}
+
bool MAssembly::Load(MonoImage* monoImage)
{
if (IsLoaded())
diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp
index 178e98444..1782134d9 100644
--- a/Source/Engine/Scripting/Runtime/None.cpp
+++ b/Source/Engine/Scripting/Runtime/None.cpp
@@ -303,6 +303,11 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const
return _classes;
}
+MAssembly::ClassesDictionary& MAssembly::GetTypeClasses() const
+{
+ return const_cast(GetClasses());
+}
+
bool MAssembly::LoadCorlib()
{
return false;
diff --git a/Source/Engine/Tools/AudioTool/AudioTool.Build.cs b/Source/Engine/Tools/AudioTool/AudioTool.Build.cs
index 88999826d..e2b1abc48 100644
--- a/Source/Engine/Tools/AudioTool/AudioTool.Build.cs
+++ b/Source/Engine/Tools/AudioTool/AudioTool.Build.cs
@@ -1,5 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved.
+using System.IO;
using System.Collections.Generic;
using Flax.Build;
using Flax.Build.NativeCpp;
@@ -26,5 +27,6 @@ public class AudioTool : EngineModule
///
public override void GetFilesToDeploy(List files)
{
+ files.Add(Path.Combine(FolderPath, "AudioTool.h"));
}
}
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.cpp b/Source/Engine/Tools/TextureTool/TextureTool.cpp
index d42a8bb59..4cb807c54 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.cpp
+++ b/Source/Engine/Tools/TextureTool/TextureTool.cpp
@@ -415,31 +415,53 @@ bool TextureTool::UpdateTexture(GPUContext* context, GPUTexture* texture, int32
if (textureFormat != dataFormat)
{
PROFILE_CPU_NAMED("ConvertTexture");
- auto dataSampler = PixelFormatSampler::Get(dataFormat);
- auto textureSampler = PixelFormatSampler::Get(textureFormat);
- if (!dataSampler || !textureSampler)
- return true;
int32 mipWidth, mipHeight, mipDepth;
texture->GetMipSize(mipIndex, mipWidth, mipHeight, mipDepth);
- auto tempRowPitch = mipWidth * textureSampler->PixelSize;
- auto tempSlicePitch = tempRowPitch * mipHeight;
- tempData.Resize(tempSlicePitch * mipDepth);
-
- ASSERT(data.Length() / rowPitch >= (uint32)mipHeight);
- for (int32 y = 0; y < mipHeight; y++)
+ auto dataSampler = PixelFormatSampler::Get(dataFormat);
+ auto textureSampler = PixelFormatSampler::Get(textureFormat);
+ if (dataSampler && textureSampler)
{
- for (int32 x = 0; x < mipWidth; x++)
+ // Conversion with an in-built samplers
+ auto tempRowPitch = mipWidth * textureSampler->PixelSize;
+ auto tempSlicePitch = tempRowPitch * mipHeight;
+ tempData.Resize(tempSlicePitch * mipDepth);
+ ASSERT(data.Length() / rowPitch >= (uint32)mipHeight);
+ for (int32 y = 0; y < mipHeight; y++)
{
- Color color = dataSampler->SamplePoint(data.Get(), x, y, rowPitch);
- textureSampler->Store(tempData.Get(), x, y, tempRowPitch, color);
+ for (int32 x = 0; x < mipWidth; x++)
+ {
+ Color color = dataSampler->SamplePoint(data.Get(), x, y, rowPitch);
+ textureSampler->Store(tempData.Get(), x, y, tempRowPitch, color);
+ }
}
+ data = ToSpan(tempData);
+ rowPitch = tempRowPitch;
+ slicePitch = tempSlicePitch;
+ }
+ else
+ {
+ // Conversion with external library
+ TextureData src, dst;
+ src.Width = mipWidth;
+ src.Height = mipHeight;
+ src.Depth = mipDepth;
+ src.Format = dataFormat;
+ auto& srcItem = src.Items.AddOne();
+ auto& srcMip = srcItem.Mips.AddOne();
+ srcMip.RowPitch = rowPitch;
+ srcMip.DepthPitch = slicePitch;
+ srcMip.Lines = slicePitch / rowPitch;
+ srcMip.Data.Link(data);
+ if (Convert(dst, src, textureFormat))
+ return true;
+ auto& dstMip = dst.Items[0].Mips[0];
+ tempData.Set(dstMip.Data.Get(), dstMip.Data.Length());
+ data = ToSpan(tempData);
+ rowPitch = dstMip.RowPitch;
+ slicePitch = dstMip.DepthPitch;
}
-
- data = ToSpan(tempData);
- rowPitch = tempRowPitch;
- slicePitch = tempSlicePitch;
}
// Update texture
diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs
index 7722fc94b..ff94a73f0 100644
--- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs
+++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs
@@ -379,6 +379,9 @@ namespace FlaxEngine.GUI
var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : GetCharPosition(selection.StartIndex, out _);
var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : GetCharPosition(selection.EndIndex, out _);
float height = font.Height;
+#if PLATFORM_MAC && !PLATFORM_SDL
+ height /= (float)Platform.Dpi / 96.0f; // TODO: refactor DPI support on macOS to skip such hacks
+#endif
float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
alpha *= alpha;
Color selectionColor = Color.White * alpha;
diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs
index 3d7099db6..1346a8c2b 100644
--- a/Source/Engine/UI/GUI/Common/TextBox.cs
+++ b/Source/Engine/UI/GUI/Common/TextBox.cs
@@ -253,8 +253,11 @@ namespace FlaxEngine.GUI
{
var leftEdge = font.GetCharPosition(text, SelectionLeft, ref _layout);
var rightEdge = font.GetCharPosition(text, SelectionRight, ref _layout);
- var fontHeight = font.Height;
- var textHeight = fontHeight / DpiScale;
+ float fontHeight = font.Height;
+#if PLATFORM_MAC && !PLATFORM_SDL
+ fontHeight /= (float)Platform.Dpi / 96.0f; // TODO: refactor DPI support on macOS to skip such hacks
+#endif
+ float textHeight = fontHeight / DpiScale;
// Draw selection background
float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs
index 491fbcd94..415a3c9c8 100644
--- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs
+++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs
@@ -556,6 +556,9 @@ namespace FlaxEngine.GUI
{
const float caretWidth = 1.2f;
Float2 caretPos = GetCharPosition(CaretPosition, out var height);
+#if PLATFORM_MAC && !PLATFORM_SDL
+ height /= (float)Platform.Dpi / 96.0f; // TODO: refactor DPI support on macOS to skip such hacks
+#endif
return new Rectangle(
caretPos.X - (caretWidth * 0.5f),
caretPos.Y,
diff --git a/Source/Engine/Visject/Graph.h b/Source/Engine/Visject/Graph.h
index 19aa3e892..7a09e63d4 100644
--- a/Source/Engine/Visject/Graph.h
+++ b/Source/Engine/Visject/Graph.h
@@ -287,7 +287,11 @@ public:
{
int32 hintIndex = (int32)(intptr)box->Connections[k];
TmpConnectionHint hint = tmpHints[hintIndex];
- box->Connections[k] = hint.Node->GetBox(hint.BoxID);
+ Box* hintBox = hint.Node->TryGetBox(hint.BoxID);
+ if (hintBox == nullptr)
+ box->Connections.RemoveAtKeepOrder(k--);
+ else
+ box->Connections[k] = hintBox;
}
}
}
diff --git a/Source/ThirdParty/FidelityFX/FidelityFX.Build.cs b/Source/ThirdParty/FidelityFX/FidelityFX.Build.cs
index 2057aad9b..a180a27d9 100644
--- a/Source/ThirdParty/FidelityFX/FidelityFX.Build.cs
+++ b/Source/ThirdParty/FidelityFX/FidelityFX.Build.cs
@@ -1,7 +1,8 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using Flax.Build;
-using Flax.Build.NativeCpp;
+using System.Collections.Generic;
+using System.IO;
///
/// https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK
@@ -19,4 +20,12 @@ public class FidelityFX : HeaderOnlyModule
// Merge third-party modules into engine binary
BinaryModuleName = "FlaxEngine";
}
+
+ ///
+ public override void GetFilesToDeploy(List files)
+ {
+ base.GetFilesToDeploy(files);
+
+ files.AddRange(Directory.GetFiles(FolderPath, "*.h", SearchOption.AllDirectories));
+ }
}
diff --git a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs
index 727483988..891c79b43 100644
--- a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs
+++ b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs
@@ -97,11 +97,22 @@ namespace Flax.Build.Bindings
UniqueFunctionNames = new HashSet();
int idx = 1;
functionInfo.UniqueName = functionInfo.Name;
- while (UniqueFunctionNames.Contains(functionInfo.UniqueName))
+ while (!IsUniqueFunctionName(this, functionInfo.UniqueName))
functionInfo.UniqueName = functionInfo.Name + idx++;
UniqueFunctionNames.Add(functionInfo.UniqueName);
}
+ private static bool IsUniqueFunctionName(VirtualClassInfo type, string name)
+ {
+ while (type != null)
+ {
+ while (type.UniqueFunctionNames.Contains(name))
+ return false;
+ type = type.BaseType as VirtualClassInfo;
+ }
+ return true;
+ }
+
public abstract int GetScriptVTableSize(out int offset);
public abstract int GetScriptVTableOffset(VirtualClassInfo classInfo);
diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs
index 164cd412f..4afb57bd1 100644
--- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs
+++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs
@@ -284,9 +284,17 @@ namespace Flax.Build
if (buildData.TargetOptions.NugetPackageReferences.Any())
{
var nugetPath = Utilities.GetNugetPackagesPath();
+ var restoreOnce = true;
foreach (var reference in buildOptions.NugetPackageReferences)
{
var path = reference.GetLibPath(nugetPath);
+ if (!File.Exists(path) && restoreOnce)
+ {
+ // Package binaries folder is missing so restore packages (incl. dependency packages)
+ RestoreNugetPackages(graph, buildOptions.Target, buildOptions);
+ restoreOnce = false;
+ path = reference.GetLibPath(nugetPath);
+ }
args.Add(string.Format("/reference:\"{0}\"", path));
}
}
diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
index 8251ff186..9cca0dfa6 100644
--- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
+++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
@@ -139,7 +139,9 @@ namespace Flax.Build.NativeCpp
{
if (libFolder == null)
libFolder = GetLibFolder(nugetPath);
- var dlls = Directory.GetFiles(libFolder, "*.dll", SearchOption.TopDirectoryOnly);
+ if (libFolder == string.Empty)
+ return string.Empty;
+ var dlls = Directory.Exists(libFolder) ? Directory.GetFiles(libFolder, "*.dll", SearchOption.TopDirectoryOnly) : [];
if (dlls.Length == 0)
{
Log.Error($"Missing NuGet package \"{Name}, {Version}, {Framework}\" binaries (folder: {libFolder})");
diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs
index b3ff862bb..8c1fb83d5 100644
--- a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs
+++ b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs
@@ -185,6 +185,15 @@ namespace Flax.Deploy
plist = plist.Replace("{Arch}", arch == TargetArchitecture.ARM64 ? "arm64" : "x86_64");
File.WriteAllText(Path.Combine(appContentsPath, "Info.plist"), plist, Encoding.ASCII);
+ // Codesign tint compiler executable and remove ones for Windows/Linux
+ var webPlatformFolder = Path.Combine(OutputPath, "Source/Platforms/Web");
+ if (Directory.Exists(webPlatformFolder))
+ {
+ Utilities.DirectoryDelete(Path.Combine(webPlatformFolder, "Binaries/Tools/Linux"));
+ Utilities.DirectoryDelete(Path.Combine(webPlatformFolder, "Binaries/Tools/Windows"));
+ CodeSign(Path.Combine(webPlatformFolder, "Binaries/Tools/Mac/ARM64/tint"));
+ }
+
// Copy output editor files
Utilities.DirectoryCopy(OutputPath, appContentsPath);
@@ -348,7 +357,8 @@ namespace Flax.Deploy
DeployFile(src, dst, "MoltenVK_icd.json");
DeployFiles(src, dst, "*.dll");
DeployFiles(src, dst, "*.dylib");
- DeployFile(src, dst, "Logo.png");
+ if (EngineConfiguration.UseSDL && MacConfiguration.UseSDL)
+ DeployFile(src, dst, "Logo.png");
// Optimize package size
Utilities.Run("strip", "FlaxEditor", null, dst, Utilities.RunOptions.None);