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