diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader
index 4eee27691..fe9223dce 100644
--- a/Content/Editor/MaterialTemplates/Surface.shader
+++ b/Content/Editor/MaterialTemplates/Surface.shader
@@ -142,9 +142,7 @@ GeometryData InterpolateGeometry(GeometryData p0, float w0, GeometryData p1, flo
{
GeometryData output = (GeometryData)0;
output.TexCoord = p0.TexCoord * w0 + p1.TexCoord * w1 + p2.TexCoord * w2;
-#if USE_LIGHTMAP
output.LightmapUV = p0.LightmapUV * w0 + p1.LightmapUV * w1 + p2.LightmapUV * w2;
-#endif
#if USE_VERTEX_COLOR
output.VertexColor = p0.VertexColor * w0 + p1.VertexColor * w1 + p2.VertexColor * w2;
#endif
@@ -581,7 +579,7 @@ VertexOutput VS_Skinned(ModelInput_Skinned input)
void ClipLODTransition(PixelInput input)
{
- float ditherFactor = input.InstanceParams.y;
+ float ditherFactor = input.Geometry.InstanceParams.y;
if (abs(ditherFactor) > 0.001)
{
float randGrid = cos(dot(floor(input.Position.xy), float2(347.83452793, 3343.28371863)));
diff --git a/Content/Editor/SpriteMaterial.flax b/Content/Editor/SpriteMaterial.flax
new file mode 100644
index 000000000..22f4f4ccd
--- /dev/null
+++ b/Content/Editor/SpriteMaterial.flax
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cb44c362605be7e468e9e5eeedb14e5d0445bbb5d3f33dd0a2fc2f0ea298ee4e
+size 31104
diff --git a/README.md b/README.md
index 7a664b7c4..1247aa5ab 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ Flax Visual Studio extension provides better programming workflow, C# scripts de
* Install Visual Studio 2015 or newer
* Install Windows 8.1 SDK or newer
-* Install Microsoft Visual C++ 2015.3 v140 toolset for desktop (x86, x64)
+* Install Microsoft Visual C++ 2015 v140 toolset or newer
* Clone repo (with LFS)
* Run **GenerateProjectFiles.bat**
* Open `Flax.sln` and set solution configuration to **Editor.Development** and solution platform to **Win64**
diff --git a/Source/Editor/Analytics/EditorAnalytics.cpp b/Source/Editor/Analytics/EditorAnalytics.cpp
index b3ab6d23d..3f067719e 100644
--- a/Source/Editor/Analytics/EditorAnalytics.cpp
+++ b/Source/Editor/Analytics/EditorAnalytics.cpp
@@ -8,6 +8,7 @@
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Types/DateTime.h"
#include "Editor/Editor.h"
+#include "Editor/ProjectInfo.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Utilities/StringConverter.h"
diff --git a/Source/Editor/Content/Proxy/CollisionDataProxy.cs b/Source/Editor/Content/Proxy/CollisionDataProxy.cs
index 91a573f1a..c23730375 100644
--- a/Source/Editor/Content/Proxy/CollisionDataProxy.cs
+++ b/Source/Editor/Content/Proxy/CollisionDataProxy.cs
@@ -46,24 +46,47 @@ namespace FlaxEditor.Content
/// Create collision data from model.
///
/// The associated model.
- public void CreateCollisionDataFromModel(Model model)
+ /// The action to call once the collision data gets created (or reused from existing).
+ public void CreateCollisionDataFromModel(Model model, Action created = null)
{
- Action created = contentItem =>
+ // Check if there already is collision data for that model to reuse
+ var modelItem = (AssetItem)Editor.Instance.ContentDatabase.Find(model.ID);
+ if (modelItem?.ParentFolder != null)
{
- var ai = (AssetItem)contentItem;
- var cd = FlaxEngine.Content.LoadAsync(ai.ID);
- if (cd == null || cd.WaitForLoaded())
+ foreach (var child in modelItem.ParentFolder.Children)
+ {
+ if (child is BinaryAssetItem b && b.IsOfType())
+ {
+ var collisionData = FlaxEngine.Content.Load(b.ID);
+ if (collisionData && collisionData.Options.Model == model.ID)
+ {
+ Editor.Instance.Windows.ContentWin.Select(b);
+ if (created != null)
+ FlaxEngine.Scripting.InvokeOnUpdate(() => created(collisionData));
+ return;
+ }
+ }
+ }
+ }
+
+ // Create new item so user can name it and then generate collision for it in async
+ Action create = contentItem =>
+ {
+ var assetItem = (AssetItem)contentItem;
+ var collisionData = FlaxEngine.Content.LoadAsync(assetItem.ID);
+ if (collisionData == null || collisionData.WaitForLoaded())
{
Editor.LogError("Failed to load created collision data.");
return;
}
-
Task.Run(() =>
{
- Editor.CookMeshCollision(ai.Path, CollisionDataType.TriangleMesh, model);
- });
+ Editor.CookMeshCollision(assetItem.Path, CollisionDataType.TriangleMesh, model);
+ if (created != null)
+ FlaxEngine.Scripting.InvokeOnUpdate(() => created(collisionData));
+ });
};
- Editor.Instance.Windows.ContentWin.NewItem(this, null, created);
+ Editor.Instance.Windows.ContentWin.NewItem(this, null, create);
}
}
}
diff --git a/Source/Editor/Content/Proxy/ModelProxy.cs b/Source/Editor/Content/Proxy/ModelProxy.cs
index 1c58139db..00caa1f07 100644
--- a/Source/Editor/Content/Proxy/ModelProxy.cs
+++ b/Source/Editor/Content/Proxy/ModelProxy.cs
@@ -45,11 +45,11 @@ namespace FlaxEditor.Content
{
base.OnContentWindowContextMenu(menu, item);
- menu.AddButton("Generate collision data", () =>
+ menu.AddButton("Create collision data", () =>
{
var model = FlaxEngine.Content.LoadAsync(((ModelAssetItem)item).ID);
- var cdProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy();
- cdProxy.CreateCollisionDataFromModel(model);
+ var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy();
+ collisionDataProxy.CreateCollisionDataFromModel(model);
});
}
diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
index 6a42450d5..fcc53f26a 100644
--- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
@@ -4,6 +4,7 @@
#include "AndroidPlatformTools.h"
#include "Editor/Editor.h"
+#include "Editor/ProjectInfo.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/Android/AndroidPlatformSettings.h"
diff --git a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
index dd7c77a94..91f22abae 100644
--- a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
+++ b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
@@ -11,6 +11,7 @@
#include "Engine/Serialization/JsonWriters.h"
#include "Editor/Cooker/PlatformTools.h"
#include "Editor/Editor.h"
+#include "Editor/ProjectInfo.h"
bool CompileScriptsStep::DeployBinaries(CookingData& data, const String& path, const String& projectFolderPath)
{
diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
index 96f8bfad2..f90658cab 100644
--- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp
+++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
@@ -97,6 +97,8 @@ bool DeployDataStep::Perform(CookingData& data)
data.AddRootEngineAsset(PRE_INTEGRATED_GF_ASSET_NAME);
data.AddRootEngineAsset(SMAA_AREA_TEX);
data.AddRootEngineAsset(SMAA_SEARCH_TEX);
+ if (data.Configuration != BuildConfiguration::Release)
+ data.AddRootEngineAsset(TEXT("Editor/Fonts/Roboto-Regular"));
// Register game assets
data.StepProgress(TEXT("Deploying game data"), 50);
diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs
index 347eb608f..c5c628755 100644
--- a/Source/Editor/CustomEditors/CustomEditor.cs
+++ b/Source/Editor/CustomEditors/CustomEditor.cs
@@ -520,15 +520,46 @@ namespace FlaxEditor.CustomEditors
try
{
string text;
- if (new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(Values.Type))
+ if (ParentEditor is Dedicated.ScriptsEditor)
{
+ // Script
+ text = JsonSerializer.Serialize(Values[0]);
+
+ // Remove properties that should be ignored when copy/pasting data
+ if (text == null)
+ text = string.Empty;
+ int idx = text.IndexOf("\"Actor\":");
+ if (idx != -1)
+ {
+ int endIdx = text.IndexOf("\n", idx);
+ if (endIdx != -1)
+ text = text.Remove(idx, endIdx - idx);
+ }
+ idx = text.IndexOf("\"Parent\":");
+ if (idx != -1)
+ {
+ int endIdx = text.IndexOf("\n", idx);
+ if (endIdx != -1)
+ text = text.Remove(idx, endIdx - idx);
+ }
+ idx = text.IndexOf("\"OrderInParent\":");
+ if (idx != -1)
+ {
+ int endIdx = text.IndexOf("\n", idx);
+ if (endIdx != -1)
+ text = text.Remove(idx, endIdx - idx);
+ }
+ }
+ else if (new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(Values.Type))
+ {
+ // Object reference
text = JsonSerializer.GetStringID(Values[0] as FlaxEngine.Object);
}
else
{
+ // Default
text = JsonSerializer.Serialize(Values[0]);
}
-
Clipboard.Text = text;
}
catch (Exception ex)
@@ -538,7 +569,7 @@ namespace FlaxEditor.CustomEditors
}
}
- private bool GetClipboardObject(out object result)
+ private bool GetClipboardObject(out object result, bool deserialize)
{
result = null;
var text = Clipboard.Text;
@@ -546,8 +577,32 @@ namespace FlaxEditor.CustomEditors
return false;
object obj;
- if (new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(Values.Type))
+ if (ParentEditor is Dedicated.ScriptsEditor)
{
+ // Script
+ obj = Values[0];
+ if (deserialize)
+ {
+ if (Presenter.Undo != null && Presenter.Undo.Enabled)
+ {
+ using (new UndoBlock(Presenter.Undo, obj, "Paste values"))
+ JsonSerializer.Deserialize(obj, text);
+ }
+ else
+ {
+ JsonSerializer.Deserialize(obj, text);
+ }
+ }
+#pragma warning disable 618
+ else if (Newtonsoft.Json.Schema.JsonSchema.Parse(text) == null)
+#pragma warning restore 618
+ {
+ return false;
+ }
+ }
+ else if (new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(Values.Type))
+ {
+ // Object reference
if (text.Length != 32)
return false;
JsonSerializer.ParseID(text, out var id);
@@ -555,6 +610,7 @@ namespace FlaxEditor.CustomEditors
}
else
{
+ // Default
obj = JsonConvert.DeserializeObject(text, TypeUtils.GetType(Values.Type), JsonSerializer.Settings);
}
@@ -576,7 +632,7 @@ namespace FlaxEditor.CustomEditors
{
try
{
- return GetClipboardObject(out _);
+ return GetClipboardObject(out _, false);
}
catch
{
@@ -594,7 +650,7 @@ namespace FlaxEditor.CustomEditors
try
{
- if (GetClipboardObject(out var obj))
+ if (GetClipboardObject(out var obj, true))
{
SetValue(obj);
}
diff --git a/Source/Editor/CustomEditors/Dedicated/LayersMaskEditor.cs b/Source/Editor/CustomEditors/Dedicated/LayersMaskEditor.cs
new file mode 100644
index 000000000..93cc84e76
--- /dev/null
+++ b/Source/Editor/CustomEditors/Dedicated/LayersMaskEditor.cs
@@ -0,0 +1,89 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+using FlaxEditor.Content.Settings;
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.CustomEditors.Dedicated
+{
+ ///
+ /// Custom editor for .
+ ///
+ [CustomEditor(typeof(LayersMask)), DefaultEditor]
+ internal class LayersMaskEditor : CustomEditor
+ {
+ private CheckBox[] _checkBoxes;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ var layers = LayersAndTagsSettings.GetCurrentLayers();
+ if (layers == null || layers.Length == 0)
+ {
+ layout.Label("Missing layers and tags settings");
+ return;
+ }
+
+ _checkBoxes = new CheckBox[layers.Length];
+ for (int i = 0; i < layers.Length; i++)
+ {
+ var layer = layers[i];
+ var property = layout.AddPropertyItem(layer);
+ var checkbox = property.Checkbox().CheckBox;
+ UpdateCheckbox(checkbox, i);
+ checkbox.Tag = i;
+ checkbox.StateChanged += OnCheckboxStateChanged;
+ _checkBoxes[i] = checkbox;
+ }
+ }
+
+ ///
+ protected override void Deinitialize()
+ {
+ _checkBoxes = null;
+
+ base.Deinitialize();
+ }
+
+ ///
+ public override void Refresh()
+ {
+ if (_checkBoxes != null)
+ {
+ for (int i = 0; i < _checkBoxes.Length; i++)
+ {
+ UpdateCheckbox(_checkBoxes[i], i);
+ }
+ }
+
+ base.Refresh();
+ }
+
+ private void OnCheckboxStateChanged(CheckBox checkBox)
+ {
+ var i = (int)checkBox.Tag;
+ var value = (LayersMask)Values[0];
+ var mask = 1u << i;
+ value.Mask &= ~mask;
+ value.Mask |= checkBox.Checked ? mask : 0;
+ SetValue(value);
+ }
+
+ private void UpdateCheckbox(CheckBox checkbox, int i)
+ {
+ for (var j = 0; j < Values.Count; j++)
+ {
+ var value = (((LayersMask)Values[j]).Mask & (1 << i)) != 0;
+ if (j == 0)
+ {
+ checkbox.Checked = value;
+ }
+ else if (checkbox.State != CheckBoxState.Intermediate)
+ {
+ if (checkbox.Checked != value)
+ checkbox.State = CheckBoxState.Intermediate;
+ }
+ }
+ }
+ }
+}
diff --git a/Source/Editor/CustomEditors/Dedicated/RigidBodyEditor.cs b/Source/Editor/CustomEditors/Dedicated/RigidBodyEditor.cs
index 02a1359c5..33a40c370 100644
--- a/Source/Editor/CustomEditors/Dedicated/RigidBodyEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/RigidBodyEditor.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using FlaxEditor.CustomEditors.GUI;
using FlaxEngine;
+using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Dedicated
{
@@ -15,6 +16,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
private readonly List _labels = new List(64);
private const int MassOrder = 110;
+ private Label _infoLabel;
///
protected override void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
@@ -63,6 +65,22 @@ namespace FlaxEditor.CustomEditors.Dedicated
check = value.OverrideMass;
_labels[i].CheckBox.Checked = check;
}
+ if (_infoLabel != null)
+ {
+ string text = string.Empty;
+ if (Editor.IsPlayMode)
+ {
+ var linearVelocity = value.LinearVelocity;
+ var centerOfMass = value.CenterOfMass;
+ text = $"Speed: {linearVelocity.Length}\n" +
+ $"Linear Velocity: {linearVelocity}\n" +
+ $"Angular Velocity: {value.AngularVelocity}\n" +
+ $"Center of Mass (local): {centerOfMass}\n" +
+ $"Center Of Mass (world): {value.Transform.LocalToWorld(centerOfMass)}\n" +
+ $"Is Sleeping: {value.IsSleeping}";
+ }
+ _infoLabel.Text = text;
+ }
}
///
@@ -71,6 +89,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
_labels.Clear();
base.Initialize(layout);
+
+ // Add info box
+ if (IsSingleObject && Values[0] is RigidBody && Editor.IsPlayMode)
+ {
+ _infoLabel = layout.Label(string.Empty).Label;
+ _infoLabel.AutoHeight = true;
+ }
}
}
}
diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
index 1e4116894..ffe4ce585 100644
--- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
@@ -624,7 +624,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
// Create group
var title = CustomEditorsUtil.GetPropertyNameUI(scriptType.Name);
- var group = layout.Group(title);
+ var group = layout.Group(title, editor);
if (Presenter.CacheExpandedGroups)
{
if (Editor.Instance.ProjectCache.IsCollapsedGroup(title))
diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs
index 59058056d..3ad1d851d 100644
--- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs
+++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs
@@ -35,6 +35,38 @@ namespace FlaxEditor.CustomEditors
///
public abstract ContainerControl ContainerControl { get; }
+ ///
+ /// Adds new group element.
+ ///
+ /// The title.
+ /// The custom editor to be linked for a group. Used to provide more utility functions for a drop panel UI via context menu.
+ /// True if use drop down icon and transparent group header, otherwise use normal style.
+ /// The created element.
+ public GroupElement Group(string title, CustomEditor linkedEditor, bool useTransparentHeader = false)
+ {
+ var element = Group(title, useTransparentHeader);
+ element.Panel.Tag = linkedEditor;
+ element.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked;
+ return element;
+ }
+
+ private void OnGroupPanelMouseButtonRightClicked(DropPanel groupPanel, Vector2 location)
+ {
+ var linkedEditor = (CustomEditor)groupPanel.Tag;
+ var menu = new ContextMenu();
+
+ var revertToPrefab = menu.AddButton("Revert to Prefab", linkedEditor.RevertToReferenceValue);
+ revertToPrefab.Enabled = linkedEditor.CanRevertReferenceValue;
+ var resetToDefault = menu.AddButton("Reset to default", linkedEditor.RevertToDefaultValue);
+ resetToDefault.Enabled = linkedEditor.CanRevertDefaultValue;
+ menu.AddSeparator();
+ menu.AddButton("Copy", linkedEditor.Copy);
+ var paste = menu.AddButton("Paste", linkedEditor.Paste);
+ paste.Enabled = linkedEditor.CanPaste;
+
+ menu.Show(groupPanel, location);
+ }
+
///
/// Adds new group element.
///
@@ -551,11 +583,9 @@ namespace FlaxEditor.CustomEditors
if (style == DisplayStyle.Group)
{
- var group = Group(name, true);
+ var group = Group(name, editor, true);
group.Panel.Close(false);
group.Panel.TooltipText = tooltip;
- group.Panel.Tag = editor;
- group.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked;
return group.Object(values, editor);
}
@@ -563,23 +593,6 @@ namespace FlaxEditor.CustomEditors
return property.Object(values, editor);
}
- private void OnGroupPanelMouseButtonRightClicked(DropPanel groupPanel, Vector2 location)
- {
- var linkedEditor = (CustomEditor)groupPanel.Tag;
- var menu = new ContextMenu();
-
- var revertToPrefab = menu.AddButton("Revert to Prefab", linkedEditor.RevertToReferenceValue);
- revertToPrefab.Enabled = linkedEditor.CanRevertReferenceValue;
- var resetToDefault = menu.AddButton("Reset to default", linkedEditor.RevertToDefaultValue);
- resetToDefault.Enabled = linkedEditor.CanRevertDefaultValue;
- menu.AddSeparator();
- menu.AddButton("Copy", linkedEditor.Copy);
- var paste = menu.AddButton("Paste", linkedEditor.Paste);
- paste.Enabled = linkedEditor.CanPaste;
-
- menu.Show(groupPanel, location);
- }
-
///
/// Adds object property editor. Selects proper based on overrides.
///
@@ -600,8 +613,9 @@ namespace FlaxEditor.CustomEditors
if (style == DisplayStyle.Group)
{
- var group = Group(label.Text, true);
+ var group = Group(label.Text, editor, true);
group.Panel.Close(false);
+ group.Panel.TooltipText = tooltip;
return group.Object(values, editor);
}
diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp
index e1eac50ea..2284478dd 100644
--- a/Source/Editor/Editor.cpp
+++ b/Source/Editor/Editor.cpp
@@ -3,6 +3,7 @@
#if USE_EDITOR
#include "Editor.h"
+#include "ProjectInfo.h"
#include "Engine/Core/Log.h"
#include "Scripting/ScriptsBuilder.h"
#include "Windows/SplashScreen.h"
diff --git a/Source/Editor/Editor.h b/Source/Editor/Editor.h
index 8c56043d9..8f4318c41 100644
--- a/Source/Editor/Editor.h
+++ b/Source/Editor/Editor.h
@@ -3,10 +3,10 @@
#pragma once
#include "Engine/Engine/Base/ApplicationBase.h"
-#include "Editor/ProjectInfo.h"
class Actor;
class SplashScreen;
+class ProjectInfo;
class ManagedEditor;
static_assert(USE_EDITOR, "Don't include Editor in non-editor builds.");
diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs
index 8ffe9fe35..1a10f35ce 100644
--- a/Source/Editor/EditorAssets.cs
+++ b/Source/Editor/EditorAssets.cs
@@ -97,6 +97,11 @@ namespace FlaxEditor
///
public static string DefaultSkyCubeTexture = "Editor/SimplySky";
+ ///
+ /// The default sprite material.
+ ///
+ public static string DefaultSpriteMaterial = "Editor/SpriteMaterial";
+
///
/// The IES Profile assets preview material.
///
@@ -112,6 +117,16 @@ namespace FlaxEditor
///
public static string VertexColorsPreviewMaterial = "Editor/Gizmo/VertexColorsPreviewMaterial";
+ ///
+ /// The Flax icon texture.
+ ///
+ public static string FlaxIconTexture = "Engine/Textures/FlaxIcon";
+
+ ///
+ /// The Flax icon (blue) texture.
+ ///
+ public static string FlaxIconBlueTexture = "Engine/Textures/FlaxIconBlue";
+
///
/// The icon lists used by editor from the SegMDL2 font.
///
diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs
index ec8faead4..af739d14c 100644
--- a/Source/Editor/GUI/Docking/DockPanelProxy.cs
+++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs
@@ -194,11 +194,22 @@ namespace FlaxEditor.GUI.Docking
bool isMouseOver = IsMouseOver && headerRect.Contains(MousePosition);
Render2D.FillRectangle(headerRect, containsFocus ? style.BackgroundSelected : isMouseOver ? style.BackgroundHighlighted : style.LightBackground);
+ float iconWidth = tab.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0;
+
+ if (tab.Icon.IsValid)
+ {
+ Render2D.DrawSprite(
+ tab.Icon,
+ new Rectangle(DockPanel.DefaultLeftTextMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize),
+ style.Foreground);
+
+ }
+
// Draw text
Render2D.DrawText(
style.FontMedium,
tab.Title,
- new Rectangle(DockPanel.DefaultLeftTextMargin, 0, Width - DockPanel.DefaultLeftTextMargin - DockPanel.DefaultButtonsSize - 2 * DockPanel.DefaultButtonsMargin, DockPanel.DefaultHeaderHeight),
+ new Rectangle(DockPanel.DefaultLeftTextMargin + iconWidth, 0, Width - DockPanel.DefaultLeftTextMargin - DockPanel.DefaultButtonsSize - 2 * DockPanel.DefaultButtonsMargin, DockPanel.DefaultHeaderHeight),
style.Foreground,
TextAlignment.Near,
TextAlignment.Center);
@@ -223,7 +234,8 @@ namespace FlaxEditor.GUI.Docking
var tab = _panel.GetTab(i);
Color tabColor = Color.Black;
var titleSize = tab.TitleSize;
- float width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin;
+ float iconWidth = tab.Icon.IsValid ? DockPanel.DefaultButtonsSize + DockPanel.DefaultLeftTextMargin : 0;
+ float width = titleSize.X + DockPanel.DefaultButtonsSize + 2 * DockPanel.DefaultButtonsMargin + DockPanel.DefaultLeftTextMargin + DockPanel.DefaultRightTextMargin + iconWidth;
var tabRect = new Rectangle(x, 0, width, DockPanel.DefaultHeaderHeight);
bool isMouseOver = IsMouseOver && tabRect.Contains(MousePosition);
bool isSelected = _panel.SelectedTab == tab;
@@ -241,11 +253,20 @@ namespace FlaxEditor.GUI.Docking
Render2D.FillRectangle(tabRect, tabColor);
}
+ if (tab.Icon.IsValid)
+ {
+ Render2D.DrawSprite(
+ tab.Icon,
+ new Rectangle(x + DockPanel.DefaultLeftTextMargin, (DockPanel.DefaultHeaderHeight - DockPanel.DefaultButtonsSize) / 2, DockPanel.DefaultButtonsSize, DockPanel.DefaultButtonsSize),
+ style.Foreground);
+
+ }
+
// Draw text
Render2D.DrawText(
style.FontMedium,
tab.Title,
- new Rectangle(x + DockPanel.DefaultLeftTextMargin, 0, 10000, DockPanel.DefaultHeaderHeight),
+ new Rectangle(x + DockPanel.DefaultLeftTextMargin + iconWidth, 0, 10000, DockPanel.DefaultHeaderHeight),
style.Foreground,
TextAlignment.Near,
TextAlignment.Center);
diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs
index 0c7f70ffc..5e94ef547 100644
--- a/Source/Editor/GUI/Docking/DockWindow.cs
+++ b/Source/Editor/GUI/Docking/DockWindow.cs
@@ -88,6 +88,11 @@ namespace FlaxEditor.GUI.Docking
}
}
+ ///
+ /// Gets or sets the window icon
+ ///
+ public SpriteHandle Icon { get; set; }
+
///
/// Gets the size of the title.
///
diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs
index f7aed5597..afda8484e 100644
--- a/Source/Editor/Gizmo/TransformGizmo.cs
+++ b/Source/Editor/Gizmo/TransformGizmo.cs
@@ -165,7 +165,10 @@ namespace FlaxEditor.Gizmo
if (_selectionParents[i] is ActorNode actorNode)
{
bounds = BoundingBox.Merge(bounds, actorNode.Actor.BoxWithChildren);
- navigationDirty |= (actorNode.Actor.StaticFlags & StaticFlags.Navigation) == StaticFlags.Navigation;
+ if (actorNode.AffectsNavigationWithChildren)
+ {
+ navigationDirty |= actorNode.Actor.HasStaticFlag(StaticFlags.Navigation);
+ }
}
}
}
diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp
index 28d4df32a..ae685829b 100644
--- a/Source/Editor/Managed/ManagedEditor.Internal.cpp
+++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp
@@ -2,6 +2,7 @@
#include "ManagedEditor.h"
#include "Editor/Editor.h"
+#include "Editor/ProjectInfo.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/ContentImporters/AssetsImportingManager.h"
diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs
index 8c0914d58..021817945 100644
--- a/Source/Editor/Modules/SceneEditingModule.cs
+++ b/Source/Editor/Modules/SceneEditingModule.cs
@@ -195,6 +195,56 @@ namespace FlaxEditor.Modules
OnSelectionChanged();
}
+ private void OnDirty(ActorNode node)
+ {
+ var options = Editor.Options.Options;
+ var isPlayMode = Editor.StateMachine.IsPlayMode;
+ var actor = node.Actor;
+
+ // Auto CSG mesh rebuild
+ if (!isPlayMode && options.General.AutoRebuildCSG)
+ {
+ if (actor is BoxBrush && actor.Scene)
+ actor.Scene.BuildCSG(options.General.AutoRebuildCSGTimeoutMs);
+ }
+
+ // Auto NavMesh rebuild
+ if (!isPlayMode && options.General.AutoRebuildNavMesh && actor.Scene && node.AffectsNavigationWithChildren)
+ {
+ var bounds = actor.BoxWithChildren;
+ Navigation.BuildNavMesh(actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
+ }
+ }
+
+ private void OnDirty(IEnumerable objects)
+ {
+ var options = Editor.Options.Options;
+ var isPlayMode = Editor.StateMachine.IsPlayMode;
+
+ // Auto CSG mesh rebuild
+ if (!isPlayMode && options.General.AutoRebuildCSG)
+ {
+ foreach (var obj in objects)
+ {
+ if (obj is ActorNode node && node.Actor is BoxBrush)
+ node.Actor.Scene.BuildCSG(options.General.AutoRebuildCSGTimeoutMs);
+ }
+ }
+
+ // Auto NavMesh rebuild
+ if (!isPlayMode && options.General.AutoRebuildNavMesh)
+ {
+ foreach (var obj in objects)
+ {
+ if (obj is ActorNode node && node.Actor.Scene && node.AffectsNavigationWithChildren)
+ {
+ var bounds = node.Actor.BoxWithChildren;
+ Navigation.BuildNavMesh(node.Actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
+ }
+ }
+ }
+ }
+
///
/// Spawns the specified actor to the game (with undo).
///
@@ -242,21 +292,92 @@ namespace FlaxEditor.Modules
SpawnEnd?.Invoke();
- var options = Editor.Options.Options;
+ OnDirty(actorNode);
+ }
- // Auto CSG mesh rebuild
- if (!isPlayMode && options.General.AutoRebuildCSG)
+ ///
+ /// Converts the selected actor to another type.
+ ///
+ /// The type to convert in.
+ public void Convert(Type to)
+ {
+ if (!Editor.SceneEditing.HasSthSelected || !(Editor.SceneEditing.Selection[0] is ActorNode))
+ return;
+
+ if (Level.IsAnySceneLoaded == false)
+ throw new InvalidOperationException("Cannot spawn actor when no scene is loaded.");
+
+ var actionList = new IUndoAction[4];
+ Actor old = ((ActorNode)Editor.SceneEditing.Selection[0]).Actor;
+ Actor actor = (Actor)FlaxEngine.Object.New(to);
+ var parent = old.Parent;
+ var orderInParent = old.OrderInParent;
+
+ SelectionDeleteBegin?.Invoke();
+
+ actionList[0] = new SelectionChangeAction(Selection.ToArray(), new SceneGraphNode[0], OnSelectionUndo);
+ actionList[0].Do();
+
+ actionList[1] = new DeleteActorsAction(new List
{
- if (actor is BoxBrush && actor.Scene)
- actor.Scene.BuildCSG(options.General.AutoRebuildCSGTimeoutMs);
+ Editor.Instance.Scene.GetActorNode(old)
+ });
+ actionList[1].Do();
+
+ SelectionDeleteEnd?.Invoke();
+
+ SpawnBegin?.Invoke();
+
+ // Copy properties
+ actor.Transform = old.Transform;
+ actor.StaticFlags = old.StaticFlags;
+ actor.HideFlags = old.HideFlags;
+ actor.Layer = old.Layer;
+ actor.Tag = old.Tag;
+ actor.Name = old.Name;
+ actor.IsActive = old.IsActive;
+
+ // Spawn actor
+ Level.SpawnActor(actor, parent);
+ if (parent != null)
+ actor.OrderInParent = orderInParent;
+ if (Editor.StateMachine.IsPlayMode)
+ actor.StaticFlags = StaticFlags.None;
+
+ // Move children
+ for (var i = old.ScriptsCount - 1; i >= 0; i--)
+ {
+ var script = old.Scripts[i];
+ script.Actor = actor;
+ Guid newid = Guid.NewGuid();
+ FlaxEngine.Object.Internal_ChangeID(FlaxEngine.Object.GetUnmanagedPtr(script), ref newid);
+ }
+ for (var i = old.Children.Length - 1; i >= 0; i--)
+ {
+ old.Children[i].Parent = actor;
}
- // Auto NavMesh rebuild
- if (!isPlayMode && options.General.AutoRebuildNavMesh && actor.Scene && (actor.StaticFlags & StaticFlags.Navigation) == StaticFlags.Navigation)
+ var actorNode = Editor.Instance.Scene.GetActorNode(actor);
+ if (actorNode == null)
+ throw new InvalidOperationException("Failed to create scene node for the spawned actor.");
+
+ actorNode.PostSpawn();
+ Editor.Scene.MarkSceneEdited(actor.Scene);
+
+ actionList[2] = new DeleteActorsAction(new List
{
- var bounds = actor.BoxWithChildren;
- Navigation.BuildNavMesh(actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
- }
+ actorNode
+ }, true);
+
+ actionList[3] = new SelectionChangeAction(new SceneGraphNode[0], new SceneGraphNode[] { actorNode }, OnSelectionUndo);
+ actionList[3].Do();
+
+ var actions = new MultiUndoAction(actionList);
+ Undo.AddAction(actions);
+
+ SpawnEnd?.Invoke();
+
+ OnDirty(actorNode);
}
///
@@ -269,8 +390,6 @@ namespace FlaxEditor.Modules
if (objects.Count == 0)
return;
- bool isPlayMode = Editor.StateMachine.IsPlayMode;
-
SelectionDeleteBegin?.Invoke();
// Change selection
@@ -290,30 +409,7 @@ namespace FlaxEditor.Modules
SelectionDeleteEnd?.Invoke();
- var options = Editor.Options.Options;
-
- // Auto CSG mesh rebuild
- if (!isPlayMode && options.General.AutoRebuildCSG)
- {
- foreach (var obj in objects)
- {
- if (obj is ActorNode node && node.Actor is BoxBrush)
- node.Actor.Scene.BuildCSG(options.General.AutoRebuildCSGTimeoutMs);
- }
- }
-
- // Auto NavMesh rebuild
- if (!isPlayMode && options.General.AutoRebuildNavMesh)
- {
- foreach (var obj in objects)
- {
- if (obj is ActorNode node && node.Actor.Scene && (node.Actor.StaticFlags & StaticFlags.Navigation) == StaticFlags.Navigation)
- {
- var bounds = node.Actor.BoxWithChildren;
- Navigation.BuildNavMesh(node.Actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
- }
- }
- }
+ OnDirty(objects);
}
///
diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs
index 07ff6a3b5..318d8379b 100644
--- a/Source/Editor/SceneGraph/ActorNode.cs
+++ b/Source/Editor/SceneGraph/ActorNode.cs
@@ -76,6 +76,29 @@ namespace FlaxEditor.SceneGraph
_treeNode.LinkNode(this);
}
+ ///
+ /// Gets a value indicating whether this actor affects navigation.
+ ///
+ public virtual bool AffectsNavigation => false;
+
+ ///
+ /// Gets a value indicating whether this actor affects navigation or any of its children (recursive).
+ ///
+ public bool AffectsNavigationWithChildren
+ {
+ get
+ {
+ if (_actor.HasStaticFlag(StaticFlags.Navigation) && AffectsNavigation)
+ return true;
+ for (var i = 0; i < ChildNodes.Count; i++)
+ {
+ if (ChildNodes[i] is ActorNode actorChild && actorChild.AffectsNavigationWithChildren)
+ return true;
+ }
+ return false;
+ }
+ }
+
///
/// Tries to find the tree node for the specified actor.
///
diff --git a/Source/Editor/SceneGraph/Actors/ColliderNode.cs b/Source/Editor/SceneGraph/Actors/ColliderNode.cs
index 3d9618939..6be43e01b 100644
--- a/Source/Editor/SceneGraph/Actors/ColliderNode.cs
+++ b/Source/Editor/SceneGraph/Actors/ColliderNode.cs
@@ -18,6 +18,9 @@ namespace FlaxEditor.SceneGraph
{
}
+ ///
+ public override bool AffectsNavigation => true;
+
///
public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal)
{
diff --git a/Source/Editor/SceneGraph/Actors/NavLinkNode.cs b/Source/Editor/SceneGraph/Actors/NavLinkNode.cs
index 62bc0a2e7..3e52b8216 100644
--- a/Source/Editor/SceneGraph/Actors/NavLinkNode.cs
+++ b/Source/Editor/SceneGraph/Actors/NavLinkNode.cs
@@ -78,6 +78,9 @@ namespace FlaxEditor.SceneGraph.Actors
AddChildNode(new LinkNode(this, new Guid(bytes), false));
}
+ ///
+ public override bool AffectsNavigation => true;
+
///
public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal)
{
diff --git a/Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs b/Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs
index 38faba8ef..56451b1fb 100644
--- a/Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs
+++ b/Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs
@@ -16,5 +16,8 @@ namespace FlaxEditor.SceneGraph.Actors
: base(actor)
{
}
+
+ ///
+ public override bool AffectsNavigation => true;
}
}
diff --git a/Source/Editor/SceneGraph/Actors/SpriteRenderNode.cs b/Source/Editor/SceneGraph/Actors/SpriteRenderNode.cs
new file mode 100644
index 000000000..33a4174e4
--- /dev/null
+++ b/Source/Editor/SceneGraph/Actors/SpriteRenderNode.cs
@@ -0,0 +1,63 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+using FlaxEngine;
+
+namespace FlaxEditor.SceneGraph.Actors
+{
+ ///
+ /// Scene tree node for actor type.
+ ///
+ ///
+ [HideInEditor]
+ public sealed class SpriteRenderNode : ActorNode
+ {
+ ///
+ public SpriteRenderNode(Actor actor)
+ : base(actor)
+ {
+ }
+
+ ///
+ public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal)
+ {
+ SpriteRender sprite = (SpriteRender)Actor;
+ Vector3 viewPosition = ray.View.Position;
+ Vector3 viewDirection = ray.View.Direction;
+ Matrix m1, m2, m3, world;
+ var size = sprite.Size;
+ Matrix.Scaling(size.X, size.Y, 1.0f, out m1);
+ var transform = sprite.Transform;
+ if (sprite.FaceCamera)
+ {
+ var up = Vector3.Up;
+ Matrix.Billboard(ref transform.Translation, ref viewPosition, ref up, ref viewDirection, out m2);
+ Matrix.Multiply(ref m1, ref m2, out m3);
+ Matrix.Scaling(ref transform.Scale, out m1);
+ Matrix.Multiply(ref m1, ref m3, out world);
+ }
+ else
+ {
+ transform.GetWorld(out m2);
+ Matrix.Multiply(ref m1, ref m2, out world);
+ }
+
+ OrientedBoundingBox bounds;
+ bounds.Extents = Vector3.Half;
+ bounds.Transformation = world;
+
+ normal = -ray.Ray.Direction;
+ return bounds.Intersects(ref ray.Ray, out distance);
+ }
+
+ ///
+ public override void PostSpawn()
+ {
+ base.PostSpawn();
+
+ // Setup for default values
+ var text = (SpriteRender)Actor;
+ text.Material = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.DefaultSpriteMaterial);
+ text.Image = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.FlaxIconTexture);
+ }
+ }
+}
diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
index 845b4fa35..cfd2c754e 100644
--- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
+++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
@@ -1,5 +1,8 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+using System;
+using FlaxEditor.Content;
+using FlaxEditor.GUI.ContextMenu;
using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors
@@ -16,5 +19,32 @@ namespace FlaxEditor.SceneGraph.Actors
: base(actor)
{
}
+
+ ///
+ public override void OnContextMenu(ContextMenu contextMenu)
+ {
+ base.OnContextMenu(contextMenu);
+
+ contextMenu.AddButton("Add mesh collider", OnAddMeshCollider).Enabled = ((StaticModel)Actor).Model != null;
+ }
+
+ private void OnAddMeshCollider()
+ {
+ var model = ((StaticModel)Actor).Model;
+ if (!model)
+ return;
+ Action created = collisionData =>
+ {
+ var actor = new MeshCollider
+ {
+ StaticFlags = Actor.StaticFlags,
+ Transform = Actor.Transform,
+ CollisionData = collisionData,
+ };
+ Editor.Instance.SceneEditing.Spawn(actor, Actor);
+ };
+ var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy();
+ collisionDataProxy.CreateCollisionDataFromModel(model, created);
+ }
}
}
diff --git a/Source/Editor/SceneGraph/Actors/TerrainNode.cs b/Source/Editor/SceneGraph/Actors/TerrainNode.cs
index 13fcb846b..120415d07 100644
--- a/Source/Editor/SceneGraph/Actors/TerrainNode.cs
+++ b/Source/Editor/SceneGraph/Actors/TerrainNode.cs
@@ -15,5 +15,8 @@ namespace FlaxEditor.SceneGraph.Actors
: base(actor)
{
}
+
+ ///
+ public override bool AffectsNavigation => true;
}
}
diff --git a/Source/Editor/SceneGraph/SceneGraphFactory.cs b/Source/Editor/SceneGraph/SceneGraphFactory.cs
index 9d4751bd9..2330d5472 100644
--- a/Source/Editor/SceneGraph/SceneGraphFactory.cs
+++ b/Source/Editor/SceneGraph/SceneGraphFactory.cs
@@ -70,6 +70,7 @@ namespace FlaxEditor.SceneGraph
CustomNodesTypes.Add(typeof(SplineCollider), typeof(ColliderNode));
CustomNodesTypes.Add(typeof(SplineRopeBody), typeof(ActorNode));
CustomNodesTypes.Add(typeof(NavMesh), typeof(ActorNode));
+ CustomNodesTypes.Add(typeof(SpriteRender), typeof(SpriteRenderNode));
}
///
diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs
index 835295147..01a832c5a 100644
--- a/Source/Editor/SceneGraph/SceneGraphNode.cs
+++ b/Source/Editor/SceneGraph/SceneGraphNode.cs
@@ -197,12 +197,12 @@ namespace FlaxEditor.SceneGraph
}
///
- /// The ray.
+ /// The ray (for intersection raycasting).
///
public Ray Ray;
///
- /// The camera view ray.
+ /// The camera view ray (camera position and direction).
///
public Ray View;
diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp
index 458d7aaeb..dde2455a7 100644
--- a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp
+++ b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp
@@ -6,6 +6,7 @@
#include "Engine/Platform/FileSystem.h"
#include "Engine/Core/Log.h"
#include "Editor/Editor.h"
+#include "Editor/ProjectInfo.h"
#include "Editor/Scripting/ScriptsBuilder.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
index 8c993ad25..75cb5fb5d 100644
--- a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
+++ b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
@@ -4,6 +4,7 @@
#include "Engine/Platform/FileSystem.h"
#include "Engine/Core/Log.h"
#include "Editor/Editor.h"
+#include "Editor/ProjectInfo.h"
#include "Editor/Scripting/ScriptsBuilder.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp
index 8c4ea4b58..5c0116a10 100644
--- a/Source/Editor/Scripting/ScriptsBuilder.cpp
+++ b/Source/Editor/Scripting/ScriptsBuilder.cpp
@@ -3,6 +3,7 @@
#include "ScriptsBuilder.h"
#include "CodeEditor.h"
#include "Editor/Editor.h"
+#include "Editor/ProjectInfo.h"
#include "Editor/Managed/ManagedEditor.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringBuilder.h"
diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs
index f5b466ab3..95f503338 100644
--- a/Source/Editor/Surface/Archetypes/Packing.cs
+++ b/Source/Editor/Surface/Archetypes/Packing.cs
@@ -579,6 +579,20 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(0, "YZ", typeof(Vector2), 1)
}
},
+ new NodeArchetype
+ {
+ TypeID = 47,
+ Title = "Mask ZW",
+ Description = "Unpack ZW components from Vector",
+ Flags = NodeFlags.AllGraphs,
+ ConnectionsHints = ConnectionsHint.Vector,
+ Size = new Vector2(110, 30),
+ Elements = new[]
+ {
+ NodeElementArchetype.Factory.Input(0, "Value", true, null, 0),
+ NodeElementArchetype.Factory.Output(0, "ZW", typeof(Vector2), 1)
+ }
+ },
// Mask XYZ
new NodeArchetype
diff --git a/Source/Editor/Tools/Terrain/EditTab.cs b/Source/Editor/Tools/Terrain/EditTab.cs
index e7b281f13..45577589b 100644
--- a/Source/Editor/Tools/Terrain/EditTab.cs
+++ b/Source/Editor/Tools/Terrain/EditTab.cs
@@ -190,7 +190,7 @@ namespace FlaxEditor.Tools.Terrain
// Auto NavMesh rebuild
if (!isPlayMode && editorOptions.General.AutoRebuildNavMesh)
{
- if (terrain.Scene && (terrain.StaticFlags & StaticFlags.Navigation) == StaticFlags.Navigation)
+ if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
{
Navigation.BuildNavMesh(terrain.Scene, patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
}
diff --git a/Source/Editor/Tools/Terrain/EditTerrainGizmo.cs b/Source/Editor/Tools/Terrain/EditTerrainGizmo.cs
index 314a6cfb8..851687bdf 100644
--- a/Source/Editor/Tools/Terrain/EditTerrainGizmo.cs
+++ b/Source/Editor/Tools/Terrain/EditTerrainGizmo.cs
@@ -207,7 +207,7 @@ namespace FlaxEditor.Tools.Terrain
// Auto NavMesh rebuild
if (!isPlayMode && editorOptions.General.AutoRebuildNavMesh)
{
- if (terrain.Scene && (terrain.StaticFlags & StaticFlags.Navigation) == StaticFlags.Navigation)
+ if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
{
Navigation.BuildNavMesh(terrain.Scene, patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
}
diff --git a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs
index eb32b3934..bc38eaaf1 100644
--- a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs
+++ b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs
@@ -154,7 +154,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
// Auto NavMesh rebuild
if (!isPlayMode && editorOptions.General.AutoRebuildNavMesh)
{
- if (terrain.Scene && (terrain.StaticFlags & StaticFlags.Navigation) == StaticFlags.Navigation)
+ if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
{
Navigation.BuildNavMesh(terrain.Scene, brushBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
}
diff --git a/Source/Editor/Utilities/EditorScene.cpp b/Source/Editor/Utilities/EditorScene.cpp
new file mode 100644
index 000000000..4188e7eb3
--- /dev/null
+++ b/Source/Editor/Utilities/EditorScene.cpp
@@ -0,0 +1,25 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#include "EditorScene.h"
+
+#include "Engine/Debug/DebugDraw.h"
+
+EditorScene::EditorScene(const SpawnParams& params)
+ : Scene(params)
+{
+ // Mock editor preview scene to be in gameplay
+ EditorScene::PostSpawn();
+ SceneBeginData beginData;
+ EditorScene::BeginPlay(&beginData);
+ beginData.OnDone();
+}
+
+void EditorScene::Update()
+{
+ for (auto& e : Ticking.Update.Ticks)
+ e.Call();
+ for (auto& e : Ticking.LateUpdate.Ticks)
+ e.Call();
+ for (auto& e : Ticking.FixedUpdate.Ticks)
+ e.Call();
+}
diff --git a/Source/Editor/Utilities/EditorScene.h b/Source/Editor/Utilities/EditorScene.h
new file mode 100644
index 000000000..998ec0141
--- /dev/null
+++ b/Source/Editor/Utilities/EditorScene.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Level/Scene/Scene.h"
+
+///
+/// Scene for editor previews with support of object drawing and updating in separation of global scenes collection. It mocks the gameplay to preview scene objects.
+///
+API_CLASS() class EditorScene final : public Scene
+{
+DECLARE_SCENE_OBJECT(EditorScene);
+public:
+
+ ///
+ /// Updates the gameplay.
+ ///
+ API_FUNCTION() void Update();
+};
diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp
index 889d94052..68e53c0ff 100644
--- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp
+++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp
@@ -57,30 +57,46 @@ public:
ViewportIconsRendererService ViewportIconsRendererServiceInstance;
-void ViewportIconsRenderer::DrawIcons(RenderContext& renderContext, Scene* scene)
+namespace
{
- auto& view = renderContext.View;
- if ((view.Flags & ViewFlags::EditorSprites) == 0 || QuadModel == nullptr || !QuadModel->IsLoaded())
- return;
-
- const BoundingFrustum frustum = view.Frustum;
- auto& icons = scene->GetSceneRendering()->ViewportIcons;
- Matrix m1, m2, world;
- Mesh::DrawInfo draw;
- draw.Lightmap = nullptr;
- draw.LightmapUVs = nullptr;
- draw.Flags = StaticFlags::Transform;
- draw.DrawModes = DrawPass::Forward;
- draw.PerInstanceRandom = 0;
- draw.LODBias = 0;
- draw.ForcedLOD = -1;
- draw.VertexColors = nullptr;
-
- for (Actor* icon : icons)
+ void DrawIcons(RenderContext& renderContext, Scene* scene, Mesh::DrawInfo& draw)
{
- BoundingSphere sphere(icon->GetPosition(), ICON_RADIUS);
+ auto& view = renderContext.View;
+ const BoundingFrustum frustum = view.Frustum;
+ auto& icons = scene->GetSceneRendering()->ViewportIcons;
+ Matrix m1, m2, world;
+ for (Actor* icon : icons)
+ {
+ BoundingSphere sphere(icon->GetPosition(), ICON_RADIUS);
+ IconTypes iconType;
+ if (frustum.Intersects(sphere) && ActorTypeToIconType.TryGet(icon->GetTypeHandle(), iconType))
+ {
+ // Create world matrix
+ Matrix::Scaling(ICON_RADIUS * 2.0f, m2);
+ Matrix::RotationY(PI, world);
+ Matrix::Multiply(m2, world, m1);
+ Matrix::Billboard(sphere.Center, view.Position, Vector3::Up, view.Direction, m2);
+ Matrix::Multiply(m1, m2, world);
+
+ // Draw icon
+ GeometryDrawStateData drawState;
+ draw.DrawState = &drawState;
+ draw.Buffer = &InstanceBuffers[static_cast(iconType)];
+ draw.World = &world;
+ draw.Bounds = sphere;
+ QuadModel->Draw(renderContext, draw);
+ }
+ }
+ }
+
+ void DrawIcons(RenderContext& renderContext, Actor* actor, Mesh::DrawInfo& draw)
+ {
+ auto& view = renderContext.View;
+ const BoundingFrustum frustum = view.Frustum;
+ Matrix m1, m2, world;
+ BoundingSphere sphere(actor->GetPosition(), ICON_RADIUS);
IconTypes iconType;
- if (frustum.Intersects(sphere) && ActorTypeToIconType.TryGet(icon->GetTypeHandle(), iconType))
+ if (frustum.Intersects(sphere) && ActorTypeToIconType.TryGet(actor->GetTypeHandle(), iconType))
{
// Create world matrix
Matrix::Scaling(ICON_RADIUS * 2.0f, m2);
@@ -97,6 +113,35 @@ void ViewportIconsRenderer::DrawIcons(RenderContext& renderContext, Scene* scene
draw.Bounds = sphere;
QuadModel->Draw(renderContext, draw);
}
+
+ for (auto child : actor->Children)
+ DrawIcons(renderContext, child, draw);
+ }
+}
+
+void ViewportIconsRenderer::DrawIcons(RenderContext& renderContext, Actor* actor)
+{
+ auto& view = renderContext.View;
+ if ((view.Flags & ViewFlags::EditorSprites) == 0 || QuadModel == nullptr || !QuadModel->IsLoaded())
+ return;
+
+ Mesh::DrawInfo draw;
+ draw.Lightmap = nullptr;
+ draw.LightmapUVs = nullptr;
+ draw.Flags = StaticFlags::Transform;
+ draw.DrawModes = DrawPass::Forward;
+ draw.PerInstanceRandom = 0;
+ draw.LODBias = 0;
+ draw.ForcedLOD = -1;
+ draw.VertexColors = nullptr;
+
+ if (const auto scene = SceneObject::Cast(actor))
+ {
+ ::DrawIcons(renderContext, scene, draw);
+ }
+ else
+ {
+ ::DrawIcons(renderContext, actor, draw);
}
}
diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.h b/Source/Editor/Utilities/ViewportIconsRenderer.h
index 8aacbe76b..0a19443a5 100644
--- a/Source/Editor/Utilities/ViewportIconsRenderer.h
+++ b/Source/Editor/Utilities/ViewportIconsRenderer.h
@@ -6,7 +6,7 @@
struct RenderContext;
class SceneRenderTask;
-class Scene;
+class Actor;
///
/// Editor viewports icons rendering service.
@@ -17,9 +17,9 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(ViewportIconsRenderer);
public:
///
- /// Draws the icons for the actors in the given scene.
+ /// Draws the icons for the actors in the given scene (or actor tree).
///
/// The rendering context.
- /// The scene.
- API_FUNCTION() static void DrawIcons(API_PARAM(Ref) RenderContext& renderContext, Scene* scene);
+ /// The actor (use scene for faster rendering).
+ API_FUNCTION() static void DrawIcons(API_PARAM(Ref) RenderContext& renderContext, Actor* actor);
};
diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
index fc6a50a34..c734b3127 100644
--- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
@@ -62,7 +62,7 @@ namespace FlaxEditor.Viewport
///
///
[HideInEditor]
- public sealed class EditorSpritesRenderer : PostProcessEffect
+ public class EditorSpritesRenderer : PostProcessEffect
{
///
/// The rendering task.
@@ -100,11 +100,7 @@ namespace FlaxEditor.Viewport
context.SetRenderTarget(depthBufferHandle, input.View());
// Collect draw calls
- for (int i = 0; i < Level.ScenesCount; i++)
- {
- var scene = Level.GetScene(i);
- ViewportIconsRenderer.DrawIcons(ref renderContext, scene);
- }
+ Draw(ref renderContext);
// Sort draw calls
renderList.SortDrawCalls(ref renderContext, true, DrawCallsListType.Forward);
@@ -118,6 +114,18 @@ namespace FlaxEditor.Viewport
Profiler.EndEventGPU();
}
+
+ ///
+ /// Draws the icons.
+ ///
+ protected virtual void Draw(ref RenderContext renderContext)
+ {
+ for (int i = 0; i < Level.ScenesCount; i++)
+ {
+ var scene = Level.GetScene(i);
+ ViewportIconsRenderer.DrawIcons(ref renderContext, scene);
+ }
+ }
}
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
@@ -175,10 +183,10 @@ namespace FlaxEditor.Viewport
_editor = editor;
// Prepare rendering task
- Task.ActorsSource = ActorsSources.ScenesAndCustomActors;
+ Task.ActorsSource = ActorsSources.Scenes;
Task.ViewFlags = ViewFlags.DefaultEditor;
- Task.Begin += RenderTaskOnBegin;
- Task.CollectDrawCalls += RenderTaskOnCollectDrawCalls;
+ Task.Begin += OnBegin;
+ Task.CollectDrawCalls += OnCollectDrawCalls;
Task.PostRender += OnPostRender;
// Render task after the main game task so streaming and render state data will use main game task instead of editor preview
@@ -396,7 +404,7 @@ namespace FlaxEditor.Viewport
Editor.Instance.SceneEditing.Spawn(actor);
}
- private void RenderTaskOnBegin(RenderTask task, GPUContext context)
+ private void OnBegin(RenderTask task, GPUContext context)
{
_debugDrawData.Clear();
@@ -412,7 +420,7 @@ namespace FlaxEditor.Viewport
}
}
- private void RenderTaskOnCollectDrawCalls(RenderContext renderContext)
+ private void OnCollectDrawCalls(RenderContext renderContext)
{
if (_previewStaticModel)
{
@@ -441,7 +449,7 @@ namespace FlaxEditor.Viewport
{
fixed (IntPtr* actors = _debugDrawData.ActorsPtrs)
{
- DebugDraw.DrawActors(new IntPtr(actors), _debugDrawData.ActorsCount);
+ DebugDraw.DrawActors(new IntPtr(actors), _debugDrawData.ActorsCount, true);
}
}
diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs
index dcb5ddc4a..89c42dc95 100644
--- a/Source/Editor/Viewport/PrefabWindowViewport.cs
+++ b/Source/Editor/Viewport/PrefabWindowViewport.cs
@@ -26,6 +26,18 @@ namespace FlaxEditor.Viewport
///
public class PrefabWindowViewport : PrefabPreview, IEditorPrimitivesOwner
{
+ private sealed class PrefabSpritesRenderer : MainEditorGizmoViewport.EditorSpritesRenderer
+ {
+ public PrefabWindowViewport Viewport;
+
+ public override bool CanRender => (Task.View.Flags & ViewFlags.EditorSprites) == ViewFlags.EditorSprites && Enabled;
+
+ protected override void Draw(ref RenderContext renderContext)
+ {
+ ViewportIconsRenderer.DrawIcons(ref renderContext, Viewport.Instance);
+ }
+ }
+
private readonly PrefabWindow _window;
private UpdateDelegate _update;
@@ -37,6 +49,9 @@ namespace FlaxEditor.Viewport
private ViewportWidgetButton _rotateSnapping;
private ViewportWidgetButton _scaleSnapping;
+ private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
+ private IntPtr _debugDrawContext;
+ private PrefabSpritesRenderer _spritesRenderer;
private readonly DragAssets _dragAssets = new DragAssets(ValidateDragItem);
private readonly DragActorType _dragActorType = new DragActorType(ValidateDragActorType);
private readonly DragHandlers _dragHandlers = new DragHandlers();
@@ -56,6 +71,11 @@ namespace FlaxEditor.Viewport
///
public EditorPrimitives EditorPrimitives;
+ ///
+ /// Gets or sets a value indicating whether draw shapes.
+ ///
+ public bool DrawDebugDraw = true;
+
///
/// Initializes a new instance of the class.
///
@@ -67,10 +87,13 @@ namespace FlaxEditor.Viewport
_window.SelectionChanged += OnSelectionChanged;
Undo = window.Undo;
ViewportCamera = new FPSCamera();
+ _debugDrawContext = DebugDraw.AllocateContext();
// Prepare rendering task
Task.ActorsSource = ActorsSources.CustomActors;
- Task.ViewFlags = ViewFlags.DefaultEditor & ~ViewFlags.EditorSprites;
+ Task.ViewFlags = ViewFlags.DefaultEditor;
+ Task.Begin += OnBegin;
+ Task.CollectDrawCalls += OnCollectDrawCalls;
Task.PostRender += OnPostRender;
// Create post effects
@@ -80,6 +103,10 @@ namespace FlaxEditor.Viewport
EditorPrimitives = FlaxEngine.Object.New();
EditorPrimitives.Viewport = this;
Task.CustomPostFx.Add(EditorPrimitives);
+ _spritesRenderer = FlaxEngine.Object.New();
+ _spritesRenderer.Task = Task;
+ _spritesRenderer.Viewport = this;
+ Task.CustomPostFx.Add(_spritesRenderer);
// Add transformation gizmo
TransformGizmo = new TransformGizmo(this);
@@ -226,6 +253,27 @@ namespace FlaxEditor.Viewport
}
}
+ private void OnBegin(RenderTask task, GPUContext context)
+ {
+ _debugDrawData.Clear();
+
+ // Collect selected objects debug shapes and visuals
+ var selectedParents = TransformGizmo.SelectedParents;
+ if (selectedParents.Count > 0)
+ {
+ for (int i = 0; i < selectedParents.Count; i++)
+ {
+ if (selectedParents[i].IsActiveInHierarchy)
+ selectedParents[i].OnDebugDraw(_debugDrawData);
+ }
+ }
+ }
+
+ private void OnCollectDrawCalls(RenderContext renderContext)
+ {
+ _debugDrawData.OnDraw(ref renderContext);
+ }
+
private void OnPostRender(GPUContext context, RenderContext renderContext)
{
if (renderContext.View.Mode != ViewMode.Default)
@@ -234,7 +282,30 @@ namespace FlaxEditor.Viewport
// Render editor primitives, gizmo and debug shapes in debug view modes
// Note: can use Output buffer as both input and output because EditorPrimitives is using a intermediate buffers
- EditorPrimitives.Render(context, ref renderContext, task.Output, task.Output);
+ if (EditorPrimitives && EditorPrimitives.CanRender)
+ {
+ EditorPrimitives.Render(context, ref renderContext, task.Output, task.Output);
+ }
+
+ // Render editor sprites
+ if (_spritesRenderer && _spritesRenderer.CanRender)
+ {
+ _spritesRenderer.Render(context, ref renderContext, task.Output, task.Output);
+ }
+
+ // Render selection outline
+ if (SelectionOutline && SelectionOutline.CanRender)
+ {
+ // Use temporary intermediate buffer
+ var desc = task.Output.Description;
+ var temp = RenderTargetPool.Get(ref desc);
+ SelectionOutline.Render(context, ref renderContext, task.Output, temp);
+
+ // Copy the results back to the output
+ context.CopyTexture(task.Output, 0, 0, 0, 0, temp, 0);
+
+ RenderTargetPool.Release(temp);
+ }
}
}
@@ -819,8 +890,14 @@ namespace FlaxEditor.Viewport
///
public override void OnDestroy()
{
+ if (_debugDrawContext != IntPtr.Zero)
+ {
+ DebugDraw.FreeContext(_debugDrawContext);
+ _debugDrawContext = IntPtr.Zero;
+ }
FlaxEngine.Object.Destroy(ref SelectionOutline);
FlaxEngine.Object.Destroy(ref EditorPrimitives);
+ FlaxEngine.Object.Destroy(ref _spritesRenderer);
base.OnDestroy();
}
@@ -828,6 +905,21 @@ namespace FlaxEditor.Viewport
///
public void DrawEditorPrimitives(GPUContext context, ref RenderContext renderContext, GPUTexture target, GPUTexture targetDepth)
{
+ // Draw selected objects debug shapes and visuals
+ if (DrawDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw)
+ {
+ DebugDraw.SetContext(_debugDrawContext);
+ DebugDraw.UpdateContext(_debugDrawContext, 1.0f / Engine.FramesPerSecond);
+ unsafe
+ {
+ fixed (IntPtr* actors = _debugDrawData.ActorsPtrs)
+ {
+ DebugDraw.DrawActors(new IntPtr(actors), _debugDrawData.ActorsCount, false);
+ }
+ }
+ DebugDraw.Draw(ref renderContext, target.View(), targetDepth.View(), true);
+ DebugDraw.SetContext(IntPtr.Zero);
+ }
}
}
}
diff --git a/Source/Editor/Windows/Assets/AudioClipWindow.cs b/Source/Editor/Windows/Assets/AudioClipWindow.cs
index 39230a3c2..1d6850200 100644
--- a/Source/Editor/Windows/Assets/AudioClipWindow.cs
+++ b/Source/Editor/Windows/Assets/AudioClipWindow.cs
@@ -5,6 +5,7 @@ using FlaxEditor.Content;
using FlaxEditor.Content.Import;
using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors;
+using FlaxEditor.GUI;
using FlaxEditor.Viewport.Previews;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -114,6 +115,10 @@ namespace FlaxEditor.Windows.Assets
private readonly SplitPanel _split;
private readonly AudioClipPreview _preview;
private readonly CustomEditorPresenter _propertiesEditor;
+ private readonly ToolStripButton _playButton;
+ private readonly ToolStripButton _pauseButton;
+ private EditorScene _previewScene;
+ private AudioSource _previewSource;
private readonly PropertiesProxy _properties;
private bool _isWaitingForLoad;
@@ -131,7 +136,7 @@ namespace FlaxEditor.Windows.Assets
Parent = this
};
- // AudioClip preview
+ // Preview
_preview = new AudioClipPreview
{
DrawMode = AudioClipPreview.DrawModes.Fill,
@@ -140,7 +145,7 @@ namespace FlaxEditor.Windows.Assets
Parent = _split.Panel1
};
- // AudioClip properties editor
+ // Properties editor
_propertiesEditor = new CustomEditorPresenter(null);
_propertiesEditor.Panel.Parent = _split.Panel2;
_properties = new PropertiesProxy();
@@ -149,14 +154,47 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_toolstrip.AddButton(Editor.Icons.Import32, () => Editor.ContentImporting.Reimport((BinaryAssetItem)Item)).LinkTooltip("Reimport");
_toolstrip.AddSeparator();
+ _playButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Play32, OnPlay).LinkTooltip("Play/stop audio");
+ _pauseButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Pause32, OnPause).LinkTooltip("Pause audio");
+ _toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/audio/audio-clip.html")).LinkTooltip("See documentation to learn more");
}
+ private void OnPlay()
+ {
+ if (!_previewScene)
+ {
+ _previewScene = new EditorScene();
+ }
+ if (!_previewSource)
+ {
+ _previewSource = new AudioSource
+ {
+ Parent = _previewScene,
+ Clip = _asset,
+ };
+ }
+ if (_previewSource.State == AudioSource.States.Playing)
+ _previewSource.Stop();
+ else
+ _previewSource.Play();
+ UpdateToolstrip();
+ }
+
+ private void OnPause()
+ {
+ if (_previewSource)
+ _previewSource.Pause();
+ UpdateToolstrip();
+ }
+
///
protected override void UnlinkItem()
{
_properties.OnClean();
_preview.Asset = null;
+ if (_previewSource)
+ _previewSource.Clip = null;
_isWaitingForLoad = false;
base.UnlinkItem();
@@ -187,6 +225,29 @@ namespace FlaxEditor.Windows.Assets
base.OnClose();
}
+ ///
+ public override void OnDestroy()
+ {
+ if (_previewSource)
+ {
+ _previewSource.Stop();
+ Object.Destroy(_previewSource);
+ _previewSource = null;
+ }
+ Object.Destroy(ref _previewScene);
+
+ base.OnDestroy();
+ }
+
+ ///
+ protected override void UpdateToolstrip()
+ {
+ base.UpdateToolstrip();
+
+ _playButton.Icon = _previewSource && _previewSource.State == AudioSource.States.Playing ? Editor.Icons.Stop32 : Editor.Icons.Play32;
+ _pauseButton.Enabled = _previewSource && _previewSource.State == AudioSource.States.Playing;
+ }
+
///
public override void Update(float deltaTime)
{
@@ -195,16 +256,21 @@ namespace FlaxEditor.Windows.Assets
// Check if need to load
if (_isWaitingForLoad && _asset.IsLoaded)
{
- // Clear flag
_isWaitingForLoad = false;
// Init properties and parameters proxy
_properties.OnLoad(this);
_propertiesEditor.BuildLayout();
+ if (_previewSource)
+ _previewSource.Stop();
// Setup
ClearEditedFlag();
}
+
+ // Tick scene
+ if (_previewScene)
+ _previewScene.Update();
}
///
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs
index e14c548a9..8dd4a6ebe 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.cs
@@ -10,6 +10,7 @@ using FlaxEditor.SceneGraph;
using FlaxEditor.Viewport;
using FlaxEngine;
using FlaxEngine.GUI;
+using Object = FlaxEngine.Object;
namespace FlaxEditor.Windows.Assets
{
@@ -22,7 +23,7 @@ namespace FlaxEditor.Windows.Assets
{
private readonly SplitPanel _split1;
private readonly SplitPanel _split2;
- private TextBox _searchBox;
+ private readonly TextBox _searchBox;
private readonly PrefabTree _tree;
private readonly PrefabWindowViewport _viewport;
private readonly CustomEditorPresenter _propertiesEditor;
diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs
index 9b7ec8fd9..350714bb3 100644
--- a/Source/Editor/Windows/ContentWindow.cs
+++ b/Source/Editor/Windows/ContentWindow.cs
@@ -66,6 +66,7 @@ namespace FlaxEditor.Windows
: base(editor, true, ScrollBars.None)
{
Title = "Content";
+ Icon = editor.Icons.Folder64;
// Content database events
editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true;
diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs
index 55781438e..7900b55c2 100644
--- a/Source/Editor/Windows/DebugLogWindow.cs
+++ b/Source/Editor/Windows/DebugLogWindow.cs
@@ -72,7 +72,7 @@ namespace FlaxEditor.Windows
///
/// The default height of the entries.
///
- public const float DefaultHeight = 48.0f;
+ public const float DefaultHeight = 32.0f;
private DebugLogWindow _window;
public LogGroup Group;
@@ -128,10 +128,11 @@ namespace FlaxEditor.Windows
Render2D.FillRectangle(clientRect, style.Background * 0.9f);
// Icon
- Render2D.DrawSprite(Icon, new Rectangle(5, 8, 32, 32), style.Foreground);
+ var iconColor = Group == LogGroup.Error ? Color.Red : (Group == LogGroup.Warning ? Color.Yellow : style.Foreground);
+ Render2D.DrawSprite(Icon, new Rectangle(5, 0, 32, 32), iconColor);
// Title
- var textRect = new Rectangle(38, 6, clientRect.Width - 40, clientRect.Height - 10);
+ var textRect = new Rectangle(38, 2, clientRect.Width - 40, clientRect.Height - 10);
Render2D.PushClip(ref clientRect);
Render2D.DrawText(style.FontMedium, Desc.Title, textRect, style.Foreground);
Render2D.PopClip();
@@ -267,6 +268,8 @@ namespace FlaxEditor.Windows
private readonly ToolStripButton _pauseOnErrorButton;
private readonly ToolStripButton[] _groupButtons = new ToolStripButton[3];
+ private LogType _iconType = LogType.Info;
+
internal SpriteHandle IconInfo;
internal SpriteHandle IconWarning;
internal SpriteHandle IconError;
@@ -279,6 +282,7 @@ namespace FlaxEditor.Windows
: base(editor, true, ScrollBars.None)
{
Title = "Debug Log";
+ Icon = IconInfo;
OnEditorOptionsChanged(Editor.Options.Options);
// Toolstrip
@@ -333,6 +337,7 @@ namespace FlaxEditor.Windows
Editor.Options.OptionsChanged += OnEditorOptionsChanged;
Debug.Logger.LogHandler.SendLog += LogHandlerOnSendLog;
Debug.Logger.LogHandler.SendExceptionLog += LogHandlerOnSendExceptionLog;
+
}
private void OnEditorOptionsChanged(EditorOptions options)
@@ -381,6 +386,18 @@ namespace FlaxEditor.Windows
_pendingEntries.Add(newEntry);
}
+ if (newEntry.Group == LogGroup.Warning && _iconType < LogType.Warning)
+ {
+ _iconType = LogType.Warning;
+ UpdateIcon();
+ }
+
+ if (newEntry.Group == LogGroup.Error && _iconType < LogType.Error)
+ {
+ _iconType = LogType.Error;
+ UpdateIcon();
+ }
+
// Pause on Error (we should do it as fast as possible)
if (newEntry.Group == LogGroup.Error && _pauseOnErrorButton.Checked && Editor.StateMachine.CurrentState == Editor.StateMachine.PlayingState)
{
@@ -426,6 +443,12 @@ namespace FlaxEditor.Windows
UpdateCount((int)LogGroup.Error, " Error");
UpdateCount((int)LogGroup.Warning, " Warning");
UpdateCount((int)LogGroup.Info, " Message");
+
+ if (_logCountPerGroup[(int)LogGroup.Error] == 0)
+ {
+ _iconType = _logCountPerGroup[(int)LogGroup.Warning] == 0 ? LogType.Info : LogType.Warning;
+ UpdateIcon();
+ }
}
private void UpdateCount(int group, string msg)
@@ -435,6 +458,22 @@ namespace FlaxEditor.Windows
_groupButtons[group].Text = _logCountPerGroup[group] + msg;
}
+ private void UpdateIcon()
+ {
+ if (_iconType == LogType.Warning)
+ {
+ Icon = IconWarning;
+ }
+ else if (_iconType == LogType.Error)
+ {
+ Icon = IconError;
+ }
+ else
+ {
+ Icon = IconInfo;
+ }
+ }
+
private void LogHandlerOnSendLog(LogType level, string msg, Object o, string stackTrace)
{
var desc = new LogEntryDescription
@@ -566,6 +605,14 @@ namespace FlaxEditor.Windows
}
}
+ ///
+ public override void OnStartContainsFocus()
+ {
+ _iconType = LogType.Info;
+ UpdateIcon();
+ base.OnStartContainsFocus();
+ }
+
///
public override void OnDestroy()
{
diff --git a/Source/Editor/Windows/EditGameWindow.cs b/Source/Editor/Windows/EditGameWindow.cs
index 26f725a82..0d3a8bf95 100644
--- a/Source/Editor/Windows/EditGameWindow.cs
+++ b/Source/Editor/Windows/EditGameWindow.cs
@@ -210,10 +210,11 @@ namespace FlaxEditor.Windows
if (Editor.Undo.Enabled)
{
- bool navigationDirty = (_pilotActor.StaticFlags & StaticFlags.Navigation) == StaticFlags.Navigation;
+ ActorNode node = Editor.Scene.GetActorNode(_pilotActor);
+ bool navigationDirty = node.AffectsNavigationWithChildren;
var action = new TransformObjectsAction
(
- new List { Editor.Scene.GetActorNode(_pilotActor) },
+ new List { node },
new List { _pilotStart },
ref _pilotBounds,
navigationDirty
diff --git a/Source/Editor/Windows/SceneTreeWindow.Actors.cs b/Source/Editor/Windows/SceneTreeWindow.Actors.cs
index 8970b6a48..04d5a3ced 100644
--- a/Source/Editor/Windows/SceneTreeWindow.Actors.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.Actors.cs
@@ -110,6 +110,7 @@ namespace FlaxEditor.Windows
new KeyValuePair("UI Control", typeof(UIControl)),
new KeyValuePair("UI Canvas", typeof(UICanvas)),
new KeyValuePair("Text Render", typeof(TextRender)),
+ new KeyValuePair("Sprite Render", typeof(SpriteRender)),
}
},
};
diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
index 9b028dfef..3cecb0ac4 100644
--- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
-using System.Collections.Generic;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph;
using FlaxEngine;
@@ -58,6 +57,30 @@ namespace FlaxEditor.Windows
b = contextMenu.AddButton("Duplicate", Editor.SceneEditing.Duplicate);
b.Enabled = hasSthSelected;
+ if (Editor.SceneEditing.SelectionCount == 1)
+ {
+ var convertMenu = contextMenu.AddChildMenu("Convert");
+ var convertActorCm = convertMenu.ContextMenu;
+ for (int i = 0; i < SpawnActorsGroups.Length; i++)
+ {
+ var group = SpawnActorsGroups[i];
+
+ if (group.Types.Length == 1)
+ {
+ var type = group.Types[0].Value;
+ convertActorCm.AddButton(group.Types[0].Key, () => Editor.SceneEditing.Convert(type));
+ }
+ else
+ {
+ var groupCm = convertActorCm.AddChildMenu(group.Name).ContextMenu;
+ for (int j = 0; j < group.Types.Length; j++)
+ {
+ var type = group.Types[j].Value;
+ groupCm.AddButton(group.Types[j].Key, () => Editor.SceneEditing.Convert(type));
+ }
+ }
+ }
+ }
b = contextMenu.AddButton("Delete", Editor.SceneEditing.Delete);
b.Enabled = hasSthSelected;
diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs
index 7e4a26548..7084addd1 100644
--- a/Source/Editor/Windows/ToolboxWindow.cs
+++ b/Source/Editor/Windows/ToolboxWindow.cs
@@ -173,6 +173,7 @@ namespace FlaxEditor.Windows
groupGui.AddChild(CreateActorItem("UI Control", typeof(UIControl)));
groupGui.AddChild(CreateActorItem("UI Canvas", typeof(UICanvas)));
groupGui.AddChild(CreateActorItem("Text Render", typeof(TextRender)));
+ groupGui.AddChild(CreateActorItem("Sprite Render", typeof(SpriteRender)));
actorGroups.SelectedTabIndex = 1;
}
diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp
index 09b7cf874..ea24d126f 100644
--- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp
+++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp
@@ -213,6 +213,11 @@ void SceneAnimationPlayer::Tick(float dt)
_lastTime = _time = time;
}
+void SceneAnimationPlayer::MapObject(const Guid& from, const Guid& to)
+{
+ _objectsMapping[from] = to;
+}
+
void SceneAnimationPlayer::Restore(SceneAnimation* anim, int32 stateIndexOffset)
{
// Restore all tracks
@@ -268,7 +273,9 @@ void SceneAnimationPlayer::Restore(SceneAnimation* anim, int32 stateIndexOffset)
case SceneAnimation::Track::Types::ObjectReferenceProperty:
{
value = &_restoreData[state.RestoreStateIndex];
- auto obj = Scripting::FindObject(*(Guid*)value);
+ Guid id = *(Guid*)value;
+ _objectsMapping.TryGet(id, id);
+ auto obj = Scripting::FindObject(id);
value = obj ? obj->GetOrCreateManagedInstance() : nullptr;
break;
}
@@ -358,7 +365,9 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
void* value = (void*)((byte*)trackDataKeyframes->Keyframes + keyframeSize * (leftKey) + sizeof(float));
if (track.Type == SceneAnimation::Track::Types::ObjectReferenceProperty)
{
- auto obj = Scripting::FindObject(*(Guid*)value);
+ Guid id = *(Guid*)value;
+ _objectsMapping.TryGet(id, id);
+ auto obj = Scripting::FindObject(id);
value = obj ? obj->GetOrCreateManagedInstance() : nullptr;
*(void**)target = value;
}
@@ -680,10 +689,12 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
// Find actor
const auto trackData = track.GetData();
+ Guid id = trackData->ID;
+ _objectsMapping.TryGet(id, id);
state.Object = Scripting::FindObject(trackData->ID);
if (!state.Object)
{
- LOG(Warning, "Failed to find {3} of ID={0} for track '{1}' in scene animation '{2}'", trackData->ID, track.Name, anim->ToString(), TEXT("actor"));
+ LOG(Warning, "Failed to find {3} of ID={0} for track '{1}' in scene animation '{2}'", id, track.Name, anim->ToString(), TEXT("actor"));
break;
}
}
@@ -708,10 +719,12 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
break;
// Find script
- state.Object = Scripting::FindObject