diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml
index df6fa97b9..3bb5242fe 100644
--- a/.github/workflows/build_linux.yml
+++ b/.github/workflows/build_linux.yml
@@ -3,6 +3,27 @@ on: [push, pull_request]
jobs:
+ # Editor
+ editor-linux:
+ name: Editor (Linux, Development x64)
+ runs-on: "ubuntu-20.04"
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v2
+ - name: Checkout LFS
+ run: |
+ git lfs version
+ git lfs pull
+ - name: Install dependencies
+ run: |
+ sudo rm -f /etc/apt/sources.list.d/*
+ sudo cp -f .github/workflows/build_linux_sources.list /etc/apt/sources.list
+ sudo apt-get update
+ sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev
+ - name: Build
+ run: |
+ ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxEditor
+
# Game
game-linux:
name: Game (Linux, Release x64)
diff --git a/.github/workflows/build_linux_sources.list b/.github/workflows/build_linux_sources.list
new file mode 100644
index 000000000..4d8f94f35
--- /dev/null
+++ b/.github/workflows/build_linux_sources.list
@@ -0,0 +1,4 @@
+deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse
+deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe multiverse
+deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse
+deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 236fa6d67..ada5e5fa5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -32,3 +32,19 @@ Go check out our [Trello](https://trello.com/b/NQjLXRCP/flax-roadmap).
Thank you for taking interest in contributing to Flax!
+
+## **Common issues**
+
+Below are some common issues that someone working with the FlaxEngine source code might run into. Hopefully some of those issues will get fixed in the future. If you know how, please contribute!
+
+* Missing MSVC toolset
+ * Install it through the Visual Studio Installer
+* Building or attaching fails
+ * Run `GenerateProjectFiles.bat`
+ * Rebuild `Flax.Build`
+ * Make sure that there isn't a stray FlaxEngine process running in the background
+ * First start Flax and then attach the C# debugger
+ * Configure the C# FlaxEngine project by going into the project properties, then the debug tab and selecting "Start external program" `Flax\FlaxEngine\Binaries\Editor\Win64\Debug\FlaxEditor.exe`
+ * Then you can also set command line arguments such as `-project "C:\Users\PROFILE\Documents\Flax Projects\FlaxSamples\BasicTemplate"`
+* Git LFS
+ * Push with `git push --no-verify`
diff --git a/Content/Editor/IconsAtlas.flax b/Content/Editor/IconsAtlas.flax
index 9dda6fec2..13af92065 100644
--- a/Content/Editor/IconsAtlas.flax
+++ b/Content/Editor/IconsAtlas.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:51daf141b7f0d7faf83bb42eceebf9c5442a2f101d02e352215b9684842b5fc7
-size 5636265
+oid sha256:de930eef22208b95882bc9f870063e339672b577d381bcfa6603f0be45c814c5
+size 5610110
diff --git a/Content/Editor/Particles/Constant Burst.flax b/Content/Editor/Particles/Constant Burst.flax
index 6e8dc3057..7a160baae 100644
--- a/Content/Editor/Particles/Constant Burst.flax
+++ b/Content/Editor/Particles/Constant Burst.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e5c47c264658adc60ba1809e50039dbaa50c3157d678d31edb8328a143f6dd34
-size 2269
+oid sha256:116453e2aa2950832d6ca0300e65591f7ffbbe262dc5b3d41af4ad465eee130c
+size 2671
diff --git a/Content/Editor/Particles/Periodic Burst.flax b/Content/Editor/Particles/Periodic Burst.flax
index 6bbb5b7d7..b165f1ee9 100644
--- a/Content/Editor/Particles/Periodic Burst.flax
+++ b/Content/Editor/Particles/Periodic Burst.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0d7266e3179077f26b1a12617cdb9b1733fd753ee557f2d81ea58a48fa4265f4
-size 3039
+oid sha256:43ab024ecfd3b54aecce42f9bdc8d81fe1c35719146996e336c2105540de1594
+size 3621
diff --git a/Content/Editor/Particles/Ribbon Spiral.flax b/Content/Editor/Particles/Ribbon Spiral.flax
index bff7f09a9..6cabcdce6 100644
--- a/Content/Editor/Particles/Ribbon Spiral.flax
+++ b/Content/Editor/Particles/Ribbon Spiral.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a1bbb361118d68c52d4fd8176c01c144f2f3105ac13b80b33ea23101f40589ea
-size 1992
+oid sha256:43d743429f984745a3eeb22fa74fe4462e1f0d7b638f65ded06fc6f14dadb175
+size 2365
diff --git a/Content/Editor/Particles/Smoke.flax b/Content/Editor/Particles/Smoke.flax
index cb474f3c3..e68821034 100644
--- a/Content/Editor/Particles/Smoke.flax
+++ b/Content/Editor/Particles/Smoke.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9787b9b4cc2caf91c65b4d858cfcb11afe0fe4917c0f741f3e0099ba687e4da9
-size 15421
+oid sha256:84c4ea742b6b4775bc518731ecb56d7fe202302b3bdb61f1dcdf8727ade94353
+size 16019
diff --git a/Content/Editor/Particles/Sparks.flax b/Content/Editor/Particles/Sparks.flax
index 9a94e1f98..8b076afdc 100644
--- a/Content/Editor/Particles/Sparks.flax
+++ b/Content/Editor/Particles/Sparks.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1abf314458e98bd45638abab1092dea374d68c9cb5085f6d3a5de65947f62ea7
-size 14743
+oid sha256:3a6dc75b65f0eb1a66b12fbe4e20091b55aed9143a8574e7a9dfc000413eb2f6
+size 15081
diff --git a/Content/Editor/Scripting/CppAssetTemplate.h b/Content/Editor/Scripting/CppAssetTemplate.h
new file mode 100644
index 000000000..ddd8109c7
--- /dev/null
+++ b/Content/Editor/Scripting/CppAssetTemplate.h
@@ -0,0 +1,23 @@
+%copyright%
+#pragma once
+
+#include "Engine/Core/ISerializable.h"
+#include "Engine/Core/Types/BaseTypes.h"
+#include "Engine/Content/Assets/Model.h"
+#include "Engine/Scripting/ScriptingType.h"
+
+///
+/// %class% Json Asset.
+///
+API_CLASS() class %module%%class% : public ISerializable
+{
+ API_AUTO_SERIALIZATION();
+ DECLARE_SCRIPTING_TYPE_NO_SPAWN(%class%);
+public:
+ // Custom float value.
+ API_FIELD(Attributes = "Range(0, 20), EditorOrder(0), EditorDisplay(\"Data\")")
+ float FloatValue = 20.0f;
+ // Custom vector data.
+ API_FIELD(Attributes = "EditorOrder(1), EditorDisplay(\"Data\")")
+ Vector3 Vector3Value = Vector3(0.1f);
+};
diff --git a/Content/Editor/Scripting/CppStaticClassTemplate.cpp b/Content/Editor/Scripting/CppStaticClassTemplate.cpp
new file mode 100644
index 000000000..08ffb9348
--- /dev/null
+++ b/Content/Editor/Scripting/CppStaticClassTemplate.cpp
@@ -0,0 +1,8 @@
+%copyright%
+#include "%filename%.h"
+#include "Engine/Core/Log.h"
+
+void %class%::RunNativeAction(Vector4 data)
+{
+ LOG(Warning, "Data in RunNativeAction: {0}", data);
+}
diff --git a/Content/Editor/Scripting/CppStaticClassTemplate.h b/Content/Editor/Scripting/CppStaticClassTemplate.h
new file mode 100644
index 000000000..8b235695f
--- /dev/null
+++ b/Content/Editor/Scripting/CppStaticClassTemplate.h
@@ -0,0 +1,20 @@
+%copyright%
+#pragma once
+
+#include "Engine/Scripting/Script.h"
+#include "Engine/Core/Math/Vector4.h"
+
+///
+/// %class% Function Library
+///
+API_CLASS(Static) class %module%%class%
+{
+ DECLARE_SCRIPTING_TYPE_MINIMAL(%class%);
+public:
+
+ ///
+ /// Logs the function parameter natively.
+ ///
+ /// Data to pass to native code
+ API_FUNCTION() static void RunNativeAction(Vector4 data);
+};
diff --git a/Content/Editor/Scripting/ScriptTemplate.cs b/Content/Editor/Scripting/ScriptTemplate.cs
index c9ae5068e..2a150306d 100644
--- a/Content/Editor/Scripting/ScriptTemplate.cs
+++ b/Content/Editor/Scripting/ScriptTemplate.cs
@@ -4,23 +4,30 @@ using FlaxEngine;
namespace %namespace%
{
+ ///
+ /// %class% Script.
+ ///
public class %class% : Script
{
+ ///
public override void OnStart()
{
// Here you can add code that needs to be called when script is created, just before the first game update
}
-
+
+ ///
public override void OnEnable()
{
// Here you can add code that needs to be called when script is enabled (eg. register for events)
}
+ ///
public override void OnDisable()
{
// Here you can add code that needs to be called when script is disabled (eg. unregister from events)
}
+ ///
public override void OnUpdate()
{
// Here you can add code that needs to be called every frame
diff --git a/Content/Shaders/BitonicSort.flax b/Content/Shaders/BitonicSort.flax
index 1d5b8a581..c9dd81dc9 100644
--- a/Content/Shaders/BitonicSort.flax
+++ b/Content/Shaders/BitonicSort.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f46a61cf8d5183230176e661a51208bfeece16cc7238655f406288ff448af64e
-size 6721
+oid sha256:0c780dc1881ef96dece237bde3e87fca3c9e4e542d89ebde7c9308f00f81c6a9
+size 6808
diff --git a/Content/Shaders/DepthOfField.flax b/Content/Shaders/DepthOfField.flax
index a84047429..6c4d9daec 100644
--- a/Content/Shaders/DepthOfField.flax
+++ b/Content/Shaders/DepthOfField.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:92355644db4cdfd96250b779a87385e6805d01ce084801b3a4be704f026e9bca
-size 21025
+oid sha256:d8b69fce536cb27d497f6ff91f8adff3fbeaa9a704ced1b3271f4d621c195d53
+size 21054
diff --git a/Content/Shaders/EyeAdaptation.flax b/Content/Shaders/EyeAdaptation.flax
index 9c538e798..93e17c1a2 100644
--- a/Content/Shaders/EyeAdaptation.flax
+++ b/Content/Shaders/EyeAdaptation.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c12b7b43743e4e609a4155d7a4117421298d679d07548ffa48a386da496556b0
-size 4605
+oid sha256:f0d2b41560554e53692958ddea4cb53a082a7fbe88d7cb99eeccd095c45f631a
+size 4600
diff --git a/Content/Shaders/GUI.flax b/Content/Shaders/GUI.flax
index 73fd80725..0e9077247 100644
--- a/Content/Shaders/GUI.flax
+++ b/Content/Shaders/GUI.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ea8cffddde04c7f3829ce0bd0cb61deecb50a9ac954c552cddb72d182538d605
-size 5108
+oid sha256:aae706e79bbc4eb9508da338a3ba2b4300f012771bac3c1a43835fd41ad7282a
+size 5111
diff --git a/Content/Shaders/VolumetricFog.flax b/Content/Shaders/VolumetricFog.flax
index 118e46c91..19420d9ad 100644
--- a/Content/Shaders/VolumetricFog.flax
+++ b/Content/Shaders/VolumetricFog.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:81de76f651892a4d410c4624de3cab685d29b6711938c04325dac776588e3ec9
-size 14293
+oid sha256:2b71f70eab5a3139707a624ec2ea796e83114d3cc5351c9648921e13bffa2386
+size 14288
diff --git a/Development/Documentation/mono.md b/Development/Documentation/mono.md
index bbde0cd56..d292ebb3d 100644
--- a/Development/Documentation/mono.md
+++ b/Development/Documentation/mono.md
@@ -5,6 +5,7 @@ Custom fork: [https://github.com/FlaxEngine/mono](https://github.com/FlaxEngine/
### Notes
Some useful notes and tips for devs:
+* Use `-monolog` to print Mono logs to Flax logs
* When working with mono fork set `localRepoPath` to local repo location in `Source\Tools\Flax.Build\Deps\Dependencies\mono.cs`
* To update mono deps when developing/updating use `.\Development\Scripts\Windows\CallBuildTool.bat -log -ReBuildDeps -verbose -depsToBuild=mono -platform=Windows`, then build engine and run it
* `MONO_GC_DEBUG=check-remset-consistency` - it will do additional checks at each collection to see if there are any missing write barriers
diff --git a/Flax.flaxproj b/Flax.flaxproj
index 0c2683afb..681c1dc55 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -2,8 +2,8 @@
"Name": "Flax",
"Version": {
"Major": 1,
- "Minor": 0,
- "Build": 6216
+ "Minor": 1,
+ "Build": 6218
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.",
diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings
index b940e9c03..c91a48bfe 100644
--- a/Flax.sln.DotSettings
+++ b/Flax.sln.DotSettings
@@ -234,6 +234,7 @@
Deprecated
(?<=\W|^)(?<TAG>\[Deprecated)(\W|$)(.*)
Normal
+ True
True
True
True
diff --git a/PackageEditor.sh b/PackageEditor.sh
new file mode 100755
index 000000000..865013285
--- /dev/null
+++ b/PackageEditor.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+# Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
+
+set -e
+
+echo Building and packaging Flax Editor...
+
+# Change the path to the script root
+cd "`dirname "$0"`"
+
+# Run Flax.Build (also pass the arguments)
+bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployEditor --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
diff --git a/PackagePlatforms.sh b/PackagePlatforms.sh
index 83cf9f4a5..0e1ae76f8 100755
--- a/PackagePlatforms.sh
+++ b/PackagePlatforms.sh
@@ -8,5 +8,5 @@ echo Building and packaging platforms data...
# Change the path to the script root
cd "`dirname "$0"`"
-# Run Flax.Build to generate project files (also pass the arguments)
+# Run Flax.Build (also pass the arguments)
bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployPlatforms --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
diff --git a/README.md b/README.md
index f7f72e5c9..be785107d 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ Flax Visual Studio extension provides better programming workflow, C# scripts de
* Install Visual Studio Code
* Install Mono ([https://www.mono-project.com/download/stable](https://www.mono-project.com/download/stable))
* Install Git with LFS
-* Install requried packages: `sudo apt-get install nuget autoconf libtool libogg-dev automake build-essential gettext cmake python curl libtool-bin libx11-dev libpulse-dev libasound2-dev libjack-dev portaudio19-dev libcurl4-gnutls-dev`
+* Install requried packages: `sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev nuget autoconf libogg-dev automake build-essential gettext cmake python libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev`
* Install compiler `sudo apt-get install clang lldb lld` (Clang 6 or newer)
* Clone repo (with LFS)
* Run `./GenerateProjectFiles.sh`
diff --git a/Source/Editor/Analytics/EditorAnalytics.cpp b/Source/Editor/Analytics/EditorAnalytics.cpp
index 3f067719e..c2a293c15 100644
--- a/Source/Editor/Analytics/EditorAnalytics.cpp
+++ b/Source/Editor/Analytics/EditorAnalytics.cpp
@@ -10,6 +10,7 @@
#include "Editor/Editor.h"
#include "Editor/ProjectInfo.h"
#include "Engine/Engine/EngineService.h"
+#include "Engine/Engine/Globals.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Utilities/StringConverter.h"
#include "FlaxEngine.Gen.h"
diff --git a/Source/Editor/Content/Create/SettingsCreateEntry.cs b/Source/Editor/Content/Create/SettingsCreateEntry.cs
index 369cc01e5..974de76cc 100644
--- a/Source/Editor/Content/Create/SettingsCreateEntry.cs
+++ b/Source/Editor/Content/Create/SettingsCreateEntry.cs
@@ -53,6 +53,11 @@ namespace FlaxEditor.Content.Create
///
NavigationSettings,
+ ///
+ /// The localization settings.
+ ///
+ LocalizationSettings,
+
///
/// The build settings.
///
@@ -92,6 +97,11 @@ namespace FlaxEditor.Content.Create
/// The Android settings
///
AndroidPlatformSettings,
+
+ ///
+ /// The Switch settings
+ ///
+ SwitchPlatformSettings,
}
private static readonly Type[] _types =
@@ -103,6 +113,7 @@ namespace FlaxEditor.Content.Create
typeof(PhysicsSettings),
typeof(GraphicsSettings),
typeof(NavigationSettings),
+ typeof(LocalizationSettings),
typeof(BuildSettings),
typeof(InputSettings),
typeof(WindowsPlatformSettings),
@@ -111,6 +122,7 @@ namespace FlaxEditor.Content.Create
TypeUtils.GetManagedType(GameSettings.PS4PlatformSettingsTypename),
TypeUtils.GetManagedType(GameSettings.XboxScarlettPlatformSettingsTypename),
typeof(AndroidPlatformSettings),
+ TypeUtils.GetManagedType(GameSettings.SwitchPlatformSettingsTypename),
};
///
diff --git a/Source/Editor/Content/Import/ImportFileEntry.cs b/Source/Editor/Content/Import/ImportFileEntry.cs
index 80cab94d3..5d21d3726 100644
--- a/Source/Editor/Content/Import/ImportFileEntry.cs
+++ b/Source/Editor/Content/Import/ImportFileEntry.cs
@@ -116,8 +116,7 @@ namespace FlaxEditor.Content.Import
if (FileTypes.TryGetValue(extension, out ImportFileEntryHandler createDelegate))
return createDelegate(ref request);
- // Use default type
- return request.IsBinaryAsset ? new AssetImportEntry(ref request) : new ImportFileEntry(ref request);
+ return request.IsInBuilt ? new AssetImportEntry(ref request) : new ImportFileEntry(ref request);
}
internal static void RegisterDefaultTypes()
diff --git a/Source/Editor/Content/Import/Request.cs b/Source/Editor/Content/Import/Request.cs
index b9da30afb..d38b0bfd0 100644
--- a/Source/Editor/Content/Import/Request.cs
+++ b/Source/Editor/Content/Import/Request.cs
@@ -21,9 +21,9 @@ namespace FlaxEditor.Content.Import
public string OutputPath;
///
- /// Flag set to true for binary assets handled by the engine internally.
+ /// Flag set to true for the assets handled by the engine internally.
///
- public bool IsBinaryAsset;
+ public bool IsInBuilt;
///
/// Flag used to skip showing import settings dialog to used. Can be used for importing assets from code by plugins.
diff --git a/Source/Editor/Content/Import/TextureImportEntry.cs b/Source/Editor/Content/Import/TextureImportEntry.cs
index c276dce1b..db1e3ae8c 100644
--- a/Source/Editor/Content/Import/TextureImportEntry.cs
+++ b/Source/Editor/Content/Import/TextureImportEntry.cs
@@ -483,7 +483,9 @@ namespace FlaxEditor.Content.Import
{
if (settings is TextureImportSettings o)
{
+ var sprites = o.Sprites ?? _settings.Sprites; // Preserve sprites if not specified to override
_settings = o;
+ _settings.Sprites = sprites;
return true;
}
return false;
diff --git a/Source/Editor/Content/Items/CSharpScriptItem.cs b/Source/Editor/Content/Items/CSharpScriptItem.cs
index 6160de2bb..332b48558 100644
--- a/Source/Editor/Content/Items/CSharpScriptItem.cs
+++ b/Source/Editor/Content/Items/CSharpScriptItem.cs
@@ -20,6 +20,6 @@ namespace FlaxEditor.Content
}
///
- public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.CSharpScript64;
+ public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.CSharpScript128;
}
}
diff --git a/Source/Editor/Content/Items/ContentFolder.cs b/Source/Editor/Content/Items/ContentFolder.cs
index 8ae934b78..8c7e52b2c 100644
--- a/Source/Editor/Content/Items/ContentFolder.cs
+++ b/Source/Editor/Content/Items/ContentFolder.cs
@@ -121,7 +121,7 @@ namespace FlaxEditor.Content
public override bool Exists => Directory.Exists(Path);
///
- public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Folder64;
+ public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Folder128;
///
internal override void UpdatePath(string value)
diff --git a/Source/Editor/Content/Items/ContentItem.cs b/Source/Editor/Content/Items/ContentItem.cs
index 2e75de940..9813cd44e 100644
--- a/Source/Editor/Content/Items/ContentItem.cs
+++ b/Source/Editor/Content/Items/ContentItem.cs
@@ -455,7 +455,7 @@ namespace FlaxEditor.Content
const float thumbnailInShadowSize = 50.0f;
var shadowRect = rectangle.MakeExpanded((DefaultThumbnailSize - thumbnailInShadowSize) * rectangle.Width / DefaultThumbnailSize * 1.3f);
if (!_shadowIcon.IsValid)
- _shadowIcon = Editor.Instance.Icons.AssetShadow;
+ _shadowIcon = Editor.Instance.Icons.AssetShadow128;
Render2D.DrawSprite(_shadowIcon, shadowRect);
}
diff --git a/Source/Editor/Content/Items/CppScriptItem.cs b/Source/Editor/Content/Items/CppScriptItem.cs
index 1f7bc9f19..1ffead58c 100644
--- a/Source/Editor/Content/Items/CppScriptItem.cs
+++ b/Source/Editor/Content/Items/CppScriptItem.cs
@@ -20,6 +20,6 @@ namespace FlaxEditor.Content
}
///
- public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.CppScript64;
+ public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.CPPScript128;
}
}
diff --git a/Source/Editor/Content/Items/FileItem.cs b/Source/Editor/Content/Items/FileItem.cs
index 7a3d0fa65..a078178d8 100644
--- a/Source/Editor/Content/Items/FileItem.cs
+++ b/Source/Editor/Content/Items/FileItem.cs
@@ -26,6 +26,6 @@ namespace FlaxEditor.Content
public override ContentItemSearchFilter SearchFilter => ContentItemSearchFilter.Other;
///
- public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document64;
+ public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document128;
}
}
diff --git a/Source/Editor/Content/Items/JsonAssetItem.cs b/Source/Editor/Content/Items/JsonAssetItem.cs
index 6e32ad49a..1c9232c9c 100644
--- a/Source/Editor/Content/Items/JsonAssetItem.cs
+++ b/Source/Editor/Content/Items/JsonAssetItem.cs
@@ -11,6 +11,8 @@ namespace FlaxEditor.Content
///
public class JsonAssetItem : AssetItem
{
+ private readonly SpriteHandle _thumbnail;
+
///
/// Initializes a new instance of the class.
///
@@ -20,13 +22,27 @@ namespace FlaxEditor.Content
public JsonAssetItem(string path, Guid id, string typeName)
: base(path, typeName, ref id)
{
+ _thumbnail = Editor.Instance.Icons.Document128;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The path.
+ /// The identifier.
+ /// Name of the resource type.
+ /// Asset icon.
+ public JsonAssetItem(string path, Guid id, string typeName, SpriteHandle thumbnail)
+ : base(path, typeName, ref id)
+ {
+ _thumbnail = thumbnail;
}
///
public override ContentItemSearchFilter SearchFilter => ContentItemSearchFilter.Json;
///
- public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document64;
+ public override SpriteHandle DefaultThumbnail => _thumbnail;
///
protected override bool DrawShadow => false;
diff --git a/Source/Editor/Content/Items/NewItem.cs b/Source/Editor/Content/Items/NewItem.cs
index da06c6bfd..366e07df8 100644
--- a/Source/Editor/Content/Items/NewItem.cs
+++ b/Source/Editor/Content/Items/NewItem.cs
@@ -40,7 +40,7 @@ namespace FlaxEditor.Content
public override ContentItemSearchFilter SearchFilter => ContentItemSearchFilter.Other;
///
- public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document64;
+ public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document128;
///
protected override bool DrawShadow => true;
diff --git a/Source/Editor/Content/Items/SceneItem.cs b/Source/Editor/Content/Items/SceneItem.cs
index 190fe9807..15b79e96b 100644
--- a/Source/Editor/Content/Items/SceneItem.cs
+++ b/Source/Editor/Content/Items/SceneItem.cs
@@ -28,7 +28,7 @@ namespace FlaxEditor.Content
public override ContentItemSearchFilter SearchFilter => ContentItemSearchFilter.Scene;
///
- public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Scene64;
+ public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Scene128;
///
public override bool IsOfType(Type type)
diff --git a/Source/Editor/Content/Items/ShaderSourceItem.cs b/Source/Editor/Content/Items/ShaderSourceItem.cs
index 77cd6d6ee..7ae04257b 100644
--- a/Source/Editor/Content/Items/ShaderSourceItem.cs
+++ b/Source/Editor/Content/Items/ShaderSourceItem.cs
@@ -27,6 +27,6 @@ namespace FlaxEditor.Content
public override ContentItemSearchFilter SearchFilter => ContentItemSearchFilter.Shader;
///
- public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document64;
+ public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document128;
}
}
diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs
index fd23887ec..04311644b 100644
--- a/Source/Editor/Content/Items/VisualScriptItem.cs
+++ b/Source/Editor/Content/Items/VisualScriptItem.cs
@@ -75,6 +75,9 @@ namespace FlaxEditor.Content
}
}
+ ///
+ public int MetadataToken => 0;
+
///
public bool HasAttribute(Type attributeType, bool inherit)
{
@@ -195,6 +198,8 @@ namespace FlaxEditor.Content
///
public ScriptType ValueType => _returnType;
+ public int MetadataToken => 0;
+
///
public bool HasAttribute(Type attributeType, bool inherit)
{
@@ -534,7 +539,7 @@ namespace FlaxEditor.Content
}
///
- public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.CodeScript64;
+ public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.VisualScript128;
///
protected override bool DrawShadow => false;
diff --git a/Source/Editor/Content/PreviewsCache.cpp b/Source/Editor/Content/PreviewsCache.cpp
index c51e8cabb..c89b00004 100644
--- a/Source/Editor/Content/PreviewsCache.cpp
+++ b/Source/Editor/Content/PreviewsCache.cpp
@@ -76,7 +76,7 @@ void PreviewsCache::FlushTask::OnEnd()
ThreadPoolTask::OnEnd();
}
-REGISTER_BINARY_ASSET(PreviewsCache, "FlaxEditor.PreviewsCache", ::New(), false);
+REGISTER_BINARY_ASSET_WITH_UPGRADER(PreviewsCache, "FlaxEditor.PreviewsCache", TextureAssetUpgrader, false);
PreviewsCache::PreviewsCache(const SpawnParams& params, const AssetInfo* info)
: SpriteAtlas(params, info)
diff --git a/Source/Editor/Content/Proxy/ContentProxy.cs b/Source/Editor/Content/Proxy/ContentProxy.cs
index 0948ef5e8..afb5c11fb 100644
--- a/Source/Editor/Content/Proxy/ContentProxy.cs
+++ b/Source/Editor/Content/Proxy/ContentProxy.cs
@@ -73,6 +73,16 @@ namespace FlaxEditor.Content
throw new NotImplementedException();
}
+ ///
+ /// Determines whether the specified filename is valid for this proxy.
+ ///
+ /// The filename.
+ /// true if the filename is valid, otherwise false.
+ public virtual bool IsFileNameValid(string filename)
+ {
+ return true;
+ }
+
///
/// Determines whether this proxy can create items in the specified target location.
///
diff --git a/Source/Editor/Content/Proxy/CppProxy.cs b/Source/Editor/Content/Proxy/CppProxy.cs
new file mode 100644
index 000000000..9ced48653
--- /dev/null
+++ b/Source/Editor/Content/Proxy/CppProxy.cs
@@ -0,0 +1,134 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+using System;
+using System.IO;
+using System.Text;
+using FlaxEditor.Content.Settings;
+using FlaxEngine;
+
+namespace FlaxEditor.Content
+{
+ ///
+ /// Context proxy object for C++ files.
+ ///
+ ///
+ public abstract class CppProxy : ScriptProxy
+ {
+ ///
+ /// Gets the paths for header and source files to format.
+ ///
+ /// The header template path.
+ /// The source template path.
+ protected abstract void GetTemplatePaths(out string headerTemplate, out string sourceTemplate);
+
+ ///
+ public override bool IsProxyFor(ContentItem item)
+ {
+ return false;
+ }
+
+ ///
+ public override void Create(string outputPath, object arg)
+ {
+ // Find the module that this script is being added (based on the path)
+ var module = string.Empty;
+ var project = TryGetProjectAtFolder(outputPath, out var moduleName);
+ if (project != null)
+ {
+ module = moduleName.ToUpperInvariant() + "_API ";
+ }
+
+ var gameSettings = GameSettings.Load();
+ var scriptName = ScriptItem.CreateScriptName(outputPath);
+ var filename = Path.GetFileNameWithoutExtension(outputPath);
+ var copyrightComment = string.IsNullOrEmpty(gameSettings.CopyrightNotice) ? string.Empty : string.Format("// {0}{1}{1}", gameSettings.CopyrightNotice, Environment.NewLine);
+
+ GetTemplatePaths(out var headerTemplatePath, out var sourceTemplatePath);
+ if (headerTemplatePath != null)
+ {
+ var headerTemplate = File.ReadAllText(headerTemplatePath);
+ headerTemplate = headerTemplate.Replace("%copyright%", copyrightComment);
+ headerTemplate = headerTemplate.Replace("%class%", scriptName);
+ headerTemplate = headerTemplate.Replace("%module%", module);
+ headerTemplate = headerTemplate.Replace("%filename%", filename);
+ File.WriteAllText(Path.ChangeExtension(outputPath, ".h"), headerTemplate, Encoding.UTF8);
+ }
+ if (sourceTemplatePath != null)
+ {
+ var sourceTemplate = File.ReadAllText(sourceTemplatePath);
+ sourceTemplate = sourceTemplate.Replace("%copyright%", copyrightComment);
+ sourceTemplate = sourceTemplate.Replace("%class%", scriptName);
+ sourceTemplate = sourceTemplate.Replace("%module%", module);
+ sourceTemplate = sourceTemplate.Replace("%filename%", filename);
+ File.WriteAllText(outputPath, sourceTemplate, Encoding.UTF8);
+ }
+ }
+
+ ///
+ public override string FileExtension => "cpp";
+
+ ///
+ public override Color AccentColor => Color.FromRGB(0x9c1c9c);
+ }
+
+ ///
+ /// Context proxy object for C++ script files.
+ ///
+ ///
+ public class CppScriptProxy : CppProxy
+ {
+ ///
+ public override string Name => "C++ Script";
+
+ ///
+ public override bool IsProxyFor(ContentItem item)
+ {
+ return item is CppScriptItem;
+ }
+
+ ///
+ protected override void GetTemplatePaths(out string headerTemplate, out string sourceTemplate)
+ {
+ headerTemplate = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/ScriptTemplate.h");
+ sourceTemplate = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/ScriptTemplate.cpp");
+ }
+ }
+
+ ///
+ /// Context proxy object for C++ Json Asset files.
+ ///
+ ///
+ public class CppStaticClassProxy : CppProxy
+ {
+ ///
+ public override string Name => "C++ Function Library";
+
+ ///
+ protected override void GetTemplatePaths(out string headerTemplate, out string sourceTemplate)
+ {
+ headerTemplate = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/CppStaticClassTemplate.h");
+ sourceTemplate = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/CppStaticClassTemplate.cpp");
+ }
+ }
+
+ ///
+ /// Context proxy object for C++ Json Asset files.
+ ///
+ ///
+ public class CppAssetProxy : CppProxy
+ {
+ ///
+ public override string Name => "C++ Json Asset";
+
+ ///
+ protected override void GetTemplatePaths(out string headerTemplate, out string sourceTemplate)
+ {
+ headerTemplate = null;
+ sourceTemplate = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/CppAssetTemplate.h");
+ //sourceTemplate = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/CppAssetTemplate.cpp");
+ }
+
+ ///
+ public override string FileExtension => "h";
+ }
+}
diff --git a/Source/Editor/Content/Proxy/CppScriptProxy.cs b/Source/Editor/Content/Proxy/CppScriptProxy.cs
deleted file mode 100644
index 8bad93021..000000000
--- a/Source/Editor/Content/Proxy/CppScriptProxy.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
-
-using System;
-using System.IO;
-using System.Text;
-using FlaxEditor.Content.Settings;
-using FlaxEngine;
-
-namespace FlaxEditor.Content
-{
- ///
- /// Context proxy object for C++ script files.
- ///
- ///
- public class CppScriptProxy : ScriptProxy
- {
- ///
- public override string Name => "C++ Script";
-
- ///
- public override bool IsProxyFor(ContentItem item)
- {
- return item is CppScriptItem;
- }
-
- ///
- public override void Create(string outputPath, object arg)
- {
- // Load templates
- var headerTemplate = File.ReadAllText(StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/ScriptTemplate.h"));
- var sourceTemplate = File.ReadAllText(StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/Scripting/ScriptTemplate.cpp"));
-
- // Find the module that this script is being added (based on the path)
- var module = string.Empty;
- var project = TryGetProjectAtFolder(outputPath, out var moduleName);
- if (project != null)
- {
- module = moduleName.ToUpperInvariant() + "_API ";
- }
-
- // Format
- var gameSettings = GameSettings.Load();
- var scriptName = ScriptItem.CreateScriptName(outputPath);
- var filename = Path.GetFileNameWithoutExtension(outputPath);
- var copyrightComment = string.IsNullOrEmpty(gameSettings.CopyrightNotice) ? string.Empty : string.Format("// {0}{1}{1}", gameSettings.CopyrightNotice, Environment.NewLine);
- headerTemplate = headerTemplate.Replace("%copyright%", copyrightComment);
- headerTemplate = headerTemplate.Replace("%class%", scriptName);
- headerTemplate = headerTemplate.Replace("%module%", module);
- sourceTemplate = sourceTemplate.Replace("%filename%", filename);
- sourceTemplate = sourceTemplate.Replace("%copyright%", copyrightComment);
- sourceTemplate = sourceTemplate.Replace("%class%", scriptName);
- sourceTemplate = sourceTemplate.Replace("%filename%", filename);
-
- // Save
- File.WriteAllText(Path.ChangeExtension(outputPath, ".h"), headerTemplate, Encoding.UTF8);
- File.WriteAllText(outputPath, sourceTemplate, Encoding.UTF8);
- }
-
- ///
- public override string FileExtension => "cpp";
-
- ///
- public override Color AccentColor => Color.FromRGB(0x9c1c9c);
- }
-}
diff --git a/Source/Editor/Content/Proxy/JsonAssetProxy.cs b/Source/Editor/Content/Proxy/JsonAssetProxy.cs
index 25155d39d..41cc06ee1 100644
--- a/Source/Editor/Content/Proxy/JsonAssetProxy.cs
+++ b/Source/Editor/Content/Proxy/JsonAssetProxy.cs
@@ -2,7 +2,6 @@
using System;
using FlaxEditor.Content.Create;
-using FlaxEditor.Content.Settings;
using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.Windows;
@@ -45,18 +44,12 @@ namespace FlaxEditor.Content
///
public override bool IsProxyFor(ContentItem item)
{
- return item is JsonAssetItem;
+ return item is JsonAssetItem json && json.TypeName == TypeName;
}
///
public override Color AccentColor => Color.FromRGB(0xd14f67);
- ///
- public override bool AcceptsAsset(string typeName, string path)
- {
- return typeName == TypeName && base.AcceptsAsset(typeName, path);
- }
-
///
public override AssetItem ConstructItem(string path, string typeName, ref Guid id)
{
@@ -143,6 +136,12 @@ namespace FlaxEditor.Content
return path.EndsWith(FileExtension, StringComparison.OrdinalIgnoreCase);
}
+ ///
+ public override bool IsProxyFor(ContentItem item)
+ {
+ return item is JsonAssetItem;
+ }
+
///
public override bool CanCreate(ContentFolder targetLocation)
{
diff --git a/Source/Editor/Content/Proxy/LocalizedStringTableProxy.cs b/Source/Editor/Content/Proxy/LocalizedStringTableProxy.cs
new file mode 100644
index 000000000..2e15f9543
--- /dev/null
+++ b/Source/Editor/Content/Proxy/LocalizedStringTableProxy.cs
@@ -0,0 +1,24 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+using FlaxEditor.Windows;
+using FlaxEditor.Windows.Assets;
+using FlaxEngine;
+
+namespace FlaxEditor.Content
+{
+ ///
+ /// proxy.
+ ///
+ ///
+ public class LocalizedStringTableProxy : JsonAssetProxy
+ {
+ ///
+ public override EditorWindow Open(Editor editor, ContentItem item)
+ {
+ return new LocalizedStringTableWindow(editor, (JsonAssetItem)item);
+ }
+
+ ///
+ public override string TypeName => "FlaxEngine.LocalizedStringTable";
+ }
+}
diff --git a/Source/Editor/Content/Proxy/ScriptProxy.cs b/Source/Editor/Content/Proxy/ScriptProxy.cs
index 89a08bb4f..0c8140d1a 100644
--- a/Source/Editor/Content/Proxy/ScriptProxy.cs
+++ b/Source/Editor/Content/Proxy/ScriptProxy.cs
@@ -66,6 +66,15 @@ namespace FlaxEditor.Content
return false;
}
+ ///
+ public override bool IsFileNameValid(string filename)
+ {
+ // Scripts cannot start with digit.
+ if (Char.IsDigit(filename[0]))
+ return false;
+ return true;
+ }
+
///
public override void Create(string outputPath, object arg)
{
diff --git a/Source/Editor/Content/Proxy/SettingsProxy.cs b/Source/Editor/Content/Proxy/SettingsProxy.cs
index a6b74b0ea..2bebc3254 100644
--- a/Source/Editor/Content/Proxy/SettingsProxy.cs
+++ b/Source/Editor/Content/Proxy/SettingsProxy.cs
@@ -3,6 +3,7 @@
using System;
using FlaxEditor.Content.Create;
using FlaxEditor.Content.Settings;
+using FlaxEngine;
namespace FlaxEditor.Content
{
@@ -13,15 +14,18 @@ namespace FlaxEditor.Content
public sealed class SettingsProxy : JsonAssetProxy
{
private readonly Type _type;
+ private readonly SpriteHandle _thumbnail;
///
/// Initializes a new instance of the class.
///
/// The settings asset type (must be subclass of SettingsBase type).
- public SettingsProxy(Type type)
+ /// Asset icon.
+ public SettingsProxy(Type type, SpriteHandle thumbnail)
{
_type = type;
TypeName = type.FullName;
+ _thumbnail = thumbnail;
}
///
@@ -44,6 +48,12 @@ namespace FlaxEditor.Content
Editor.Instance.ContentImporting.Create(new SettingsCreateEntry(outputPath));
}
+ ///
+ public override AssetItem ConstructItem(string path, string typeName, ref Guid id)
+ {
+ return new JsonAssetItem(path, id, typeName, _thumbnail);
+ }
+
///
public override bool IsProxyFor()
{
diff --git a/Source/Editor/Content/Proxy/ShaderProxy.cs b/Source/Editor/Content/Proxy/ShaderProxy.cs
index e6e288318..71350e277 100644
--- a/Source/Editor/Content/Proxy/ShaderProxy.cs
+++ b/Source/Editor/Content/Proxy/ShaderProxy.cs
@@ -29,7 +29,7 @@ namespace FlaxEditor.Content
if (asset)
{
var source = Editor.GetShaderSourceCode(asset);
- Utilities.Utils.ShowSourceCodeWindow(source, "Shader Source");
+ Utilities.Utils.ShowSourceCodeWindow(source, "Shader Source", item.RootWindow.Window);
}
return null;
}
diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs
index 78cc7f2b5..9179c3c07 100644
--- a/Source/Editor/Content/Tree/ContentTreeNode.cs
+++ b/Source/Editor/Content/Tree/ContentTreeNode.cs
@@ -77,7 +77,7 @@ namespace FlaxEditor.Content
/// The folder type.
/// The folder path.
protected ContentTreeNode(ContentTreeNode parent, ContentFolderType type, string path)
- : base(false, Editor.Instance.Icons.FolderClosed12, Editor.Instance.Icons.FolderOpened12)
+ : base(false, Editor.Instance.Icons.FolderClosed32, Editor.Instance.Icons.FolderOpen32)
{
_folder = new ContentFolder(type, path, this);
Text = _folder.ShortName;
diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h
index 56b15af23..77774a9df 100644
--- a/Source/Editor/Cooker/CookingData.h
+++ b/Source/Editor/Cooker/CookingData.h
@@ -7,6 +7,7 @@
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/HashSet.h"
#include "Engine/Core/Collections/Dictionary.h"
+#include "Engine/Core/Types/Guid.h"
class GameCooker;
class PlatformTools;
@@ -87,34 +88,14 @@ API_ENUM() enum class BuildPlatform
///
API_ENUM(Attributes="EditorDisplay(null, \"Android ARM64 (arm64-v8a)\")")
AndroidARM64 = 9,
+
+ ///
+ /// Switch.
+ ///
+ Switch = 10,
};
-inline const Char* ToString(const BuildPlatform platform)
-{
- switch (platform)
- {
- case BuildPlatform::Windows32:
- return TEXT("Windows x86");
- case BuildPlatform::Windows64:
- return TEXT("Windows x64");
- case BuildPlatform::UWPx86:
- return TEXT("Windows Store x86");
- case BuildPlatform::UWPx64:
- return TEXT("Windows Store x64");
- case BuildPlatform::XboxOne:
- return TEXT("Xbox One");
- case BuildPlatform::LinuxX64:
- return TEXT("Linux x64");
- case BuildPlatform::PS4:
- return TEXT("PlayStation 4");
- case BuildPlatform::XboxScarlett:
- return TEXT("Xbox Scarlett");
- case BuildPlatform::AndroidARM64:
- return TEXT("Android ARM64");
- default:
- return TEXT("?");
- }
-}
+extern FLAXENGINE_API const Char* ToString(const BuildPlatform platform);
///
/// Game build configuration modes.
@@ -137,20 +118,7 @@ API_ENUM() enum class BuildConfiguration
Release = 2,
};
-inline const Char* ToString(const BuildConfiguration configuration)
-{
- switch (configuration)
- {
- case BuildConfiguration::Debug:
- return TEXT("Debug");
- case BuildConfiguration::Development:
- return TEXT("Development");
- case BuildConfiguration::Release:
- return TEXT("Release");
- default:
- return TEXT("?");
- }
-}
+extern FLAXENGINE_API const Char* ToString(const BuildConfiguration configuration);
#define BUILD_STEP_CANCEL_CHECK if (GameCooker::IsCancelRequested()) return true
@@ -185,9 +153,14 @@ struct FLAXENGINE_API CookingData
String OriginalOutputPath;
///
- /// The output path.
+ /// The output path for data files (Content, Mono, etc.).
///
- String OutputPath;
+ String DataOutputPath;
+
+ ///
+ /// The output path for binaries (executable and code libraries).
+ ///
+ String CodeOutputPath;
///
/// The platform tools.
diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp
index 9eca81eba..f64045ff5 100644
--- a/Source/Editor/Cooker/GameCooker.cpp
+++ b/Source/Editor/Cooker/GameCooker.cpp
@@ -10,6 +10,7 @@
#include "Engine/Serialization/JsonTools.h"
#include "Engine/Content/Content.h"
#include "Engine/Engine/EngineService.h"
+#include "Engine/Engine/Globals.h"
#include "Engine/Threading/ThreadSpawner.h"
#include "Engine/Platform/FileSystem.h"
#include "Steps/ValidateStep.h"
@@ -39,7 +40,6 @@
#endif
#if PLATFORM_TOOLS_PS4
#include "Platforms/PS4/Editor/PlatformTools/PS4PlatformTools.h"
-#include "Platforms/PS4/Engine/Platform/PS4PlatformSettings.h"
#endif
#if PLATFORM_TOOLS_XBOX_SCARLETT
#include "Platforms/XboxScarlett/Editor/PlatformTools/XboxScarlettPlatformTools.h"
@@ -47,6 +47,9 @@
#if PLATFORM_TOOLS_ANDROID
#include "Platform/Android/AndroidPlatformTools.h"
#endif
+#if PLATFORM_TOOLS_SWITCH
+#include "Platforms/Switch/Editor/PlatformTools/SwitchPlatformTools.h"
+#endif
namespace GameCookerImpl
{
@@ -89,6 +92,50 @@ using namespace GameCookerImpl;
Delegate GameCooker::OnEvent;
Delegate GameCooker::OnProgress;
+const Char* ToString(const BuildPlatform platform)
+{
+ switch (platform)
+ {
+ case BuildPlatform::Windows32:
+ return TEXT("Windows x86");
+ case BuildPlatform::Windows64:
+ return TEXT("Windows x64");
+ case BuildPlatform::UWPx86:
+ return TEXT("Windows Store x86");
+ case BuildPlatform::UWPx64:
+ return TEXT("Windows Store x64");
+ case BuildPlatform::XboxOne:
+ return TEXT("Xbox One");
+ case BuildPlatform::LinuxX64:
+ return TEXT("Linux x64");
+ case BuildPlatform::PS4:
+ return TEXT("PlayStation 4");
+ case BuildPlatform::XboxScarlett:
+ return TEXT("Xbox Scarlett");
+ case BuildPlatform::AndroidARM64:
+ return TEXT("Android ARM64");
+ case BuildPlatform::Switch:
+ return TEXT("Switch");
+ default:
+ return TEXT("?");
+ }
+}
+
+const Char* ToString(const BuildConfiguration configuration)
+{
+ switch (configuration)
+ {
+ case BuildConfiguration::Debug:
+ return TEXT("Debug");
+ case BuildConfiguration::Development:
+ return TEXT("Development");
+ case BuildConfiguration::Release:
+ return TEXT("Release");
+ default:
+ return TEXT("?");
+ }
+}
+
bool CookingData::AssetTypeStatistics::operator<(const AssetTypeStatistics& other) const
{
if (ContentSize != other.ContentSize)
@@ -250,6 +297,11 @@ PlatformTools* GameCooker::GetTools(BuildPlatform platform)
case BuildPlatform::AndroidARM64:
result = New(ArchitectureType::ARM64);
break;
+#endif
+#if PLATFORM_TOOLS_SWITCH
+ case BuildPlatform::Switch:
+ result = New();
+ break;
#endif
}
Tools.Add(platform, result);
@@ -282,9 +334,10 @@ void GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration,
data.Configuration = configuration;
data.Options = options;
data.CustomDefines = customDefines;
- data.OutputPath = outputPath;
- FileSystem::NormalizePath(data.OutputPath);
- data.OutputPath = data.OriginalOutputPath = FileSystem::ConvertRelativePathToAbsolute(Globals::ProjectFolder, data.OutputPath);
+ data.OriginalOutputPath = outputPath;
+ FileSystem::NormalizePath(data.OriginalOutputPath);
+ data.OriginalOutputPath = FileSystem::ConvertRelativePathToAbsolute(Globals::ProjectFolder, data.OriginalOutputPath);
+ data.CodeOutputPath = data.DataOutputPath = data.OriginalOutputPath;
data.CacheDirectory = Globals::ProjectCacheFolder / TEXT("Cooker") / tools->GetName();
if (!FileSystem::DirectoryExists(data.CacheDirectory))
{
@@ -367,7 +420,7 @@ bool GameCookerImpl::Build()
CookingData& data = Data;
LOG(Info, "Starting Game Cooker...");
LOG(Info, "Platform: {0}, Configuration: {2}, Options: {1}", ::ToString(data.Platform), (int32)data.Options, ::ToString(data.Configuration));
- LOG(Info, "Output Path: {0}", data.OutputPath);
+ LOG(Info, "Output Path: {0}", data.OriginalOutputPath);
// Late init feature
if (Steps.IsEmpty())
diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
index fcc53f26a..3987fcb4a 100644
--- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
@@ -104,7 +104,8 @@ PixelFormat AndroidPlatformTools::GetTextureFormat(CookingData& data, TextureBas
void AndroidPlatformTools::OnBuildStarted(CookingData& data)
{
// Adjust the cooking output folder to be located inside the Gradle assets directory
- data.OutputPath /= TEXT("app/assets");
+ data.DataOutputPath /= TEXT("app/assets");
+ data.CodeOutputPath /= TEXT("app/assets");
PlatformTools::OnBuildStarted(data);
}
@@ -114,7 +115,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
const auto gameSettings = GameSettings::Get();
const auto platformSettings = AndroidPlatformSettings::Get();
const auto platformDataPath = data.GetPlatformBinariesRoot();
- const auto assetsPath = data.OutputPath;
+ const auto assetsPath = data.DataOutputPath;
const auto jniLibsPath = data.OriginalOutputPath / TEXT("app/jniLibs");
const auto projectVersion = Editor::Project->Version.ToString();
const Char* abi;
diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
index fa47c5ee5..57562f537 100644
--- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
@@ -38,7 +38,7 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data)
{
const auto gameSettings = GameSettings::Get();
const auto platformSettings = LinuxPlatformSettings::Get();
- const auto outputPath = data.OutputPath;
+ const auto outputPath = data.DataOutputPath;
// Copy binaries
{
@@ -79,6 +79,11 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data)
const String gameExePath = outputPath / TEXT("FlaxGame");
#endif
+ // Ensure the output binary can be executed
+#if PLATFORM_LINUX
+ system(*StringAnsi(String::Format(TEXT("chmod +x \"{0}\""), gameExePath)));
+#endif
+
// Apply game icon
TextureData iconData;
if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData))
diff --git a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp
index c8f9e45a7..e6516bf67 100644
--- a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp
@@ -25,7 +25,7 @@ bool UWPPlatformTools::OnScriptsStepDone(CookingData& data)
{
// Override Newtonsoft.Json.dll for some platforms (that don't support runtime code generation)
const String customBinPath = data.GetPlatformBinariesRoot() / TEXT("Newtonsoft.Json.dll");
- const String assembliesPath = data.OutputPath;
+ const String assembliesPath = data.CodeOutputPath;
if (FileSystem::CopyFile(assembliesPath / TEXT("Newtonsoft.Json.dll"), customBinPath))
{
data.Error(TEXT("Failed to copy deploy custom assembly."));
@@ -43,7 +43,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
const auto uwpDataPath = platformDataPath / (isXboxOne ? TEXT("XboxOne") : TEXT("UWP")) / TEXT("Binaries");
const auto gameSettings = GameSettings::Get();
const auto platformSettings = UWPPlatformSettings::Get();
- Array fileTemplate;
+ StringAnsi fileTemplate;
// Copy binaries
const auto binPath = data.GetGameBinariesPath();
@@ -64,7 +64,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
return true;
}
- if (FileSystem::CopyFile(data.OutputPath / StringUtils::GetFileName(files[i]), files[i]))
+ if (FileSystem::CopyFile(data.DataOutputPath / StringUtils::GetFileName(files[i]), files[i]))
{
data.Error(TEXT("Failed to setup output directory."));
return true;
@@ -92,7 +92,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
// Prepare certificate
const auto srcCertificatePath = Globals::ProjectFolder / platformSettings->CertificateLocation;
- const auto dstCertificatePath = data.OutputPath / TEXT("WSACertificate.pfx");
+ const auto dstCertificatePath = data.DataOutputPath / TEXT("WSACertificate.pfx");
if (platformSettings->CertificateLocation.HasChars() && FileSystem::FileExists(srcCertificatePath))
{
// Use cert from settings
@@ -115,7 +115,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
}
// Copy assets
- const auto dstAssetsPath = data.OutputPath / TEXT("Assets");
+ const auto dstAssetsPath = data.DataOutputPath / TEXT("Assets");
const auto srcAssetsPath = uwpDataPath / TEXT("Assets");
if (!FileSystem::DirectoryExists(dstAssetsPath))
{
@@ -125,7 +125,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
return true;
}
}
- const auto dstPropertiesPath = data.OutputPath / TEXT("Properties");
+ const auto dstPropertiesPath = data.DataOutputPath / TEXT("Properties");
if (!FileSystem::DirectoryExists(dstPropertiesPath))
{
if (FileSystem::CreateDirectory(dstPropertiesPath))
@@ -149,12 +149,11 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
if (!FileSystem::FileExists(dstAssemblyInfoPath))
{
// Get template
- if (File::ReadAllBytes(srcAssemblyInfoPath, fileTemplate))
+ if (File::ReadAllText(srcAssemblyInfoPath, fileTemplate))
{
data.Error(TEXT("Failed to load AssemblyInfo.cs template."));
return true;
}
- fileTemplate[fileTemplate.Count() - 1] = 0;
// Write data to file
auto file = FileWriteStream::Open(dstAssemblyInfoPath);
@@ -163,7 +162,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
{
auto now = DateTime::Now();
file->WriteTextFormatted(
- (char*)fileTemplate.Get()
+ fileTemplate.Get()
, gameSettings->ProductName.ToStringAnsi()
, gameSettings->CompanyName.ToStringAnsi()
, now.GetYear()
@@ -177,17 +176,16 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
return true;
}
}
- const auto dstAppPath = data.OutputPath / TEXT("App.cs");
+ const auto dstAppPath = data.DataOutputPath / TEXT("App.cs");
const auto srcAppPath = uwpDataPath / TEXT("App.cs");
if (!FileSystem::FileExists(dstAppPath))
{
// Get template
- if (File::ReadAllBytes(srcAppPath, fileTemplate))
+ if (File::ReadAllText(srcAppPath, fileTemplate))
{
data.Error(TEXT("Failed to load App.cs template."));
return true;
}
- fileTemplate[fileTemplate.Count() - 1] = 0;
// Write data to file
auto file = FileWriteStream::Open(dstAppPath);
@@ -195,7 +193,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
if (file)
{
file->WriteTextFormatted(
- (char*)fileTemplate.Get()
+ fileTemplate.Get()
, defaultNamespace.ToStringAnsi() // {0} Default Namespace
);
hasError = file->HasError();
@@ -207,16 +205,15 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
return true;
}
}
- const auto dstFlaxGeneratedPath = data.OutputPath / TEXT("FlaxGenerated.cs");
+ const auto dstFlaxGeneratedPath = data.DataOutputPath / TEXT("FlaxGenerated.cs");
const auto srcFlaxGeneratedPath = uwpDataPath / TEXT("FlaxGenerated.cs");
{
// Get template
- if (File::ReadAllBytes(srcFlaxGeneratedPath, fileTemplate))
+ if (File::ReadAllText(srcFlaxGeneratedPath, fileTemplate))
{
data.Error(TEXT("Failed to load FlaxGenerated.cs template."));
return true;
}
- fileTemplate[fileTemplate.Count() - 1] = 0;
// Prepare
StringAnsi autoRotationPreferences;
@@ -252,7 +249,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
if (file)
{
file->WriteTextFormatted(
- (char*)fileTemplate.Get()
+ fileTemplate.Get()
, autoRotationPreferences.Get()
, preferredLaunchWindowingMode.Get()
);
@@ -267,17 +264,16 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
}
// Create solution
- const auto dstSolutionPath = data.OutputPath / projectName + TEXT(".sln");
+ const auto dstSolutionPath = data.DataOutputPath / projectName + TEXT(".sln");
const auto srcSolutionPath = uwpDataPath / TEXT("Solution.sln");
if (!FileSystem::FileExists(dstSolutionPath))
{
// Get template
- if (File::ReadAllBytes(srcSolutionPath, fileTemplate))
+ if (File::ReadAllText(srcSolutionPath, fileTemplate))
{
data.Error(TEXT("Failed to load Solution.sln template."));
return true;
}
- fileTemplate[fileTemplate.Count() - 1] = 0;
// Write data to file
auto file = FileWriteStream::Open(dstSolutionPath);
@@ -285,7 +281,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
if (file)
{
file->WriteTextFormatted(
- (char*)fileTemplate.Get()
+ fileTemplate.Get()
, projectName.ToStringAnsi() // {0} Project Name
, mode // {1} Platform Mode
, projectGuid.ToStringAnsi() // {2} Project ID
@@ -301,16 +297,15 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
}
// Create project
- const auto dstProjectPath = data.OutputPath / projectName + TEXT(".csproj");
+ const auto dstProjectPath = data.DataOutputPath / projectName + TEXT(".csproj");
const auto srcProjectPath = uwpDataPath / TEXT("Project.csproj");
{
// Get template
- if (File::ReadAllBytes(srcProjectPath, fileTemplate))
+ if (File::ReadAllText(srcProjectPath, fileTemplate))
{
data.Error(TEXT("Failed to load Project.csproj template."));
return true;
}
- fileTemplate[fileTemplate.Count() - 1] = 0;
// Build included files data
StringBuilder filesInclude(2048);
@@ -334,7 +329,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
if (file)
{
file->WriteTextFormatted(
- (char*)fileTemplate.Get()
+ fileTemplate.Get()
, projectName.ToStringAnsi() // {0} Project Name
, mode // {1} Platform Mode
, projectGuid.Get() // {2} Project ID
@@ -352,17 +347,16 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
}
// Create manifest
- const auto dstManifestPath = data.OutputPath / TEXT("Package.appxmanifest");
+ const auto dstManifestPath = data.DataOutputPath / TEXT("Package.appxmanifest");
const auto srcManifestPath = uwpDataPath / TEXT("Package.appxmanifest");
if (!FileSystem::FileExists(dstManifestPath))
{
// Get template
- if (File::ReadAllBytes(srcManifestPath, fileTemplate))
+ if (File::ReadAllText(srcManifestPath, fileTemplate))
{
data.Error(TEXT("Failed to load Package.appxmanifest template."));
return true;
}
- fileTemplate[fileTemplate.Count() - 1] = 0;
// Build included files data
StringBuilder filesInclude(2048);
@@ -385,7 +379,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
if (file)
{
file->WriteTextFormatted(
- (char*)fileTemplate.Get()
+ fileTemplate.Get()
, projectName.ToStringAnsi() // {0} Display Name
, gameSettings->CompanyName.ToStringAnsi() // {1} Company Name
, productId.ToStringAnsi() // {2} Product ID
@@ -490,8 +484,8 @@ bool UWPPlatformTools::OnPostProcess(CookingData& data)
// Special case for UWP
// FlaxEngine.dll cannot be added to the solution as `Content` item (due to conflicts with C++ /CX FlaxEngine.dll)
// Use special directory for it (generated UWP project handles this case and copies lib to the output)
- const String assembliesPath = data.OutputPath;
- const auto dstPath1 = data.OutputPath / TEXT("DataSecondary");
+ const String assembliesPath = data.DataOutputPath;
+ const auto dstPath1 = data.DataOutputPath / TEXT("DataSecondary");
if (!FileSystem::DirectoryExists(dstPath1))
{
if (FileSystem::CreateDirectory(dstPath1))
diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp
index 348930dd5..d5dc1c933 100644
--- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp
@@ -36,7 +36,7 @@ ArchitectureType WindowsPlatformTools::GetArchitecture() const
bool WindowsPlatformTools::OnDeployBinaries(CookingData& data)
{
const auto platformSettings = WindowsPlatformSettings::Get();
- const auto& outputPath = data.OutputPath;
+ const auto& outputPath = data.CodeOutputPath;
// Apply executable icon
Array files;
diff --git a/Source/Editor/Cooker/PlatformTools.h b/Source/Editor/Cooker/PlatformTools.h
index c98c008bc..20d578d2e 100644
--- a/Source/Editor/Cooker/PlatformTools.h
+++ b/Source/Editor/Cooker/PlatformTools.h
@@ -144,8 +144,8 @@ public:
AotConfig(CookingData& data)
{
Platform::GetEnvironmentVariables(EnvVars);
- EnvVars[TEXT("MONO_PATH")] = data.OutputPath / TEXT("Mono/lib/mono/4.5");
- AssembliesSearchDirs.Add(data.OutputPath / TEXT("Mono/lib/mono/4.5"));
+ EnvVars[TEXT("MONO_PATH")] = data.DataOutputPath / TEXT("Mono/lib/mono/4.5");
+ AssembliesSearchDirs.Add(data.DataOutputPath / TEXT("Mono/lib/mono/4.5"));
}
};
diff --git a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
index 91f22abae..5d3125960 100644
--- a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
+++ b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
@@ -101,7 +101,7 @@ bool CompileScriptsStep::DeployBinaries(CookingData& data, const String& path, c
// Deploy files
Array files(16);
const String outputPath = StringUtils::GetDirectoryName(path);
- FileSystem::DirectoryGetFiles(files, outputPath, TEXT("*.*"), DirectorySearchOption::TopDirectoryOnly);
+ FileSystem::DirectoryGetFiles(files, outputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
for (int32 i = files.Count() - 1; i >= 0; i--)
{
bool skip = false;
@@ -119,7 +119,7 @@ bool CompileScriptsStep::DeployBinaries(CookingData& data, const String& path, c
}
for (auto& file : files)
{
- const String dst = data.OutputPath / StringUtils::GetFileName(file);
+ const String dst = data.CodeOutputPath / StringUtils::GetFileName(file);
if (dst != file && FileSystem::CopyFile(dst, file))
{
data.Error(TEXT("Failed to copy file from {0} to {1}."), file, dst);
@@ -180,7 +180,12 @@ bool CompileScriptsStep::Perform(CookingData& data)
platform = TEXT("Android");
architecture = TEXT("ARM64");
break;
+ case BuildPlatform::Switch:
+ platform = TEXT("Switch");
+ architecture = TEXT("ARM64");
+ break;
default:
+ LOG(Error, "Unknown or unsupported build platform.");
return true;
}
_extensionsToSkip.Clear();
@@ -289,7 +294,7 @@ bool CompileScriptsStep::Perform(CookingData& data)
}
writer.EndObject();
- const String outputBuildInfo = data.OutputPath / TEXT("Game.Build.json");
+ const String outputBuildInfo = data.CodeOutputPath / TEXT("Game.Build.json");
if (File::WriteAllBytes(outputBuildInfo, (byte*)buffer.GetString(), (int32)buffer.GetSize()))
{
LOG(Error, "Failed to save binary modules info file {0}.", outputBuildInfo);
diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp
index be4dca78d..5f097f638 100644
--- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp
+++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp
@@ -29,6 +29,7 @@
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Engine/Base/GameBase.h"
+#include "Engine/Engine/Globals.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
#if PLATFORM_TOOLS_WINDOWS
#include "Engine/Platform/Windows/WindowsPlatformSettings.h"
@@ -368,6 +369,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
// Compile for a target platform
switch (data.Data.Platform)
{
+#if PLATFORM_TOOLS_WINDOWS
case BuildPlatform::Windows32:
case BuildPlatform::Windows64:
{
@@ -391,6 +393,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
}
break;
}
+#endif
#if PLATFORM_TOOLS_UWP
case BuildPlatform::UWPx86:
case BuildPlatform::UWPx64:
@@ -408,12 +411,14 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
break;
}
#endif
+#if PLATFORM_TOOLS_UWP
case BuildPlatform::XboxOne:
{
const char* platformDefineName = "PLATFORM_XBOX_ONE";
COMPILE_PROFILE(DirectX_SM4, SHADER_FILE_CHUNK_INTERNAL_D3D_SM4_CACHE);
break;
}
+#endif
#if PLATFORM_TOOLS_LINUX
case BuildPlatform::LinuxX64:
{
@@ -426,24 +431,38 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
break;
}
#endif
+#if PLATFORM_TOOLS_PS4
case BuildPlatform::PS4:
{
const char* platformDefineName = "PLATFORM_PS4";
COMPILE_PROFILE(PS4, SHADER_FILE_CHUNK_INTERNAL_GENERIC_CACHE);
break;
}
+#endif
+#if PLATFORM_TOOLS_XBOX_SCARLETT
case BuildPlatform::XboxScarlett:
{
const char* platformDefineName = "PLATFORM_XBOX_SCARLETT";
COMPILE_PROFILE(DirectX_SM6, SHADER_FILE_CHUNK_INTERNAL_D3D_SM6_CACHE);
break;
}
+#endif
+#if PLATFORM_TOOLS_ANDROID
case BuildPlatform::AndroidARM64:
{
const char* platformDefineName = "PLATFORM_ANDROID";
COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE);
break;
}
+#endif
+#if PLATFORM_TOOLS_SWITCH
+ case BuildPlatform::Switch:
+ {
+ const char* platformDefineName = "PLATFORM_SWITCH";
+ COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE);
+ break;
+ }
+#endif
default:
{
LOG(Warning, "Not implemented platform or shaders not supported.");
@@ -851,7 +870,7 @@ public:
// Create package
// Note: FlaxStorage::Create overrides chunks locations in file so don't use files anymore (only readonly)
const String localPath = String::Format(TEXT("Content/Data_{0}.{1}"), _packageIndex, PACKAGE_FILES_EXTENSION);
- const String path = data.OutputPath / localPath;
+ const String path = data.DataOutputPath / localPath;
if (FlaxStorage::Create(path, assetsData, false, &CustomData))
{
data.Error(TEXT("Failed to create assets package."));
@@ -895,21 +914,27 @@ bool CookAssetsStep::Perform(CookingData& data)
cache.Load(data);
// Update build settings
+#if PLATFORM_TOOLS_WINDOWS
{
const auto settings = WindowsPlatformSettings::Get();
cache.Settings.Windows.SupportDX11 = settings->SupportDX11;
cache.Settings.Windows.SupportDX10 = settings->SupportDX10;
cache.Settings.Windows.SupportVulkan = settings->SupportVulkan;
}
+#endif
+#if PLATFORM_TOOLS_UWP
{
const auto settings = UWPPlatformSettings::Get();
cache.Settings.UWP.SupportDX11 = settings->SupportDX11;
cache.Settings.UWP.SupportDX10 = settings->SupportDX10;
}
+#endif
+#if PLATFORM_TOOLS_LINUX
{
const auto settings = LinuxPlatformSettings::Get();
cache.Settings.Linux.SupportVulkan = settings->SupportVulkan;
}
+#endif
{
cache.Settings.Global.ShadersNoOptimize = buildSettings->ShadersNoOptimize;
cache.Settings.Global.ShadersGenerateDebugData = buildSettings->ShadersGenerateDebugData;
@@ -1011,7 +1036,7 @@ bool CookAssetsStep::Perform(CookingData& data)
gameFlags |= GameHeaderFlags::ShowSplashScreen;
// Open file
- auto stream = FileWriteStream::Open(data.OutputPath / TEXT("Content/head"));
+ auto stream = FileWriteStream::Open(data.DataOutputPath / TEXT("Content/head"));
if (stream == nullptr)
{
data.Error(TEXT("Failed to create game data file."));
@@ -1105,7 +1130,7 @@ bool CookAssetsStep::Perform(CookingData& data)
BUILD_STEP_CANCEL_CHECK;
// Save assets cache
- if (AssetsCache::Save(data.OutputPath / TEXT("Content/AssetsCache.dat"), AssetsRegistry, AssetPathsMapping, AssetsCacheFlags::RelativePaths))
+ if (AssetsCache::Save(data.DataOutputPath / TEXT("Content/AssetsCache.dat"), AssetsRegistry, AssetPathsMapping, AssetsCacheFlags::RelativePaths))
{
data.Error(TEXT("Failed to create assets registry."));
return true;
diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
index f90658cab..17ddbf73c 100644
--- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp
+++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
@@ -7,6 +7,7 @@
#include "Engine/Core/Config/GameSettings.h"
#include "Engine/Renderer/ReflectionsPass.h"
#include "Engine/Renderer/AntiAliasing/SMAA.h"
+#include "Engine/Engine/Globals.h"
bool DeployDataStep::Perform(CookingData& data)
{
@@ -15,7 +16,7 @@ bool DeployDataStep::Perform(CookingData& data)
const auto gameSettings = GameSettings::Get();
// Setup output folders and copy required data
- const auto contentDir = data.OutputPath / TEXT("Content");
+ const auto contentDir = data.DataOutputPath / TEXT("Content");
if (FileSystem::DirectoryExists(contentDir))
{
// Remove old content files
@@ -26,7 +27,7 @@ bool DeployDataStep::Perform(CookingData& data)
}
FileSystem::CreateDirectory(contentDir);
const auto srcMono = depsRoot / TEXT("Mono");
- const auto dstMono = data.OutputPath / TEXT("Mono");
+ const auto dstMono = data.DataOutputPath / TEXT("Mono");
if (!FileSystem::DirectoryExists(dstMono))
{
if (!FileSystem::DirectoryExists(srcMono))
diff --git a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
index 4c482c529..6e5fe50a7 100644
--- a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
+++ b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
@@ -24,7 +24,7 @@ bool PrecompileAssembliesStep::Perform(CookingData& data)
data.Tools->OnConfigureAOT(data, config);
// Prepare output directory
- config.AotCachePath = data.OutputPath / TEXT("Mono/lib/mono/aot-cache");
+ config.AotCachePath = data.DataOutputPath / TEXT("Mono/lib/mono/aot-cache");
switch (data.Tools->GetArchitecture())
{
case ArchitectureType::x86:
@@ -52,9 +52,9 @@ bool PrecompileAssembliesStep::Perform(CookingData& data)
FileSystem::DirectoryGetFiles(config.Assemblies, dir, TEXT("*.dll"), DirectorySearchOption::TopDirectoryOnly);
for (auto& binaryModule : data.BinaryModules)
if (binaryModule.ManagedPath.HasChars())
- config.Assemblies.Add(data.OutputPath / binaryModule.ManagedPath);
+ config.Assemblies.Add(data.CodeOutputPath / binaryModule.ManagedPath);
// TODO: move AOT to Flax.Build and perform it on all C# assemblies used in target build
- config.Assemblies.Add(data.OutputPath / TEXT("Newtonsoft.Json.dll"));
+ config.Assemblies.Add(data.CodeOutputPath / TEXT("Newtonsoft.Json.dll"));
// Perform AOT for the assemblies
for (int32 i = 0; i < config.Assemblies.Count(); i++)
diff --git a/Source/Editor/Cooker/Steps/ValidateStep.cpp b/Source/Editor/Cooker/Steps/ValidateStep.cpp
index 915f7839d..a64317496 100644
--- a/Source/Editor/Cooker/Steps/ValidateStep.cpp
+++ b/Source/Editor/Cooker/Steps/ValidateStep.cpp
@@ -11,9 +11,17 @@ bool ValidateStep::Perform(CookingData& data)
data.StepProgress(TEXT("Performing validation"), 0);
// Ensure output and cache directories exist
- if (!FileSystem::DirectoryExists(data.OutputPath))
+ if (!FileSystem::DirectoryExists(data.CodeOutputPath))
{
- if (FileSystem::CreateDirectory(data.OutputPath))
+ if (FileSystem::CreateDirectory(data.CodeOutputPath))
+ {
+ data.Error(TEXT("Failed to create build output directory."));
+ return true;
+ }
+ }
+ if (!FileSystem::DirectoryExists(data.DataOutputPath))
+ {
+ if (FileSystem::CreateDirectory(data.DataOutputPath))
{
data.Error(TEXT("Failed to create build output directory."));
return true;
diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs
index c5c628755..ca45652b9 100644
--- a/Source/Editor/CustomEditors/CustomEditor.cs
+++ b/Source/Editor/CustomEditors/CustomEditor.cs
@@ -250,6 +250,15 @@ namespace FlaxEditor.CustomEditors
_children[i].RefreshInternal();
}
+ ///
+ /// Synchronizes the value of the container. Called during Refresh to flush property after editing it in UI.
+ ///
+ /// The value to set.
+ protected virtual void SynchronizeValue(object value)
+ {
+ _values.Set(_parent.Values, value);
+ }
+
internal virtual void RefreshInternal()
{
if (_values == null)
@@ -264,7 +273,7 @@ namespace FlaxEditor.CustomEditors
_valueToSet = null;
// Assign value
- _values.Set(_parent.Values, val);
+ SynchronizeValue(val);
// Propagate values up (eg. when member of structure gets modified, also structure should be updated as a part of the other object)
var obj = _parent;
diff --git a/Source/Editor/CustomEditors/CustomEditorPresenter.cs b/Source/Editor/CustomEditors/CustomEditorPresenter.cs
index 2cea89f08..be7b4de5c 100644
--- a/Source/Editor/CustomEditors/CustomEditorPresenter.cs
+++ b/Source/Editor/CustomEditors/CustomEditorPresenter.cs
@@ -139,7 +139,7 @@ namespace FlaxEditor.CustomEditors
///
protected override void OnModified()
{
- Presenter.Modified?.Invoke();
+ Presenter.OnModified();
base.OnModified();
}
@@ -250,6 +250,7 @@ namespace FlaxEditor.CustomEditors
Selection.Clear();
Selection.Add(obj);
+ Selection.SetType(new ScriptType(obj.GetType()));
OnSelectionChanged();
}
@@ -271,6 +272,7 @@ namespace FlaxEditor.CustomEditors
Selection.Clear();
Selection.AddRange(objectsArray);
+ Selection.SetType(new ScriptType(objectsArray.GetType()));
OnSelectionChanged();
}
@@ -284,6 +286,7 @@ namespace FlaxEditor.CustomEditors
return;
Selection.Clear();
+ Selection.SetType(ScriptType.Null);
OnSelectionChanged();
}
@@ -351,6 +354,14 @@ namespace FlaxEditor.CustomEditors
ExpandGroups(this, false);
}
+ ///
+ /// Invokes event.
+ ///
+ public void OnModified()
+ {
+ Modified?.Invoke();
+ }
+
///
/// Called when selection gets changed.
///
diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp
index ffd42d750..519acdf60 100644
--- a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp
+++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp
@@ -3,6 +3,7 @@
#include "CustomEditorsUtil.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/DateTime.h"
+#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Scripting/Scripting.h"
diff --git a/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs b/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs
new file mode 100644
index 000000000..521b844ec
--- /dev/null
+++ b/Source/Editor/CustomEditors/Dedicated/LocalizationSettingsEditor.cs
@@ -0,0 +1,429 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using FlaxEditor.Content.Settings;
+using FlaxEditor.CustomEditors.Editors;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Object = FlaxEngine.Object;
+
+namespace FlaxEditor.CustomEditors.Dedicated
+{
+ [CustomEditor(typeof(LocalizationSettings))]
+ sealed class LocalizationSettingsEditor : GenericEditor
+ {
+ private CultureInfo _theMostTranslatedCulture;
+ private int _theMostTranslatedCultureCount;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ Profiler.BeginEvent("LocalizationSettingsEditor.Initialize");
+ var settings = (LocalizationSettings)Values[0];
+ var tablesLength = settings.LocalizedStringTables?.Length ?? 0;
+ var tables = new List(tablesLength);
+ for (int i = 0; i < tablesLength; i++)
+ {
+ var table = settings.LocalizedStringTables[i];
+ if (table && !table.WaitForLoaded())
+ tables.Add(table);
+ }
+ var locales = tables.GroupBy(x => x.Locale);
+ var tableEntries = new Dictionary>();
+ var allKeys = new HashSet();
+ foreach (var e in locales)
+ {
+ foreach (var table in e)
+ {
+ var entries = table.Entries;
+ tableEntries[table] = entries;
+ allKeys.AddRange(entries.Keys);
+ }
+ }
+
+ {
+ var group = layout.Group("Preview");
+
+ // Current language and culture preview management
+ group.Object("Current Language", new CustomValueContainer(new ScriptType(typeof(CultureInfo)), Localization.CurrentLanguage, (instance, index) => Localization.CurrentLanguage, (instance, index, value) => Localization.CurrentLanguage = value as CultureInfo), null, "Current UI display language for the game preview.");
+ group.Object("Current Culture", new CustomValueContainer(new ScriptType(typeof(CultureInfo)), Localization.CurrentCulture, (instance, index) => Localization.CurrentCulture, (instance, index, value) => Localization.CurrentCulture = value as CultureInfo), null, "Current values formatting culture for the game preview.");
+ }
+
+ {
+ var group = layout.Group("Locales");
+
+ // Show all existing locales
+ _theMostTranslatedCulture = null;
+ _theMostTranslatedCultureCount = -1;
+ foreach (var e in locales)
+ {
+ var culture = new CultureInfo(e.Key);
+ var prop = group.AddPropertyItem(CultureInfoEditor.GetName(culture), culture.NativeName);
+ int count = e.Sum(x => tableEntries[x].Count);
+ int validCount = e.Sum(x => tableEntries[x].Values.Count(y => y != null && y.Length != 0 && !string.IsNullOrEmpty(y[0])));
+ if (count > _theMostTranslatedCultureCount)
+ {
+ _theMostTranslatedCulture = culture;
+ _theMostTranslatedCultureCount = count;
+ }
+ prop.Label(string.Format("Progress: {0}% ({1}/{2})", (int)(((float)validCount / allKeys.Count * 100.0f)), validCount, allKeys.Count));
+ prop.Label("Tables:");
+ foreach (var table in e)
+ {
+ var namePath = table.Path;
+ if (namePath.StartsWith(Globals.ProjectFolder))
+ namePath = namePath.Substring(Globals.ProjectFolder.Length + 1);
+ var tableLabel = prop.ClickableLabel(namePath).CustomControl;
+ tableLabel.TextColorHighlighted = Color.Wheat;
+ tableLabel.DoubleClick += delegate { Editor.Instance.Windows.ContentWin.Select(table); };
+ }
+ group.Space(10);
+ }
+
+ // Update add button
+ var update = group.Button("Update").Button;
+ update.TooltipText = "Refreshes the dashboard statistics";
+ update.Height = 16.0f;
+ update.Clicked += RebuildLayout;
+
+ // New locale add button
+ var addLocale = group.Button("Add Locale...").Button;
+ addLocale.TooltipText = "Shows a locale picker and creates new localization for it with not translated string tables";
+ addLocale.Height = 16.0f;
+ addLocale.ButtonClicked += delegate(Button button)
+ {
+ var menu = CultureInfoEditor.CreatePicker(null, culture =>
+ {
+ var displayName = CultureInfoEditor.GetName(culture);
+ if (locales.Any(x => x.Key == culture.Name))
+ {
+ MessageBox.Show($"Culture '{displayName}' is already added.");
+ return;
+ }
+ Profiler.BeginEvent("LocalizationSettingsEditor.AddLocale");
+ Editor.Log($"Adding culture '{displayName}' to localization settings");
+ var newTables = settings.LocalizedStringTables.ToList();
+ if (_theMostTranslatedCulture != null)
+ {
+ // Duplicate localization for culture with the highest amount of keys
+ var g = locales.First(x => x.Key == _theMostTranslatedCulture.Name);
+ foreach (var e in g)
+ {
+ var path = e.Path;
+ var filename = Path.GetFileNameWithoutExtension(path);
+ if (filename.EndsWith(_theMostTranslatedCulture.Name))
+ filename = filename.Substring(0, filename.Length - _theMostTranslatedCulture.Name.Length);
+ path = Path.Combine(Path.GetDirectoryName(path), filename + culture.Name + ".json");
+ var table = FlaxEngine.Content.CreateVirtualAsset();
+ table.Locale = culture.Name;
+ var entries = new Dictionary();
+ foreach (var ee in tableEntries[e])
+ {
+ var vv = (string[])ee.Value.Clone();
+ for (var i = 0; i < vv.Length; i++)
+ vv[i] = string.Empty;
+ entries.Add(ee.Key, vv);
+ }
+ table.Entries = entries;
+ if (!table.Save(path))
+ {
+ Object.Destroy(table);
+ newTables.Add(FlaxEngine.Content.LoadAsync(path));
+ }
+ }
+ }
+ else
+ {
+ // No localization so initialize with empty table
+ var path = Path.Combine(Path.Combine(Path.GetDirectoryName(GameSettings.Load().Localization.Path), "Localization", culture.Name + ".json"));
+ var table = FlaxEngine.Content.CreateVirtualAsset();
+ table.Locale = culture.Name;
+ if (!table.Save(path))
+ {
+ Object.Destroy(table);
+ newTables.Add(FlaxEngine.Content.LoadAsync(path));
+ }
+ }
+ settings.LocalizedStringTables = newTables.ToArray();
+ Presenter.OnModified();
+ RebuildLayout();
+ Profiler.EndEvent();
+ });
+ menu.Show(button, new Vector2(0, button.Height));
+ };
+
+ // Export button
+ var exportLocalization = group.Button("Export...").Button;
+ exportLocalization.TooltipText = "Exports the localization strings into .pot file for translation";
+ exportLocalization.Height = 16.0f;
+ exportLocalization.Clicked += delegate
+ {
+ if (FileSystem.ShowSaveFileDialog(null, null, "*.pot", false, "Export localization for translation to .pot file", out var filenames))
+ return;
+ Profiler.BeginEvent("LocalizationSettingsEditor.Export");
+ if (!filenames[0].EndsWith(".pot"))
+ filenames[0] += ".pot";
+ var nplurals = 1;
+ foreach (var e in tableEntries)
+ {
+ foreach (var value in e.Value.Values)
+ {
+ if (value != null && value.Length > nplurals)
+ nplurals = value.Length;
+ }
+ }
+ using (var writer = new StreamWriter(filenames[0], false, Encoding.UTF8))
+ {
+ writer.WriteLine("msgid \"\"");
+ writer.WriteLine("msgstr \"\"");
+ writer.WriteLine("\"Language: English\\n\"");
+ writer.WriteLine("\"MIME-Version: 1.0\\n\"");
+ writer.WriteLine("\"Content-Type: text/plain; charset=UTF-8\\n\"");
+ writer.WriteLine("\"Content-Transfer-Encoding: 8bit\\n\"");
+ writer.WriteLine($"\"Plural-Forms: nplurals={nplurals}; plural=(n != 1);\\n\"");
+ writer.WriteLine("\"X-Generator: FlaxEngine\\n\"");
+ var written = new HashSet();
+ foreach (var e in tableEntries)
+ {
+ foreach (var pair in e.Value)
+ {
+ if (written.Contains(pair.Key))
+ continue;
+ written.Add(pair.Key);
+
+ writer.WriteLine("");
+ writer.WriteLine($"msgid \"{pair.Key}\"");
+ if (pair.Value == null || pair.Value.Length < 2)
+ {
+ writer.WriteLine("msgstr \"\"");
+ }
+ else
+ {
+ writer.WriteLine("msgid_plural \"\"");
+ for (int i = 0; i < pair.Value.Length; i++)
+ writer.WriteLine($"msgstr[{i}] \"\"");
+ }
+ }
+ if (written.Count == allKeys.Count)
+ break;
+ }
+ }
+ Profiler.EndEvent();
+ };
+
+ // Find localized strings in code button
+ var findStringsCode = group.Button("Find localized strings in code").Button;
+ findStringsCode.TooltipText = "Searches for localized string usage in inside a project source files";
+ findStringsCode.Height = 16.0f;
+ findStringsCode.Clicked += delegate
+ {
+ var newKeys = new Dictionary();
+ Profiler.BeginEvent("LocalizationSettingsEditor.FindLocalizedStringsInSource");
+
+ // C#
+ var files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.cs", SearchOption.AllDirectories);
+ var filesCount = files.Length;
+ foreach (var file in files)
+ FindNewKeysCSharp(file, newKeys, allKeys);
+
+ // C++
+ files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.cpp", SearchOption.AllDirectories);
+ filesCount += files.Length;
+ foreach (var file in files)
+ FindNewKeysCpp(file, newKeys, allKeys);
+ files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.h", SearchOption.AllDirectories);
+ filesCount += files.Length;
+ foreach (var file in files)
+ FindNewKeysCpp(file, newKeys, allKeys);
+
+ AddNewKeys(newKeys, filesCount, locales, tableEntries);
+ Profiler.EndEvent();
+ };
+
+ // Find localized strings in content button
+ var findStringsContent = group.Button("Find localized strings in content").Button;
+ findStringsContent.TooltipText = "Searches for localized string usage in inside a project content files (scenes, prefabs)";
+ findStringsContent.Height = 16.0f;
+ findStringsContent.Clicked += delegate
+ {
+ var newKeys = new Dictionary();
+ Profiler.BeginEvent("LocalizationSettingsEditor.FindLocalizedStringsInContent");
+
+ // Scenes
+ var files = Directory.GetFiles(Globals.ProjectContentFolder, "*.scene", SearchOption.AllDirectories);
+ var filesCount = files.Length;
+ foreach (var file in files)
+ FindNewKeysJson(file, newKeys, allKeys);
+
+ // Prefabs
+ files = Directory.GetFiles(Globals.ProjectContentFolder, "*.prefab", SearchOption.AllDirectories);
+ filesCount += files.Length;
+ foreach (var file in files)
+ FindNewKeysJson(file, newKeys, allKeys);
+
+ AddNewKeys(newKeys, filesCount, locales, tableEntries);
+ Profiler.EndEvent();
+ };
+ }
+
+ {
+ // Raw asset data editing
+ var group = layout.Group("Data");
+ base.Initialize(group);
+ }
+
+ Profiler.EndEvent();
+ }
+
+ private static void FindNewKeysCSharp(string file, Dictionary newKeys, HashSet allKeys)
+ {
+ var startToken = "Localization.GetString";
+ var textToken = "\"";
+ FindNewKeys(file, newKeys, allKeys, startToken, textToken);
+ }
+
+ private static void FindNewKeysCpp(string file, Dictionary newKeys, HashSet allKeys)
+ {
+ var startToken = "Localization::GetString";
+ var textToken = "TEXT(\"";
+ FindNewKeys(file, newKeys, allKeys, startToken, textToken);
+ }
+
+ private static void FindNewKeys(string file, Dictionary newKeys, HashSet allKeys, string startToken, string textToken)
+ {
+ var contents = File.ReadAllText(file);
+ var idx = contents.IndexOf(startToken);
+ while (idx != -1)
+ {
+ idx += startToken.Length + 1;
+ int braces = 1;
+ int start = idx;
+ while (idx < contents.Length && braces != 0)
+ {
+ if (contents[idx] == '(')
+ braces++;
+ if (contents[idx] == ')')
+ braces--;
+ idx++;
+ }
+ if (idx == contents.Length)
+ break;
+ var inside = contents.Substring(start, idx - start - 1);
+ var textStart = inside.IndexOf(textToken);
+ if (textStart != -1)
+ {
+ textStart += textToken.Length;
+ var textEnd = textStart;
+ while (textEnd < inside.Length && inside[textEnd] != '\"')
+ {
+ if (inside[textEnd] == '\\')
+ textEnd++;
+ textEnd++;
+ }
+ var id = inside.Substring(textStart, textEnd - textStart);
+ textStart = inside.Length > textEnd + 2 ? inside.IndexOf(textToken, textEnd + 2) : -1;
+ string value = null;
+ if (textStart != -1)
+ {
+ textStart += textToken.Length;
+ textEnd = textStart;
+ while (textEnd < inside.Length && inside[textEnd] != '\"')
+ {
+ if (inside[textEnd] == '\\')
+ textEnd++;
+ textEnd++;
+ }
+ value = inside.Substring(textStart, textEnd - textStart);
+ }
+
+ if (!allKeys.Contains(id))
+ newKeys[id] = value;
+ }
+
+ idx = contents.IndexOf(startToken, idx);
+ }
+ }
+
+ private static void FindNewKeysJson(Dictionary newKeys, HashSet allKeys, JToken token)
+ {
+ if (token is JObject o)
+ {
+ foreach (var p in o)
+ {
+ if (string.Equals(p.Key, "Id", StringComparison.Ordinal) && p.Value is JValue i && i.Value is string id && !allKeys.Contains(id))
+ {
+ var count = o.Properties().Count();
+ if (count == 1)
+ {
+ newKeys[id] = null;
+ return;
+ }
+ if (count == 2)
+ {
+ var v = o.Property("Value")?.Value as JValue;
+ if (v?.Value is string value)
+ {
+ newKeys[id] = value;
+ return;
+ }
+ }
+ }
+ FindNewKeysJson(newKeys, allKeys, p.Value);
+ }
+ }
+ else if (token is JArray a)
+ {
+ foreach (var p in a)
+ {
+ FindNewKeysJson(newKeys, allKeys, p);
+ }
+ }
+ }
+
+ private static void FindNewKeysJson(string file, Dictionary newKeys, HashSet allKeys)
+ {
+ using (var reader = new StreamReader(file))
+ using (var jsonReader = new JsonTextReader(reader))
+ {
+ var token = JToken.ReadFrom(jsonReader);
+ FindNewKeysJson(newKeys, allKeys, token);
+ }
+ }
+
+ private void AddNewKeys(Dictionary newKeys, int filesCount, IEnumerable> locales, Dictionary> tableEntries)
+ {
+ Editor.Log($"Found {newKeys.Count} new localized strings in {filesCount} files");
+ if (newKeys.Count == 0)
+ return;
+ foreach (var e in newKeys)
+ Editor.Log(e.Key + (e.Value != null ? " = " + e.Value : string.Empty));
+ foreach (var locale in locales)
+ {
+ var table = locale.First();
+ var entries = tableEntries[table];
+ if (table.Locale == "en")
+ {
+ foreach (var e in newKeys)
+ entries[e.Key] = new[] { e.Value };
+ }
+ else
+ {
+ foreach (var e in newKeys)
+ entries[e.Key] = new[] { string.Empty };
+ }
+ table.Entries = entries;
+ table.Save();
+ }
+ RebuildLayout();
+ }
+ }
+}
diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
index 0fcf9d78a..dcd70a340 100644
--- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
@@ -2,7 +2,9 @@
using System;
using System.Linq;
+using System.Reflection;
using FlaxEditor.CustomEditors.Editors;
+using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting;
@@ -30,17 +32,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (_presets != value)
{
_presets = value;
- OnPresetsChanged();
+ TooltipText = CustomEditorsUtil.GetPropertyNameUI(_presets.ToString());
}
}
}
public bool IsSelected;
-
- private void OnPresetsChanged()
- {
- TooltipText = CustomEditorsUtil.GetPropertyNameUI(_presets.ToString());
- }
+ public bool SupportsShiftModulation;
///
public override void Draw()
@@ -77,6 +75,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
borderColor = BorderColorHighlighted;
}
+ if (SupportsShiftModulation && Input.GetKey(KeyboardKeys.Shift))
+ {
+ backgroundColor = BackgroundColorSelected;
+ }
+
// Calculate fill area
float fillSize = rect.Width / 3;
Rectangle fillArea;
@@ -152,24 +155,83 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
Render2D.DrawRectangle(rect, style.BackgroundSelected.AlphaMultiplied(0.8f), 1.1f);
}
+
+ // Draw pivot point
+ if (SupportsShiftModulation && Input.GetKey(KeyboardKeys.Control))
+ {
+ Vector2 pivotPoint;
+ switch (_presets)
+ {
+ case AnchorPresets.Custom:
+ pivotPoint = Vector2.Minimum;
+ break;
+ case AnchorPresets.TopLeft:
+ pivotPoint = new Vector2(0, 0);
+ break;
+ case AnchorPresets.TopCenter:
+ case AnchorPresets.HorizontalStretchTop:
+ pivotPoint = new Vector2(rect.Width / 2, 0);
+ break;
+ case AnchorPresets.TopRight:
+ pivotPoint = new Vector2(rect.Width, 0);
+ break;
+ case AnchorPresets.MiddleLeft:
+ case AnchorPresets.VerticalStretchLeft:
+ pivotPoint = new Vector2(0, rect.Height / 2);
+ break;
+ case AnchorPresets.MiddleCenter:
+ case AnchorPresets.VerticalStretchCenter:
+ case AnchorPresets.HorizontalStretchMiddle:
+ case AnchorPresets.StretchAll:
+ pivotPoint = new Vector2(rect.Width / 2, rect.Height / 2);
+ break;
+ case AnchorPresets.MiddleRight:
+ case AnchorPresets.VerticalStretchRight:
+ pivotPoint = new Vector2(rect.Width, rect.Height / 2);
+ break;
+ case AnchorPresets.BottomLeft:
+ pivotPoint = new Vector2(0, rect.Height);
+ break;
+ case AnchorPresets.BottomCenter:
+ case AnchorPresets.HorizontalStretchBottom:
+ pivotPoint = new Vector2(rect.Width / 2, rect.Height);
+ break;
+ case AnchorPresets.BottomRight:
+ pivotPoint = new Vector2(rect.Width, rect.Height);
+ break;
+ default: throw new ArgumentOutOfRangeException();
+ }
+ var pivotPointSize = new Vector2(3.0f);
+ Render2D.DrawRectangle(new Rectangle(pivotPoint - pivotPointSize * 0.5f, pivotPointSize), style.ProgressNormal, 1.1f);
+ }
}
}
- class AnchorPresetsEditorPopup : ContextMenuBase
+ ///
+ /// Context menu for anchors presets editing.
+ ///
+ ///
+ public sealed class AnchorPresetsEditorPopup : ContextMenuBase
{
const float ButtonsMargin = 10.0f;
const float ButtonsMarginStretch = 8.0f;
const float ButtonsSize = 32.0f;
const float TitleHeight = 23.0f;
+ const float InfoHeight = 23.0f;
const float DialogWidth = ButtonsSize * 4 + ButtonsMargin * 5 + ButtonsMarginStretch;
- const float DialogHeight = TitleHeight + ButtonsSize * 4 + ButtonsMargin * 5 + ButtonsMarginStretch;
+ const float DialogHeight = TitleHeight + InfoHeight + ButtonsSize * 4 + ButtonsMargin * 5 + ButtonsMarginStretch;
+
+ private readonly bool _supportsShiftModulation;
///
/// Initializes a new instance of the class.
///
/// The initial value.
- public AnchorPresetsEditorPopup(AnchorPresets presets)
+ /// If the popup should react to shift
+ public AnchorPresetsEditorPopup(AnchorPresets presets, bool supportsShiftModulation = true)
{
+ _supportsShiftModulation = supportsShiftModulation;
+
var style = FlaxEngine.GUI.Style.Current;
Tag = presets;
Size = new Vector2(DialogWidth, DialogHeight);
@@ -182,9 +244,17 @@ namespace FlaxEditor.CustomEditors.Dedicated
Parent = this
};
+ // Info
+ var info = new Label(0, title.Bottom, DialogWidth, InfoHeight)
+ {
+ Font = new FontReference(style.FontSmall),
+ Text = "Shift: also set bounds\nControl: also set pivot",
+ Parent = this
+ };
+
// Buttons
var buttonsX = ButtonsMargin;
- var buttonsY = title.Bottom + ButtonsMargin;
+ var buttonsY = info.Bottom + ButtonsMargin;
var buttonsSpacingX = ButtonsSize + ButtonsMargin;
var buttonsSpacingY = ButtonsSize + ButtonsMargin;
//
@@ -217,6 +287,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
Parent = this,
Presets = presets,
IsSelected = presets == (AnchorPresets)Tag,
+ SupportsShiftModulation = _supportsShiftModulation,
Tag = presets,
};
button.ButtonClicked += OnButtonClicked;
@@ -276,7 +347,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
private void OnButtonClicked()
{
var location = _button.Center + new Vector2(3.0f);
- var editor = new AnchorPresetsEditorPopup(_button.Presets);
+ var editor = new AnchorPresetsEditorPopup(_button.Presets, true);
editor.VisibleChanged += OnEditorVisibleChanged;
editor.Show(_button.Parent, location);
}
@@ -288,6 +359,30 @@ namespace FlaxEditor.CustomEditors.Dedicated
SetValue(control.Tag);
}
+ ///
+ protected override void SynchronizeValue(object value)
+ {
+ // Custom anchors editing for Control to handle bounds preservation via key modifiers
+ if (ParentEditor != null)
+ {
+ var centerToPosition = Input.GetKey(KeyboardKeys.Shift);
+ var setPivot = Input.GetKey(KeyboardKeys.Control);
+ var editedAny = false;
+ foreach (var parentValue in ParentEditor.Values)
+ {
+ if (parentValue is Control parentControl)
+ {
+ parentControl.SetAnchorPreset((AnchorPresets)value, !centerToPosition, setPivot);
+ editedAny = true;
+ }
+ }
+ if (editedAny)
+ return;
+ }
+
+ base.SynchronizeValue(value);
+ }
+
///
public override void Refresh()
{
@@ -309,7 +404,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// Dedicated custom editor for object.
///
///
- public sealed class UIControlControlEditor : GenericEditor
+ public class UIControlControlEditor : GenericEditor
{
private Type _cachedType;
@@ -349,6 +444,176 @@ namespace FlaxEditor.CustomEditors.Dedicated
// Show control properties
base.Initialize(layout);
+
+ for (int i = 0; i < layout.Children.Count; i++)
+ {
+ if (layout.Children[i] is GroupElement group && group.Panel.HeaderText == "Transform")
+ {
+ VerticalPanelElement mainHor = VerticalPanelWithoutMargin(group);
+ CreateTransformElements(mainHor, ValuesTypes);
+ group.ContainerControl.ChangeChildIndex(mainHor.Control, 0);
+ break;
+ }
+ }
+ }
+
+ private void CreateTransformElements(LayoutElementsContainer main, ScriptType[] valueTypes)
+ {
+ main.Space(10);
+ HorizontalPanelElement sidePanel = main.HorizontalPanel();
+ sidePanel.Panel.ClipChildren = false;
+
+ ScriptMemberInfo anchorInfo = valueTypes[0].GetProperty("AnchorPreset");
+ ItemInfo anchorItem = new ItemInfo(anchorInfo);
+ sidePanel.Object(anchorItem.GetValues(Values));
+
+ VerticalPanelElement group = VerticalPanelWithoutMargin(sidePanel);
+
+ group.Panel.AnchorPreset = AnchorPresets.HorizontalStretchTop;
+ group.Panel.Offsets = new Margin(100, 10, 0, 0);
+
+ var horUp = UniformGridTwoByOne(group);
+ horUp.CustomControl.Height = TextBoxBase.DefaultHeight;
+ var horDown = UniformGridTwoByOne(group);
+ horDown.CustomControl.Height = TextBoxBase.DefaultHeight;
+
+ GetAnchorEquality(out _cachedXEq, out _cachedYEq, valueTypes);
+
+ BuildLocationSizeOffsets(horUp, horDown, _cachedXEq, _cachedYEq, valueTypes);
+
+ main.Space(10);
+ BuildAnchorsDropper(main, valueTypes);
+ }
+
+ private void BuildAnchorsDropper(LayoutElementsContainer main, ScriptType[] valueTypes)
+ {
+ ScriptMemberInfo minInfo = valueTypes[0].GetProperty("AnchorMin");
+ ScriptMemberInfo maxInfo = valueTypes[0].GetProperty("AnchorMax");
+ ItemInfo minItem = new ItemInfo(minInfo);
+ ItemInfo maxItem = new ItemInfo(maxInfo);
+
+ GroupElement ng = main.Group("Anchors", true);
+ ng.Panel.Close(false);
+ ng.Property("Min", minItem.GetValues(Values));
+ ng.Property("Max", maxItem.GetValues(Values));
+ }
+
+ private void GetAnchorEquality(out bool xEq, out bool yEq, ScriptType[] valueTypes)
+ {
+ ScriptMemberInfo minInfo = valueTypes[0].GetProperty("AnchorMin");
+ ScriptMemberInfo maxInfo = valueTypes[0].GetProperty("AnchorMax");
+ ItemInfo minItem = new ItemInfo(minInfo);
+ ItemInfo maxItem = new ItemInfo(maxInfo);
+ ValueContainer minVal = minItem.GetValues(Values);
+ ValueContainer maxVal = maxItem.GetValues(Values);
+
+ ItemInfo xItem = new ItemInfo(minInfo.ValueType.GetField("X"));
+ ItemInfo yItem = new ItemInfo(minInfo.ValueType.GetField("Y"));
+
+ xEq = xItem.GetValues(minVal).ToList().Any(xItem.GetValues(maxVal).ToList().Contains);
+ yEq = yItem.GetValues(minVal).ToList().Any(yItem.GetValues(maxVal).ToList().Contains);
+ }
+
+ private void BuildLocationSizeOffsets(LayoutElementsContainer horUp, LayoutElementsContainer horDown, bool xEq, bool yEq, ScriptType[] valueTypes)
+ {
+ ScriptMemberInfo xInfo = valueTypes[0].GetProperty("LocalX");
+ ItemInfo xItem = new ItemInfo(xInfo);
+ ScriptMemberInfo yInfo = valueTypes[0].GetProperty("LocalY");
+ ItemInfo yItem = new ItemInfo(yInfo);
+ ScriptMemberInfo widthInfo = valueTypes[0].GetProperty("Width");
+ ItemInfo widthItem = new ItemInfo(widthInfo);
+ ScriptMemberInfo heightInfo = valueTypes[0].GetProperty("Height");
+ ItemInfo heightItem = new ItemInfo(heightInfo);
+
+ ScriptMemberInfo leftInfo = valueTypes[0].GetProperty("Proxy_Offset_Left", BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ ItemInfo leftItem = new ItemInfo(leftInfo);
+ ScriptMemberInfo rightInfo = valueTypes[0].GetProperty("Proxy_Offset_Right", BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ ItemInfo rightItem = new ItemInfo(rightInfo);
+ ScriptMemberInfo topInfo = valueTypes[0].GetProperty("Proxy_Offset_Top", BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ ItemInfo topItem = new ItemInfo(topInfo);
+ ScriptMemberInfo bottomInfo = valueTypes[0].GetProperty("Proxy_Offset_Bottom", BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ ItemInfo bottomItem = new ItemInfo(bottomInfo);
+
+ LayoutElementsContainer xEl;
+ LayoutElementsContainer yEl;
+ LayoutElementsContainer hEl;
+ LayoutElementsContainer vEl;
+ if (xEq)
+ {
+ xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values));
+ vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values));
+ }
+ else
+ {
+ xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values));
+ vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values));
+ }
+ if (yEq)
+ {
+ yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values));
+ hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values));
+ }
+ else
+ {
+ yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values));
+ hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values));
+ }
+ xEl.Control.AnchorMin = new Vector2(0, xEl.Control.AnchorMin.Y);
+ xEl.Control.AnchorMax = new Vector2(0.5f, xEl.Control.AnchorMax.Y);
+
+ vEl.Control.AnchorMin = new Vector2(0, xEl.Control.AnchorMin.Y);
+ vEl.Control.AnchorMax = new Vector2(0.5f, xEl.Control.AnchorMax.Y);
+
+ yEl.Control.AnchorMin = new Vector2(0.5f, xEl.Control.AnchorMin.Y);
+ yEl.Control.AnchorMax = new Vector2(1, xEl.Control.AnchorMax.Y);
+
+ hEl.Control.AnchorMin = new Vector2(0.5f, xEl.Control.AnchorMin.Y);
+ hEl.Control.AnchorMax = new Vector2(1, xEl.Control.AnchorMax.Y);
+ }
+
+ private VerticalPanelElement VerticalPanelWithoutMargin(LayoutElementsContainer cont)
+ {
+ var horUp = cont.VerticalPanel();
+ horUp.Panel.Margin = Margin.Zero;
+ return horUp;
+ }
+
+ private CustomElementsContainer UniformGridTwoByOne(LayoutElementsContainer cont)
+ {
+ var horUp = cont.CustomContainer();
+ horUp.CustomControl.SlotsHorizontally = 2;
+ horUp.CustomControl.SlotsVertically = 1;
+ horUp.CustomControl.SlotPadding = Margin.Zero;
+ horUp.CustomControl.ClipChildren = false;
+ return horUp;
+ }
+
+ private CustomElementsContainer UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values)
+ {
+ CustomElementsContainer hor = UniformGridTwoByOne(el);
+ hor.CustomControl.SlotPadding = new Margin(5, 5, 0, 0);
+ LabelElement lab = hor.Label(text);
+ hor.Object(values);
+ return hor;
+ }
+
+ private bool _cachedXEq;
+ private bool _cachedYEq;
+
+ ///
+ /// Refreshes if equality of anchors does not correspond to the cached equality
+ ///
+ public void RefreshBaseOnAnchorsEquality()
+ {
+ if (Values.HasNull)
+ return;
+
+ GetAnchorEquality(out bool xEq, out bool yEq, ValuesTypes);
+ if (xEq != _cachedXEq || yEq != _cachedYEq)
+ {
+ RebuildLayout();
+ return;
+ }
}
///
@@ -361,6 +626,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
RebuildLayout();
return;
}
+ RefreshBaseOnAnchorsEquality();
+ //RefreshValues();
base.Refresh();
}
@@ -405,7 +672,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
for (int i = 0; i < uiControls.Count; i++)
{
var uiControl = (UIControl)uiControls[i];
+ string previousName = uiControl.Control?.GetType()?.Name ?? typeof(UIControl).Name;
uiControl.Control = (Control)controlType.CreateInstance();
+ if (uiControl.Name.StartsWith(previousName))
+ {
+ string newName = controlType.Name + uiControl.Name.Substring(previousName.Length);
+ uiControl.Name = StringUtils.IncrementNameNumber(newName, x => uiControl.Parent.GetChild(x) == null);
+ }
}
}
}
@@ -414,7 +687,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
for (int i = 0; i < uiControls.Count; i++)
{
var uiControl = (UIControl)uiControls[i];
+ string previousName = uiControl.Control?.GetType()?.Name ?? typeof(UIControl).Name;
uiControl.Control = (Control)controlType.CreateInstance();
+ if (uiControl.Name.StartsWith(previousName))
+ {
+ string newName = controlType.Name + uiControl.Name.Substring(previousName.Length);
+ uiControl.Name = StringUtils.IncrementNameNumber(newName, x => uiControl.Parent.GetChild(x) == null);
+ }
}
}
diff --git a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
index a6aa34880..555ebc870 100644
--- a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
@@ -22,10 +22,9 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Initialize(LayoutElementsContainer layout)
{
element = layout.ComboBox();
- element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged;
-
- // Set layer names
element.ComboBox.SetItems(LayersAndTagsSettings.GetCurrentLayers());
+ element.ComboBox.SelectedIndex = (int)Values[0];
+ element.ComboBox.SelectedIndexChanged += OnSelectedIndexChanged;
}
private void GetActorsTree(List list, Actor a)
diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
index 2e5aeade7..141330aea 100644
--- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
@@ -154,9 +154,20 @@ namespace FlaxEditor.CustomEditors.Editors
if (i != 0 && spacing > 0f)
{
if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement)
+ {
+ if (propertiesListElement.Labels.Count > 0)
+ {
+ var label = propertiesListElement.Labels[propertiesListElement.Labels.Count - 1];
+ var margin = label.Margin;
+ margin.Bottom += spacing;
+ label.Margin = margin;
+ }
propertiesListElement.Space(spacing);
+ }
else
+ {
layout.Space(spacing);
+ }
}
var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null;
diff --git a/Source/Editor/CustomEditors/Editors/CultureInfoEditor.cs b/Source/Editor/CustomEditors/Editors/CultureInfoEditor.cs
new file mode 100644
index 000000000..67533ed56
--- /dev/null
+++ b/Source/Editor/CustomEditors/Editors/CultureInfoEditor.cs
@@ -0,0 +1,164 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using FlaxEditor.GUI;
+using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.GUI.Tree;
+using FlaxEditor.Utilities;
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.CustomEditors.Editors
+{
+ ///
+ /// Default implementation of the inspector used to edit value type properties. Supports editing property of type (as culture name).
+ ///
+ [CustomEditor(typeof(CultureInfo)), DefaultEditor]
+ internal class CultureInfoEditor : CustomEditor
+ {
+ private ClickableLabel _label;
+
+ ///
+ public override DisplayStyle Style => DisplayStyle.Inline;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ _label = layout.ClickableLabel(GetName(Culture)).CustomControl;
+ _label.RightClick += ShowPicker;
+ var button = new Button
+ {
+ Width = 16.0f,
+ Text = "...",
+ Parent = _label,
+ };
+ button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
+ button.Clicked += ShowPicker;
+ }
+
+ ///
+ public override void Refresh()
+ {
+ base.Refresh();
+
+ _label.Text = GetName(Culture);
+ }
+
+ ///
+ protected override void Deinitialize()
+ {
+ _label = null;
+
+ base.Deinitialize();
+ }
+
+ private CultureInfo Culture
+ {
+ get
+ {
+ if (Values[0] is CultureInfo asCultureInfo)
+ return asCultureInfo;
+ if (Values[0] is string asString)
+ return new CultureInfo(asString);
+ return null;
+ }
+ set
+ {
+ if (Values[0] is CultureInfo)
+ SetValue(value);
+ else if (Values[0] is string)
+ SetValue(value.Name);
+ }
+ }
+
+ private class CultureInfoComparer : IComparer
+ {
+ public int Compare(CultureInfo a, CultureInfo b)
+ {
+ return string.Compare(a.Name, b.Name, StringComparison.Ordinal);
+ }
+ }
+
+ private static void UpdateFilter(TreeNode node, string filterText)
+ {
+ // Update children
+ bool isAnyChildVisible = false;
+ for (int i = 0; i < node.Children.Count; i++)
+ {
+ if (node.Children[i] is TreeNode child)
+ {
+ UpdateFilter(child, filterText);
+ isAnyChildVisible |= child.Visible;
+ }
+ }
+
+ // Update itself
+ bool noFilter = string.IsNullOrWhiteSpace(filterText);
+ bool isThisVisible = noFilter || QueryFilterHelper.Match(filterText, node.Text);
+ bool isExpanded = isAnyChildVisible;
+ if (isExpanded)
+ node.Expand(true);
+ else
+ node.Collapse(true);
+ node.Visible = isThisVisible | isAnyChildVisible;
+ }
+
+ private void ShowPicker()
+ {
+ var menu = CreatePicker(Culture, value => { Culture = value; });
+ menu.Show(_label, new Vector2(0, _label.Height));
+ }
+
+ internal static ContextMenuBase CreatePicker(CultureInfo value, Action changed)
+ {
+ var menu = Utilities.Utils.CreateSearchPopup(out var searchBox, out var tree);
+ tree.Margin = new Margin(-16.0f, 0.0f, -16.0f, -0.0f); // Hide root node
+ var root = tree.AddChild();
+ var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
+ Array.Sort(cultures, 1, cultures.Length - 2, new CultureInfoComparer()); // at 0 there is Invariant Culture
+ var lcidToNode = new Dictionary();
+ for (var i = 0; i < cultures.Length; i++)
+ {
+ var culture = cultures[i];
+ var node = new TreeNode
+ {
+ Tag = culture,
+ Text = GetName(culture),
+ };
+ if (!lcidToNode.TryGetValue(culture.Parent.LCID, out ContainerControl parent))
+ parent = root;
+ node.Parent = parent;
+ lcidToNode[culture.LCID] = node;
+ }
+ if (value != null)
+ tree.Select((TreeNode)lcidToNode[value.LCID]);
+ tree.SelectedChanged += delegate(List before, List after)
+ {
+ if (after.Count == 1)
+ {
+ menu.Hide();
+ changed((CultureInfo)after[0].Tag);
+ }
+ };
+ searchBox.TextChanged += delegate
+ {
+ if (tree.IsLayoutLocked)
+ return;
+ root.LockChildrenRecursive();
+ var query = searchBox.Text;
+ UpdateFilter(root, query);
+ root.UnlockChildrenRecursive();
+ menu.PerformLayout();
+ };
+ root.ExpandAll(true);
+ return menu;
+ }
+
+ internal static string GetName(CultureInfo value)
+ {
+ return value != null ? string.Format("{0} - {1}", value.Name, value.EnglishName) : null;
+ }
+ }
+}
diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs
index 4e4048dba..4a94136db 100644
--- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs
@@ -61,7 +61,7 @@ namespace FlaxEditor.CustomEditors.Editors
var keyType = _editor.Values.Type.GetGenericArguments()[0];
if (keyType == typeof(string) || keyType.IsPrimitive)
{
- var popup = RenamePopup.Show(Parent, Bounds, Text, false);
+ var popup = RenamePopup.Show(Parent, Rectangle.Margin(Bounds, Margin), Text, false);
popup.Validate += (renamePopup, value) =>
{
object newKey;
@@ -86,7 +86,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
else if (keyType.IsEnum)
{
- var popup = RenamePopup.Show(Parent, Bounds, Text, false);
+ var popup = RenamePopup.Show(Parent, Rectangle.Margin(Bounds, Margin), Text, false);
var picker = new EnumComboBox(keyType)
{
AnchorPreset = AnchorPresets.StretchAll,
@@ -220,9 +220,20 @@ namespace FlaxEditor.CustomEditors.Editors
if (i != 0 && spacing > 0f)
{
if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement)
+ {
+ if (propertiesListElement.Labels.Count > 0)
+ {
+ var label = propertiesListElement.Labels[propertiesListElement.Labels.Count - 1];
+ var margin = label.Margin;
+ margin.Bottom += spacing;
+ label.Margin = margin;
+ }
propertiesListElement.Space(spacing);
+ }
else
+ {
layout.Space(spacing);
+ }
}
var key = keys.ElementAt(i);
diff --git a/Source/Editor/CustomEditors/Editors/EnumEditor.cs b/Source/Editor/CustomEditors/Editors/EnumEditor.cs
index 7954e18bf..b91c0cf95 100644
--- a/Source/Editor/CustomEditors/Editors/EnumEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/EnumEditor.cs
@@ -9,7 +9,7 @@ using FlaxEngine;
namespace FlaxEditor.CustomEditors.Editors
{
///
- /// Default implementation of the inspector used to edit float value type properties.
+ /// Default implementation of the inspector used to edit enum value type properties.
///
[CustomEditor(typeof(Enum)), DefaultEditor]
public class EnumEditor : CustomEditor
diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
index ed6d88e9e..d4079ad79 100644
--- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
@@ -173,6 +173,15 @@ namespace FlaxEditor.CustomEditors.Editors
return string.Compare(Display.Group, other.Display.Group, StringComparison.InvariantCulture);
}
+ if (Editor.Instance.Options.Options.General.ScriptMembersOrder == Options.GeneralOptions.MembersOrder.Declaration)
+ {
+ // By declaration order
+ if (Info.MetadataToken > other.Info.MetadataToken)
+ return 1;
+ if (Info.MetadataToken < other.Info.MetadataToken)
+ return -1;
+ }
+
// By name
return string.Compare(Info.Name, other.Info.Name, StringComparison.InvariantCulture);
}
@@ -205,6 +214,7 @@ namespace FlaxEditor.CustomEditors.Editors
public ScriptMemberInfo Target;
public ScriptMemberInfo Source;
public PropertiesListElement PropertiesList;
+ public GroupElement Group;
public bool Invert;
public int LabelIndex;
@@ -248,15 +258,15 @@ namespace FlaxEditor.CustomEditors.Editors
for (int i = 0; i < properties.Length; i++)
{
var p = properties[i];
+ var attributes = p.GetAttributes(true);
+ var showInEditor = attributes.Any(x => x is ShowInEditorAttribute);
// Skip properties without getter or setter
- if (!p.HasGet || !p.HasSet)
+ if (!p.HasGet || (!p.HasSet && !showInEditor))
continue;
-
- var attributes = p.GetAttributes(true);
-
+
// Skip hidden fields, handle special attributes
- if ((!p.IsPublic && !attributes.Any(x => x is ShowInEditorAttribute)) || attributes.Any(x => x is HideInEditorAttribute))
+ if ((!p.IsPublic && !showInEditor) || attributes.Any(x => x is HideInEditorAttribute))
continue;
items.Add(new ItemInfo(p, attributes));
@@ -370,26 +380,22 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
}
- if (item.VisibleIf != null)
+ if (item.VisibleIf != null && itemLayout.Children.Count > 0)
{
- PropertiesListElement list;
- if (itemLayout.Children.Count > 0 && itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement list1)
- {
+ PropertiesListElement list = null;
+ GroupElement group = null;
+ if (itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement list1)
list = list1;
- }
+ else if (itemLayout.Children[itemLayout.Children.Count - 1] is GroupElement group1)
+ group = group1;
else
- {
- // TODO: support inlined objects hiding?
return;
- }
// Get source member used to check rule
var sourceMember = GetVisibleIfSource(item.Info.DeclaringType, item.VisibleIf);
if (sourceMember == ScriptType.Null)
return;
- // Find the target control to show/hide
-
// Resize cache
if (_visibleIfCaches == null)
_visibleIfCaches = new VisibleIfCache[8];
@@ -405,6 +411,7 @@ namespace FlaxEditor.CustomEditors.Editors
Target = item.Info,
Source = sourceMember,
PropertiesList = list,
+ Group = group,
LabelIndex = labelIndex,
Invert = item.VisibleIf.Invert,
};
@@ -560,8 +567,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
for (int i = 0; i < _visibleIfCaches.Length; i++)
{
- var c = _visibleIfCaches[i];
-
+ ref var c = ref _visibleIfCaches[i];
if (c.Target == ScriptMemberInfo.Null)
break;
@@ -577,7 +583,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
// Apply the visibility (note: there may be no label)
- if (c.LabelIndex != -1 && c.PropertiesList.Labels.Count > c.LabelIndex)
+ if (c.LabelIndex != -1 && c.PropertiesList != null && c.PropertiesList.Labels.Count > c.LabelIndex)
{
var label = c.PropertiesList.Labels[c.LabelIndex];
label.Visible = visible;
@@ -590,6 +596,10 @@ namespace FlaxEditor.CustomEditors.Editors
child.Visible = visible;
}
}
+ if (c.Group != null)
+ {
+ c.Group.Panel.Visible = visible;
+ }
}
}
catch (Exception ex)
diff --git a/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs b/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs
new file mode 100644
index 000000000..82503d9bc
--- /dev/null
+++ b/Source/Editor/CustomEditors/Editors/LocalizedStringEditor.cs
@@ -0,0 +1,234 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using FlaxEditor.Content.Settings;
+using FlaxEditor.CustomEditors.Elements;
+using FlaxEditor.GUI.Tree;
+using FlaxEditor.Scripting;
+using FlaxEditor.Utilities;
+using FlaxEngine;
+using FlaxEngine.GUI;
+using Utils = FlaxEditor.Utilities.Utils;
+
+namespace FlaxEditor.CustomEditors.Editors
+{
+ ///
+ /// Default implementation of the inspector used to edit localized string properties.
+ ///
+ [CustomEditor(typeof(LocalizedString)), DefaultEditor]
+ public sealed class LocalizedStringEditor : GenericEditor
+ {
+ private TextBoxElement _idElement, _valueElement;
+
+ ///
+ public override DisplayStyle Style => DisplayStyle.Inline;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ base.Initialize(layout);
+
+ if (layout.Children.Count == 0)
+ return;
+ var propList = layout.Children[layout.Children.Count - 1] as PropertiesListElement;
+ if (propList == null || propList.Children.Count != 2)
+ return;
+ var idElement = propList.Children[0] as TextBoxElement;
+ var valueElement = propList.Children[1] as TextBoxElement;
+ if (idElement == null || valueElement == null)
+ return;
+ _idElement = idElement;
+ _valueElement = valueElement;
+
+ var attributes = Values.GetAttributes();
+ var multiLine = attributes?.FirstOrDefault(x => x is MultilineTextAttribute);
+ if (multiLine != null)
+ {
+ valueElement.TextBox.IsMultiline = true;
+ valueElement.TextBox.Height *= 3;
+ }
+
+ var selectString = new Button
+ {
+ Width = 16.0f,
+ Text = "...",
+ TooltipText = "Select localized text from Localization Settings...",
+ Parent = idElement.TextBox,
+ };
+ selectString.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
+ selectString.ButtonClicked += OnSelectStringClicked;
+
+ var addString = new Button
+ {
+ Width = 16.0f,
+ Text = "+",
+ TooltipText = "Add new localized text to Localization Settings (all used locales)",
+ Parent = _valueElement.TextBox,
+ Enabled = IsSingleObject,
+ };
+ addString.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
+ addString.ButtonClicked += OnAddStringClicked;
+ }
+
+ ///
+ internal override void RefreshInternal()
+ {
+ base.RefreshInternal();
+
+ if (_valueElement != null)
+ {
+ _valueElement.TextBox.WatermarkText = Localization.GetString(_idElement.Text);
+ }
+ }
+
+ ///
+ protected override void Deinitialize()
+ {
+ base.Deinitialize();
+
+ _idElement = null;
+ _valueElement = null;
+ }
+
+ private void OnSelectStringClicked(Button button)
+ {
+ var settings = GameSettings.Load();
+ if (settings?.LocalizedStringTables == null || settings.LocalizedStringTables.Length == 0)
+ {
+ MessageBox.Show("No valid localization settings setup.");
+ return;
+ }
+ Profiler.BeginEvent("LocalizedStringEditor.OnSelectStringClicked");
+ var allKeys = new HashSet();
+ for (int i = 0; i < settings.LocalizedStringTables.Length; i++)
+ {
+ var table = settings.LocalizedStringTables[i];
+ if (table && !table.WaitForLoaded())
+ {
+ var entries = table.Entries;
+ foreach (var e in entries)
+ allKeys.Add(e.Key);
+ }
+ }
+ var allKeysSorted = allKeys.ToList();
+ allKeysSorted.Sort();
+ var value = _idElement?.TextBox.Text;
+ var menu = Utils.CreateSearchPopup(out var searchBox, out var tree);
+ var idToNode = new TreeNode[allKeysSorted.Count];
+ for (var i = 0; i < allKeysSorted.Count; i++)
+ {
+ var key = allKeysSorted[i];
+ var node = new TreeNode
+ {
+ Text = key,
+ TooltipText = Localization.GetString(key),
+ Parent = tree,
+ };
+ if (key == value)
+ tree.Select(node);
+ idToNode[i] = node;
+ }
+ tree.SelectedChanged += delegate(List before, List after)
+ {
+ if (after.Count == 1)
+ {
+ menu.Hide();
+ _idElement.TextBox.SetTextAsUser(after[0].Text);
+ }
+ };
+ searchBox.TextChanged += delegate
+ {
+ if (tree.IsLayoutLocked)
+ return;
+ tree.LockChildrenRecursive();
+ var query = searchBox.Text;
+ for (int i = 0; i < idToNode.Length; i++)
+ {
+ var node = idToNode[i];
+ node.Visible = string.IsNullOrWhiteSpace(query) || QueryFilterHelper.Match(query, node.Text);
+ }
+ tree.UnlockChildrenRecursive();
+ menu.PerformLayout();
+ };
+ menu.Show(button, new Vector2(0, button.Height));
+ Profiler.EndEvent();
+ }
+
+ private void OnAddStringClicked(Button button)
+ {
+ var settings = GameSettings.Load();
+ if (settings?.LocalizedStringTables == null || settings.LocalizedStringTables.Length == 0)
+ {
+ MessageBox.Show("No valid localization settings setup.");
+ return;
+ }
+ Profiler.BeginEvent("LocalizedStringEditor.OnAddStringClicked");
+ var allKeys = new HashSet();
+ for (int i = 0; i < settings.LocalizedStringTables.Length; i++)
+ {
+ var table = settings.LocalizedStringTables[i];
+ if (table && !table.WaitForLoaded())
+ {
+ var entries = table.Entries;
+ foreach (var e in entries)
+ allKeys.Add(e.Key);
+ }
+ }
+ _valueElement.TextBox.SetTextAsUser(null);
+ string newKey = null;
+ if (string.IsNullOrEmpty(_idElement.Text))
+ {
+ CustomEditor customEditor = this;
+ while (customEditor?.Values != null)
+ {
+ if (customEditor.Values.Info != ScriptMemberInfo.Null)
+ if (newKey == null)
+ newKey = customEditor.Values.Info.Name;
+ else
+ newKey = customEditor.Values.Info.Name + '.' + newKey;
+ else if (customEditor.Values[0] is SceneObject sceneObject)
+ if (newKey == null)
+ newKey = sceneObject.GetNamePath('.');
+ else
+ newKey = sceneObject.GetNamePath('.') + '.' + newKey;
+ else
+ break;
+ customEditor = customEditor.ParentEditor;
+ }
+ if (string.IsNullOrWhiteSpace(newKey))
+ newKey = Guid.NewGuid().ToString("N");
+ }
+ else
+ {
+ newKey = _idElement.Text;
+ }
+ if (allKeys.Contains(newKey))
+ {
+ Profiler.EndEvent();
+ if (_idElement.Text != newKey)
+ _idElement.TextBox.SetTextAsUser(newKey);
+ else
+ MessageBox.Show("Already added.");
+ return;
+ }
+ var newValue = _valueElement.Text;
+ Editor.Log(newKey + (newValue != null ? " = " + newValue : string.Empty));
+ var locales = settings.LocalizedStringTables.GroupBy(x => x.Locale);
+ foreach (var locale in locales)
+ {
+ var table = locale.First();
+ var entries = table.Entries;
+ if (table.Locale == "en")
+ entries[newKey] = new[] { newValue };
+ else
+ entries[newKey] = new[] { string.Empty };
+ table.Entries = entries;
+ table.Save();
+ }
+ _idElement.TextBox.SetTextAsUser(newKey);
+ Profiler.EndEvent();
+ }
+ }
+}
diff --git a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
index 7724acf18..dcdf3114b 100644
--- a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
@@ -3,7 +3,6 @@
using System;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
-using FlaxEngine;
namespace FlaxEditor.CustomEditors.Editors
{
diff --git a/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs b/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs
index 338c8eca6..f80c81e27 100644
--- a/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs
@@ -12,6 +12,9 @@ namespace FlaxEditor.CustomEditors.Editors
[CustomEditor(typeof(Quaternion)), DefaultEditor]
public class QuaternionEditor : CustomEditor
{
+ private Vector3 _cachedAngles = Vector3.Zero;
+ private object _cachedToken;
+
///
/// The X component element
///
@@ -58,15 +61,36 @@ namespace FlaxEditor.CustomEditors.Editors
if (IsSetBlocked)
return;
- float x = XElement.FloatValue.Value;
- float y = YElement.FloatValue.Value;
- float z = ZElement.FloatValue.Value;
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
var token = isSliding ? this : null;
+ var useCachedAngles = isSliding && token == _cachedToken;
+
+ float x = (useCachedAngles && !XElement.IsSliding) ? _cachedAngles.X : XElement.FloatValue.Value;
+ float y = (useCachedAngles && !YElement.IsSliding) ? _cachedAngles.Y : YElement.FloatValue.Value;
+ float z = (useCachedAngles && !ZElement.IsSliding) ? _cachedAngles.Z : ZElement.FloatValue.Value;
+
+ x = Mathf.UnwindDegrees(x);
+ y = Mathf.UnwindDegrees(y);
+ z = Mathf.UnwindDegrees(z);
+
+ if (!useCachedAngles)
+ {
+ _cachedAngles = new Vector3(x, y, z);
+ }
+
+ _cachedToken = token;
+
Quaternion.Euler(x, y, z, out Quaternion value);
SetValue(value, token);
}
+ ///
+ protected override void ClearToken()
+ {
+ _cachedToken = null;
+ base.ClearToken();
+ }
+
///
public override void Refresh()
{
diff --git a/Source/Editor/CustomEditors/Elements/Container/HorizontalPanelElement.cs b/Source/Editor/CustomEditors/Elements/Container/HorizontalPanelElement.cs
index 2958cd337..de015a371 100644
--- a/Source/Editor/CustomEditors/Elements/Container/HorizontalPanelElement.cs
+++ b/Source/Editor/CustomEditors/Elements/Container/HorizontalPanelElement.cs
@@ -5,15 +5,15 @@ using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Elements
{
///
- /// The vertical panel element.
+ /// The horizontal panel element.
///
///
- public class VerticalPanelElement : LayoutElementsContainer
+ public class HorizontalPanelElement : LayoutElementsContainer
{
///
/// The panel.
///
- public readonly VerticalPanel Panel = new VerticalPanel();
+ public readonly HorizontalPanel Panel = new HorizontalPanel();
///
public override ContainerControl ContainerControl => Panel;
diff --git a/Source/Editor/CustomEditors/Elements/Container/VerticalPanelElement.cs b/Source/Editor/CustomEditors/Elements/Container/VerticalPanelElement.cs
index de015a371..2958cd337 100644
--- a/Source/Editor/CustomEditors/Elements/Container/VerticalPanelElement.cs
+++ b/Source/Editor/CustomEditors/Elements/Container/VerticalPanelElement.cs
@@ -5,15 +5,15 @@ using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Elements
{
///
- /// The horizontal panel element.
+ /// The vertical panel element.
///
///
- public class HorizontalPanelElement : LayoutElementsContainer
+ public class VerticalPanelElement : LayoutElementsContainer
{
///
/// The panel.
///
- public readonly HorizontalPanel Panel = new HorizontalPanel();
+ public readonly VerticalPanel Panel = new VerticalPanel();
///
public override ContainerControl ContainerControl => Panel;
diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs
index ae33599c1..6f645789a 100644
--- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs
+++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs
@@ -112,7 +112,7 @@ namespace FlaxEditor.CustomEditors
OnAddElement(element);
return element;
}
-
+
///
/// Adds new horizontal panel element.
///
@@ -690,6 +690,17 @@ namespace FlaxEditor.CustomEditors
return element;
}
+ ///
+ /// Adds custom element to the layout.
+ ///
+ /// The element.
+ public void AddElement(LayoutElement element)
+ {
+ if (element == null)
+ throw new ArgumentNullException();
+ OnAddElement(element);
+ }
+
///
/// Called when element is added to the layout.
///
diff --git a/Source/Editor/CustomEditors/Values/ListValueContainer.cs b/Source/Editor/CustomEditors/Values/ListValueContainer.cs
index f8e5dd26c..416b866cc 100644
--- a/Source/Editor/CustomEditors/Values/ListValueContainer.cs
+++ b/Source/Editor/CustomEditors/Values/ListValueContainer.cs
@@ -46,10 +46,7 @@ namespace FlaxEditor.CustomEditors
if (values.HasReferenceValue)
{
- var v = (IList)values.ReferenceValue;
-
- // Get the reference value if collections are the same size
- if (v != null && values.Count == v.Count)
+ if (values.ReferenceValue is IList v && values.Count == v.Count && v.Count > index)
{
_referenceValue = v[index];
_hasReferenceValue = true;
diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs
index f8cecb801..49d3b5591 100644
--- a/Source/Editor/CustomEditors/Values/ValueContainer.cs
+++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs
@@ -43,7 +43,7 @@ namespace FlaxEditor.CustomEditors
///
/// Gets the values type.
///
- public ScriptType Type { get; }
+ public ScriptType Type { get; private set; }
///
/// Gets a value indicating whether single object is selected.
@@ -167,14 +167,14 @@ namespace FlaxEditor.CustomEditors
{
if (_hasReferenceValue)
{
- if (_referenceValue is SceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink)
+ if (_referenceValue is SceneObject referenceSceneObject && referenceSceneObject && referenceSceneObject.HasPrefabLink)
{
for (int i = 0; i < Count; i++)
{
if (this[i] == referenceSceneObject)
continue;
- if (this[i] == null || (this[i] is SceneObject valueSceneObject && valueSceneObject.PrefabObjectID != referenceSceneObject.PrefabObjectID))
+ if (this[i] == null || (this[i] is SceneObject valueSceneObject && valueSceneObject && valueSceneObject.PrefabObjectID != referenceSceneObject.PrefabObjectID))
return true;
}
}
@@ -251,6 +251,10 @@ namespace FlaxEditor.CustomEditors
}
if (instanceValues._hasReferenceValue)
{
+ // If the reference value is set for the parent values but it's null object then skip it
+ if (instanceValues._referenceValue == null && !instanceValues.Type.IsValueType)
+ return;
+
_referenceValue = Info.GetValue(instanceValues._referenceValue);
_hasReferenceValue = true;
}
@@ -280,6 +284,15 @@ namespace FlaxEditor.CustomEditors
Type = type;
}
+ ///
+ /// Sets the type. Use with caution.
+ ///
+ /// The type.
+ public void SetType(ScriptType type)
+ {
+ Type = type;
+ }
+
///
/// Gets the custom attributes defined for the values source member.
///
diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs
index 3ca463a2c..69c842820 100644
--- a/Source/Editor/Editor.Build.cs
+++ b/Source/Editor/Editor.Build.cs
@@ -51,14 +51,17 @@ public class Editor : EditorModule
var platformToolsRoot = Path.Combine(FolderPath, "Cooker", "Platform");
var platformToolsRootExternal = Path.Combine(Globals.EngineRoot, "Source", "Platforms");
- AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Windows", "PLATFORM_TOOLS_WINDOWS");
- AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "UWP", "PLATFORM_TOOLS_UWP");
- AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "UWP", "PLATFORM_TOOLS_XBOX_ONE");
+ if (options.Platform.Target == TargetPlatform.Windows)
+ {
+ AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Windows", "PLATFORM_TOOLS_WINDOWS");
+ AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "UWP", "PLATFORM_TOOLS_UWP");
+ AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "UWP", "PLATFORM_TOOLS_XBOX_ONE");
+ AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "PS4", "PLATFORM_TOOLS_PS4");
+ AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "XboxScarlett", "PLATFORM_TOOLS_XBOX_SCARLETT");
+ AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Android", "PLATFORM_TOOLS_ANDROID");
+ AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Switch", "PLATFORM_TOOLS_SWITCH");
+ }
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Linux", "PLATFORM_TOOLS_LINUX");
- AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "PS4", "PLATFORM_TOOLS_PS4");
- AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "XboxScarlett", "PLATFORM_TOOLS_XBOX_SCARLETT");
- AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Android", "PLATFORM_TOOLS_ANDROID");
- AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Switch", "PLATFORM_TOOLS_SWITCH");
// Visual Studio integration
if (options.Platform.Target == TargetPlatform.Windows)
diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs
index 3aa8346c8..adf4d566b 100644
--- a/Source/Editor/Editor.cs
+++ b/Source/Editor/Editor.cs
@@ -220,7 +220,7 @@ namespace FlaxEditor
GameProject = ProjectInfo.Load(Internal_GetProjectPath());
Icons = new EditorIcons();
- Icons.GetIcons();
+ Icons.LoadIcons();
// Create common editor modules
RegisterModule(Options = new OptionsModule(this));
@@ -752,10 +752,11 @@ namespace FlaxEditor
/// The collision data type.
/// The source model.
/// The source model LOD index.
+ /// The source model material slots mask. One bit per-slot. Can be sued to exclude particular material slots from collision cooking.
/// The convex mesh generation flags.
/// The convex mesh vertex limit. Use values in range [8;255]
/// True if failed, otherwise false.
- public static bool CookMeshCollision(string path, CollisionDataType type, Model model, int modelLodIndex = 0, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags.None, int convexVertexLimit = 255)
+ public static bool CookMeshCollision(string path, CollisionDataType type, ModelBase model, int modelLodIndex = 0, uint materialSlotsMask = uint.MaxValue, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags.None, int convexVertexLimit = 255)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException(nameof(path));
@@ -764,7 +765,7 @@ namespace FlaxEditor
if (type == CollisionDataType.None)
throw new ArgumentException(nameof(type));
- return Internal_CookMeshCollision(path, type, FlaxEngine.Object.GetUnmanagedPtr(model), modelLodIndex, convexFlags, convexVertexLimit);
+ return Internal_CookMeshCollision(path, type, FlaxEngine.Object.GetUnmanagedPtr(model), modelLodIndex, materialSlotsMask, convexFlags, convexVertexLimit);
}
///
@@ -816,9 +817,16 @@ namespace FlaxEditor
/// The bounding sphere.
public static void GetActorEditorSphere(Actor actor, out BoundingSphere sphere)
{
- Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out var box);
- BoundingSphere.FromBox(ref box, out sphere);
- sphere.Radius = Math.Max(sphere.Radius, 15.0f);
+ if (actor)
+ {
+ Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out var box);
+ BoundingSphere.FromBox(ref box, out sphere);
+ sphere.Radius = Math.Max(sphere.Radius, 15.0f);
+ }
+ else
+ {
+ sphere = BoundingSphere.Empty;
+ }
}
///
@@ -828,7 +836,14 @@ namespace FlaxEditor
/// The bounding box.
public static void GetActorEditorBox(Actor actor, out BoundingBox box)
{
- Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out box);
+ if (actor)
+ {
+ Internal_GetEditorBoxWithChildren(FlaxEngine.Object.GetUnmanagedPtr(actor), out box);
+ }
+ else
+ {
+ box = BoundingBox.Zero;
+ }
}
///
@@ -863,10 +878,12 @@ namespace FlaxEditor
/// Checks if can import asset with the given extension.
///
/// The file extension.
+ /// The output file extension (flax, json, etc.).
/// True if can import files with given extension, otherwise false.
- public static bool CanImport(string extension)
+ public static bool CanImport(string extension, out string outputExtension)
{
- return Internal_CanImport(extension);
+ outputExtension = Internal_CanImport(extension);
+ return outputExtension != null;
}
///
@@ -1166,8 +1183,10 @@ namespace FlaxEditor
if (Windows.GameWin != null && Windows.GameWin.ContainsFocus)
{
var win = Windows.GameWin.Root;
- if (win != null && win.RootWindow is WindowRootControl root && root.Window.IsFocused)
+ if (win?.RootWindow is WindowRootControl root && root.Window && root.Window.IsFocused)
{
+ if (StateMachine.IsPlayMode && StateMachine.PlayingState.IsPaused)
+ return false;
return true;
}
}
@@ -1179,9 +1198,9 @@ namespace FlaxEditor
if (Windows.GameWin != null && Windows.GameWin.ContainsFocus)
{
var win = Windows.GameWin.Root;
- if (win != null && win.RootWindow is WindowRootControl root && root.Window.IsFocused)
+ if (win?.RootWindow is WindowRootControl root && root.Window && root.Window.IsFocused)
{
- pos = Vector2.Round(Windows.GameWin.Viewport.PointFromWindow(root.Window.ScreenToClient(pos)));
+ pos = Vector2.Round(Windows.GameWin.Viewport.PointFromScreen(pos) * root.DpiScale);
}
else
{
@@ -1199,9 +1218,9 @@ namespace FlaxEditor
if (Windows.GameWin != null && Windows.GameWin.ContainsFocus)
{
var win = Windows.GameWin.Root;
- if (win != null && win.RootWindow is WindowRootControl root && root.Window.IsFocused)
+ if (win?.RootWindow is WindowRootControl root && root.Window && root.Window.IsFocused)
{
- pos = Vector2.Round(root.Window.ClientToScreen(Windows.GameWin.Viewport.PointToWindow(pos)));
+ pos = Vector2.Round(Windows.GameWin.Viewport.PointToScreen(pos / root.DpiScale));
}
else
{
@@ -1231,13 +1250,18 @@ namespace FlaxEditor
var gameWin = Windows.GameWin;
if (gameWin != null)
{
- // Handle case when Game window is not selected in tab view
- var dockedTo = gameWin.ParentDockPanel;
- if (dockedTo != null && dockedTo.SelectedTab != gameWin && dockedTo.SelectedTab != null)
- resultAsRef = dockedTo.SelectedTab.Size;
- else
- resultAsRef = gameWin.Size;
- resultAsRef = Vector2.Round(resultAsRef);
+ var win = gameWin.Root;
+ if (win != null && win.RootWindow is WindowRootControl root)
+ {
+ // Handle case when Game window is not selected in tab view
+ var dockedTo = gameWin.ParentDockPanel;
+ if (dockedTo != null && dockedTo.SelectedTab != gameWin && dockedTo.SelectedTab != null)
+ resultAsRef = dockedTo.SelectedTab.Size * root.DpiScale;
+ else
+ resultAsRef = gameWin.Size * root.DpiScale;
+
+ resultAsRef = Vector2.Round(resultAsRef);
+ }
}
}
@@ -1325,7 +1349,7 @@ namespace FlaxEditor
internal static extern string Internal_GetShaderAssetSourceCode(IntPtr obj);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_CookMeshCollision(string path, CollisionDataType type, IntPtr model, int modelLodIndex, ConvexMeshGenerationFlags convexFlags, int convexVertexLimit);
+ internal static extern bool Internal_CookMeshCollision(string path, CollisionDataType type, IntPtr model, int modelLodIndex, uint materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int convexVertexLimit);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_GetCollisionWires(IntPtr collisionData, out Vector3[] triangles, out int[] indices);
@@ -1349,7 +1373,7 @@ namespace FlaxEditor
internal static extern bool Internal_CreateVisualScript(string outputPath, string baseTypename);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_CanImport(string extension);
+ internal static extern string Internal_CanImport(string extension);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_CanExport(string path);
diff --git a/Source/Editor/EditorIcons.cs b/Source/Editor/EditorIcons.cs
index 700b5e25e..d4b89b8d3 100644
--- a/Source/Editor/EditorIcons.cs
+++ b/Source/Editor/EditorIcons.cs
@@ -16,102 +16,124 @@ namespace FlaxEditor
[HideInEditor]
public sealed class EditorIcons
{
- public SpriteHandle FolderClosed12;
- public SpriteHandle FolderOpened12;
+ // 12px
public SpriteHandle DragBar12;
- public SpriteHandle ArrowDown12;
- public SpriteHandle ArrowRight12;
public SpriteHandle Search12;
+ public SpriteHandle WindowDrag12;
+ public SpriteHandle CheckBoxIntermediate12;
+ public SpriteHandle ArrowRight12;
public SpriteHandle Settings12;
public SpriteHandle Cross12;
- public SpriteHandle CheckBoxIntermediate12;
public SpriteHandle CheckBoxTick12;
- public SpriteHandle StatusBarSizeGrip12;
+ public SpriteHandle ArrowDown12;
- public SpriteHandle ArrowRightBorder16;
- public SpriteHandle World16;
- public SpriteHandle ScaleStep16;
- public SpriteHandle RotateStep16;
- public SpriteHandle Grid16;
- public SpriteHandle Translate16;
- public SpriteHandle Rotate16;
- public SpriteHandle Scale16;
- public SpriteHandle Link16;
- public SpriteHandle Docs16;
-
- public SpriteHandle Save32;
- public SpriteHandle Undo32;
- public SpriteHandle Redo32;
+ // 32px
+ public SpriteHandle Scalar32;
public SpriteHandle Translate32;
public SpriteHandle Rotate32;
public SpriteHandle Scale32;
- public SpriteHandle Play32;
- public SpriteHandle Pause32;
- public SpriteHandle Step32;
- public SpriteHandle Stop32;
- public SpriteHandle PageScale32;
- public SpriteHandle Bone32;
- public SpriteHandle Docs32;
- public SpriteHandle Import32;
- public SpriteHandle AddDoc32;
- public SpriteHandle RemoveDoc32;
- public SpriteHandle BracketsSlash32;
- public SpriteHandle Find32;
- public SpriteHandle Reload32;
- public SpriteHandle ArrowLeft32;
- public SpriteHandle ArrowRight32;
- public SpriteHandle ArrowDown32;
- public SpriteHandle ArrowUp32;
- public SpriteHandle Error32;
- public SpriteHandle Warning32;
- public SpriteHandle Info32;
- public SpriteHandle UV32;
- public SpriteHandle Image32;
+ public SpriteHandle Grid32;
+ public SpriteHandle Flax32;
+ public SpriteHandle RotateSnap32;
+ public SpriteHandle ScaleSnap32;
+ public SpriteHandle Globe32;
+ public SpriteHandle CamSpeed32;
public SpriteHandle Link32;
- public SpriteHandle Next32;
- public SpriteHandle Camera32;
- public SpriteHandle Build32;
+ public SpriteHandle Add32;
+ public SpriteHandle Left32;
+ public SpriteHandle Right32;
+ public SpriteHandle Up32;
+ public SpriteHandle Down32;
+ public SpriteHandle FolderClosed32;
+ public SpriteHandle FolderOpen32;
- public SpriteHandle Add48;
- public SpriteHandle Paint48;
- public SpriteHandle Foliage48;
- public SpriteHandle Mountain48;
+ // Visject
+ public SpriteHandle VisjectBoxOpen32;
+ public SpriteHandle VisjectBoxClosed32;
+ public SpriteHandle VisjectArrowOpen32;
+ public SpriteHandle VisjectArrowClosed32;
- public SpriteHandle Plugin64;
- public SpriteHandle Document64;
- public SpriteHandle CSharpScript64;
- public SpriteHandle CppScript64;
+ // 64px
+ public SpriteHandle Flax64;
+ public SpriteHandle Save64;
+ public SpriteHandle Play64;
+ public SpriteHandle Stop64;
+ public SpriteHandle Pause64;
+ public SpriteHandle Skip64;
+ public SpriteHandle Info64;
+ public SpriteHandle Error64;
+ public SpriteHandle Warning64;
+ public SpriteHandle AddFile64;
+ public SpriteHandle DeleteFile64;
+ public SpriteHandle Import64;
+ public SpriteHandle Left64;
+ public SpriteHandle Right64;
+ public SpriteHandle Up64;
+ public SpriteHandle Down64;
+ public SpriteHandle Undo64;
+ public SpriteHandle Redo64;
+ public SpriteHandle Translate64;
+ public SpriteHandle Rotate64;
+ public SpriteHandle Scale64;
+ public SpriteHandle Refresh64;
+ public SpriteHandle Shift64;
+ public SpriteHandle Code64;
public SpriteHandle Folder64;
- public SpriteHandle Scene64;
- public SpriteHandle CodeScript64;
+ public SpriteHandle CenterView64;
+ public SpriteHandle Image64;
+ public SpriteHandle Camera64;
+ public SpriteHandle Docs64;
+ public SpriteHandle Search64;
+ public SpriteHandle Bone64;
+ public SpriteHandle Link64;
+ public SpriteHandle Build64;
+ public SpriteHandle Add64;
- public SpriteHandle Logo128;
+ // 96px
+ public SpriteHandle Toolbox96;
+ public SpriteHandle Paint96;
+ public SpriteHandle Foliage96;
+ public SpriteHandle Terrain96;
- public SpriteHandle VisjectBoxOpen;
- public SpriteHandle VisjectBoxClose;
- public SpriteHandle VisjectArrowOpen;
- public SpriteHandle VisjectArrowClose;
+ // 128px
+ public SpriteHandle AndroidSettings128;
+ public SpriteHandle PlaystationSettings128;
+ public SpriteHandle InputSettings128;
+ public SpriteHandle PhysicsSettings128;
+ public SpriteHandle CSharpScript128;
+ public SpriteHandle Folder128;
+ public SpriteHandle WindowsIcon128;
+ public SpriteHandle LinuxIcon128;
+ public SpriteHandle UWPSettings128;
+ public SpriteHandle XBOXSettings128;
+ public SpriteHandle LayersTagsSettings128;
+ public SpriteHandle GraphicsSettings128;
+ public SpriteHandle CPPScript128;
+ public SpriteHandle Plugin128;
+ public SpriteHandle XBoxScarletIcon128;
+ public SpriteHandle AssetShadow128;
+ public SpriteHandle WindowsSettings128;
+ public SpriteHandle TimeSettings128;
+ public SpriteHandle GameSettings128;
+ public SpriteHandle VisualScript128;
+ public SpriteHandle Document128;
+ public SpriteHandle XBoxOne128;
+ public SpriteHandle UWPStore128;
+ public SpriteHandle ColorWheel128;
+ public SpriteHandle LinuxSettings128;
+ public SpriteHandle NavigationSettings128;
+ public SpriteHandle AudioSettings128;
+ public SpriteHandle BuildSettings128;
+ public SpriteHandle Scene128;
+ public SpriteHandle AndroidIcon128;
+ public SpriteHandle PS4Icon128;
+ public SpriteHandle FlaxLogo128;
- public SpriteHandle AssetShadow;
- public SpriteHandle ColorWheel;
- public SpriteHandle Windows;
- public SpriteHandle XboxOne;
- public SpriteHandle WindowsStore;
- public SpriteHandle Linux;
- public SpriteHandle PS4;
- public SpriteHandle XboxSeriesX;
- public SpriteHandle Android;
-
- internal void GetIcons()
+ internal void LoadIcons()
{
- // Load asset
+ // Load & validate
var iconsAtlas = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.IconsAtlas);
- if (iconsAtlas == null)
- {
- Editor.LogError("Cannot load editor icons atlas.");
- return;
- }
- if (iconsAtlas.WaitForLoaded())
+ if (iconsAtlas is null || iconsAtlas.WaitForLoaded())
{
Editor.LogError("Failed to load editor icons atlas.");
return;
@@ -122,11 +144,11 @@ namespace FlaxEditor
for (int i = 0; i < fields.Length; i++)
{
var field = fields[i];
+
var sprite = iconsAtlas.FindSprite(field.Name);
if (!sprite.IsValid)
- {
- Editor.LogWarning(string.Format("Failed to load sprite icon \'{0}\'.", field.Name));
- }
+ Editor.LogWarning($"Failed to load sprite icon \'{field.Name}\'.");
+
field.SetValue(this, sprite);
}
}
diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs
index aa1c13c8a..d06c1eef9 100644
--- a/Source/Editor/GUI/ComboBox.cs
+++ b/Source/Editor/GUI/ComboBox.cs
@@ -349,6 +349,8 @@ namespace FlaxEditor.GUI
///
protected virtual void OnSelectedIndexChanged()
{
+ if (_tooltips != null && _tooltips.Length == _items.Count)
+ TooltipText = _selectedIndices.Count == 1 ? _tooltips[_selectedIndices[0]] : null;
SelectedIndexChanged?.Invoke(this);
}
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenu.cs
index 04dded53c..966f3502b 100644
--- a/Source/Editor/GUI/ContextMenu/ContextMenu.cs
+++ b/Source/Editor/GUI/ContextMenu/ContextMenu.cs
@@ -439,7 +439,7 @@ namespace FlaxEditor.GUI.ContextMenu
}
}
}
- if (startIndex != -1)
+ if (startIndex > 0 && startIndex <= _panel.Children.Count)
{
// No more items found so start from the top if there are matching items
_panel.Children[startIndex - 1].Defocus();
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
index 6caf7f651..402374a8e 100644
--- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
+++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
@@ -47,6 +47,11 @@ namespace FlaxEditor.GUI.ContextMenu
private Window _window;
private Control _previouslyFocused;
+ ///
+ /// Gets a value indicating whether use automatic popup direction fix based on the screen dimensions.
+ ///
+ protected virtual bool UseAutomaticDirectionFix => true;
+
///
/// Returns true if context menu is opened
///
@@ -124,7 +129,7 @@ namespace FlaxEditor.GUI.ContextMenu
PerformLayout();
// Calculate popup direction and initial location (fit on a single monitor)
- var dpiScale = Platform.DpiScale;
+ var dpiScale = parentWin.DpiScale;
Vector2 dpiSize = Size * dpiScale;
Vector2 locationWS = parent.PointToWindow(location);
Vector2 locationSS = parentWin.PointToScreen(locationWS);
@@ -132,21 +137,24 @@ namespace FlaxEditor.GUI.ContextMenu
Rectangle monitorBounds = Platform.GetMonitorBounds(locationSS);
Vector2 rightBottomLocationSS = locationSS + dpiSize;
bool isUp = false, isLeft = false;
- if (monitorBounds.Bottom < rightBottomLocationSS.Y)
+ if (UseAutomaticDirectionFix)
{
- // Direction: up
- isUp = true;
- locationSS.Y -= dpiSize.Y;
+ if (monitorBounds.Bottom < rightBottomLocationSS.Y)
+ {
+ // Direction: up
+ isUp = true;
+ locationSS.Y -= dpiSize.Y;
- // Offset to fix sub-menu location
- if (parent is ContextMenu menu && menu._childCM != null)
- locationSS.Y += 30.0f * dpiScale;
- }
- if (monitorBounds.Right < rightBottomLocationSS.X)
- {
- // Direction: left
- isLeft = true;
- locationSS.X -= dpiSize.X;
+ // Offset to fix sub-menu location
+ if (parent is ContextMenu menu && menu._childCM != null)
+ locationSS.Y += 30.0f * dpiScale;
+ }
+ if (monitorBounds.Right < rightBottomLocationSS.X)
+ {
+ // Direction: left
+ isLeft = true;
+ locationSS.X -= dpiSize.X;
+ }
}
// Update direction flag
@@ -275,7 +283,7 @@ namespace FlaxEditor.GUI.ContextMenu
{
if (_window != null)
{
- _window.ClientSize = Size * Platform.DpiScale;
+ _window.ClientSize = Size * _window.DpiScale;
}
}
diff --git a/Source/Editor/GUI/Dialogs/ColorSelector.cs b/Source/Editor/GUI/Dialogs/ColorSelector.cs
index da1b913a5..0cfc452fe 100644
--- a/Source/Editor/GUI/Dialogs/ColorSelector.cs
+++ b/Source/Editor/GUI/Dialogs/ColorSelector.cs
@@ -61,7 +61,7 @@ namespace FlaxEditor.GUI.Dialogs
public ColorSelector(float wheelSize = 64)
: base(0, 0, wheelSize, wheelSize)
{
- _colorWheelSprite = Editor.Instance.Icons.ColorWheel;
+ _colorWheelSprite = Editor.Instance.Icons.ColorWheel128;
_wheelRect = new Rectangle(0, 0, wheelSize, wheelSize);
}
diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs
index 173dd9128..c33429609 100644
--- a/Source/Editor/GUI/Dialogs/Dialog.cs
+++ b/Source/Editor/GUI/Dialogs/Dialog.cs
@@ -143,7 +143,7 @@ namespace FlaxEditor.GUI.Dialogs
// Setup initial window settings
CreateWindowSettings settings = CreateWindowSettings.Default;
settings.Title = _title;
- settings.Size = _dialogSize * Platform.DpiScale;
+ settings.Size = _dialogSize * parentWindow.DpiScale;
settings.AllowMaximize = false;
settings.AllowMinimize = false;
settings.HasSizingFrame = false;
diff --git a/Source/Editor/GUI/Docking/DockPanel.cs b/Source/Editor/GUI/Docking/DockPanel.cs
index 2865515da..f968c2501 100644
--- a/Source/Editor/GUI/Docking/DockPanel.cs
+++ b/Source/Editor/GUI/Docking/DockPanel.cs
@@ -123,9 +123,8 @@ namespace FlaxEditor.GUI.Docking
if (parentWin == null)
throw new InvalidOperationException("Missing parent window.");
var control = _tabsProxy != null ? (Control)_tabsProxy : this;
- var dpiScale = Platform.DpiScale;
var clientPos = control.PointToWindow(Vector2.Zero);
- return new Rectangle(parentWin.PointToScreen(clientPos), control.Size * dpiScale);
+ return new Rectangle(parentWin.PointToScreen(clientPos), control.Size * DpiScale);
}
}
@@ -290,6 +289,10 @@ namespace FlaxEditor.GUI.Docking
}
OnSelectedTabChanged();
}
+ else if (autoFocus && _selectedTab != null && !_selectedTab.ContainsFocus)
+ {
+ _selectedTab.Focus();
+ }
}
///
diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs
index 1f6b3924e..fbc9d7bee 100644
--- a/Source/Editor/GUI/Docking/DockPanelProxy.cs
+++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs
@@ -348,7 +348,7 @@ namespace FlaxEditor.GUI.Docking
// Cache data
IsMouseRightButtonDown = true;
if (MouseDownWindow != null)
- _panel.SelectTab(MouseDownWindow);
+ _panel.SelectTab(MouseDownWindow, false);
}
else if (button == MouseButton.Middle)
{
diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs
index 5e94ef547..1a28b1307 100644
--- a/Source/Editor/GUI/Docking/DockWindow.cs
+++ b/Source/Editor/GUI/Docking/DockWindow.cs
@@ -59,7 +59,10 @@ namespace FlaxEditor.GUI.Docking
///
/// Gets the default window size.
///
- public virtual Vector2 DefaultSize => new Vector2(900, 580);
+ ///
+ /// Scaled by the DPI, because the window should be large enough for its content on every monitor
+ ///
+ public virtual Vector2 DefaultSize => new Vector2(900, 580) * DpiScale;
///
/// Gets the serialization typename.
diff --git a/Source/Editor/GUI/EnumComboBox.cs b/Source/Editor/GUI/EnumComboBox.cs
index 8ceae0bb0..ed1c80cd6 100644
--- a/Source/Editor/GUI/EnumComboBox.cs
+++ b/Source/Editor/GUI/EnumComboBox.cs
@@ -29,7 +29,7 @@ namespace FlaxEditor.GUI
///
/// The cached value from the UI.
///
- protected int _cachedValue;
+ protected long _cachedValue;
///
/// True if has value cached, otherwise false.
@@ -54,7 +54,7 @@ namespace FlaxEditor.GUI
///
/// The value.
///
- public int Value;
+ public long Value;
///
/// Initializes a new instance of the struct.
@@ -62,7 +62,7 @@ namespace FlaxEditor.GUI
/// The name.
/// The tooltip.
/// The value.
- public Entry(string name, int value, string tooltip = null)
+ public Entry(string name, long value, string tooltip = null)
{
Name = name;
Tooltip = tooltip;
@@ -88,13 +88,13 @@ namespace FlaxEditor.GUI
public object EnumTypeValue
{
get => Enum.ToObject(_enumType, Value);
- set => Value = Convert.ToInt32(value);
+ set => Value = Convert.ToInt64(value);
}
///
/// Gets or sets the value.
///
- public int Value
+ public long Value
{
get => _cachedValue;
set
@@ -209,13 +209,13 @@ namespace FlaxEditor.GUI
///
protected void CacheValue()
{
- int value = 0;
+ long value = 0;
if (IsFlags)
{
var selection = Selection;
for (int i = 0; i < selection.Count; i++)
{
- int index = selection[i];
+ var index = selection[i];
value |= _entries[index].Value;
}
}
@@ -276,7 +276,7 @@ namespace FlaxEditor.GUI
tooltip = tooltipAttr.Text;
}
- entries.Add(new Entry(name, Convert.ToInt32(field.GetRawConstantValue()), tooltip));
+ entries.Add(new Entry(name, Convert.ToInt64(field.GetRawConstantValue()), tooltip));
}
}
@@ -295,9 +295,9 @@ namespace FlaxEditor.GUI
}
// Calculate value that will be set after change
- int valueAfter = 0;
+ long valueAfter = 0;
bool isSelected = _selectedIndices.Contains(index);
- int selectedValue = entries[index].Value;
+ long selectedValue = entries[index].Value;
for (int i = 0; i < _selectedIndices.Count; i++)
{
int selectedIndex = _selectedIndices[i];
diff --git a/Source/Editor/GUI/Input/FloatValueBox.cs b/Source/Editor/GUI/Input/FloatValueBox.cs
index 1bbd56c9a..e66fb721b 100644
--- a/Source/Editor/GUI/Input/FloatValueBox.cs
+++ b/Source/Editor/GUI/Input/FloatValueBox.cs
@@ -23,10 +23,7 @@ namespace FlaxEditor.GUI.Input
value = Mathf.Clamp(value, _min, _max);
if (Math.Abs(_value - value) > Mathf.Epsilon)
{
- // Set value
_value = value;
-
- // Update
UpdateText();
OnValueChanged();
}
@@ -43,7 +40,6 @@ namespace FlaxEditor.GUI.Input
{
if (value > _max)
throw new ArgumentException();
-
_min = value;
Value = Value;
}
@@ -60,7 +56,6 @@ namespace FlaxEditor.GUI.Input
{
if (value < _min)
throw new ArgumentException();
-
_max = value;
Value = Value;
}
@@ -80,9 +75,19 @@ namespace FlaxEditor.GUI.Input
public FloatValueBox(float value, float x = 0, float y = 0, float width = 120, float min = float.MinValue, float max = float.MaxValue, float slideSpeed = 1)
: base(Mathf.Clamp(value, min, max), x, y, width, min, max, slideSpeed)
{
+ TryUseAutoSliderSpeed();
UpdateText();
}
+ private void TryUseAutoSliderSpeed()
+ {
+ var range = _max - _min;
+ if (Mathf.IsOne(_slideSpeed) && range > Mathf.Epsilon * 200.0f && range < 1000000.0f)
+ {
+ _slideSpeed = range * 0.01f;
+ }
+ }
+
///
/// Sets the value limits.
///
@@ -103,6 +108,7 @@ namespace FlaxEditor.GUI.Input
{
_min = limits.Min;
_max = Mathf.Max(_min, limits.Max);
+ TryUseAutoSliderSpeed();
Value = Value;
}
@@ -115,6 +121,7 @@ namespace FlaxEditor.GUI.Input
_min = limits.Min;
_max = Mathf.Max(_min, limits.Max);
_slideSpeed = limits.SliderSpeed;
+ TryUseAutoSliderSpeed();
Value = Value;
}
diff --git a/Source/Editor/GUI/Input/ValueBox.cs b/Source/Editor/GUI/Input/ValueBox.cs
index d34acd17b..b1b0408bd 100644
--- a/Source/Editor/GUI/Input/ValueBox.cs
+++ b/Source/Editor/GUI/Input/ValueBox.cs
@@ -184,7 +184,7 @@ namespace FlaxEditor.GUI.Input
var style = Style.Current;
// Draw sliding UI
- Render2D.DrawSprite(style.Scale, SlideRect, style.Foreground);
+ Render2D.DrawSprite(style.Scalar, SlideRect, style.Foreground);
// Check if is sliding
if (_isSliding)
diff --git a/Source/Editor/GUI/MainMenu.cs b/Source/Editor/GUI/MainMenu.cs
index ff4e02c4b..bf375a358 100644
--- a/Source/Editor/GUI/MainMenu.cs
+++ b/Source/Editor/GUI/MainMenu.cs
@@ -190,15 +190,15 @@ namespace FlaxEditor.GUI
private WindowHitCodes OnHitTest(ref Vector2 mouse)
{
- var pos = _window.ScreenToClient(mouse * Platform.DpiScale);
+ var dpiScale = _window.DpiScale;
if (_window.IsMinimized)
return WindowHitCodes.NoWhere;
if (!_window.IsMaximized)
{
- var dpiScale = Platform.DpiScale;
- var winSize = RootWindow.Size * dpiScale;
+ var pos = _window.ScreenToClient(mouse * dpiScale); // pos is not DPI adjusted
+ var winSize = _window.Size;
// Distance from which the mouse is considered to be on the border/corner
float distance = 5.0f * dpiScale;
@@ -228,11 +228,11 @@ namespace FlaxEditor.GUI
return WindowHitCodes.Bottom;
}
- var menuPos = PointFromWindow(pos);
- var controlUnderMouse = GetChildAt(menuPos);
+ var mousePos = PointFromScreen(mouse * dpiScale);
+ var controlUnderMouse = GetChildAt(mousePos);
var isMouseOverSth = controlUnderMouse != null && controlUnderMouse != _title;
var rb = GetRightButton();
- if (rb != null && _minimizeButton != null && new Rectangle(rb.UpperRight * Platform.DpiScale, (_minimizeButton.BottomLeft - rb.UpperRight) * Platform.DpiScale).Contains(ref menuPos) && !isMouseOverSth)
+ if (rb != null && _minimizeButton != null && new Rectangle(rb.UpperRight, _minimizeButton.BottomLeft - rb.UpperRight).Contains(ref mousePos) && !isMouseOverSth)
return WindowHitCodes.Caption;
return WindowHitCodes.Client;
diff --git a/Source/Editor/GUI/PlatformSelector.cs b/Source/Editor/GUI/PlatformSelector.cs
index d2e8bf682..cc7bc003a 100644
--- a/Source/Editor/GUI/PlatformSelector.cs
+++ b/Source/Editor/GUI/PlatformSelector.cs
@@ -82,13 +82,15 @@ namespace FlaxEditor.GUI
var icons = Editor.Instance.Icons;
var platforms = new[]
{
- new PlatformData(PlatformType.Windows, icons.Windows, "Windows"),
- new PlatformData(PlatformType.XboxOne, icons.XboxOne, "Xbox One"),
- new PlatformData(PlatformType.UWP, icons.WindowsStore, "Windows Store"),
- new PlatformData(PlatformType.Linux, icons.Linux, "Linux"),
- new PlatformData(PlatformType.PS4, icons.PS4, "PlayStation 4"),
- new PlatformData(PlatformType.XboxScarlett, icons.XboxSeriesX, "Xbox Scarlett"),
- new PlatformData(PlatformType.Android, icons.Android, "Android"),
+ new PlatformData(PlatformType.Windows, icons.WindowsIcon128, "Windows"),
+ new PlatformData(PlatformType.XboxOne, icons.XBoxOne128, "Xbox One"),
+ new PlatformData(PlatformType.UWP, icons.UWPStore128, "Windows Store"),
+ new PlatformData(PlatformType.Linux, icons.LinuxIcon128, "Linux"),
+ new PlatformData(PlatformType.PS4, icons.PS4Icon128, "PlayStation 4"),
+ new PlatformData(PlatformType.XboxScarlett, icons.XBoxScarletIcon128, "Xbox Scarlett"),
+ new PlatformData(PlatformType.Android, icons.AndroidIcon128, "Android"),
+ new PlatformData(PlatformType.Switch, icons.ColorWheel128, "Switch"),
+
};
const float IconSize = 48.0f;
diff --git a/Source/Editor/GUI/Popups/RenamePopup.cs b/Source/Editor/GUI/Popups/RenamePopup.cs
index 834e620d9..c1da5dc3e 100644
--- a/Source/Editor/GUI/Popups/RenamePopup.cs
+++ b/Source/Editor/GUI/Popups/RenamePopup.cs
@@ -42,9 +42,6 @@ namespace FlaxEditor.GUI
///
/// Gets or sets the initial value.
///
- ///
- /// The initial value.
- ///
public string InitialValue
{
get => _startValue;
@@ -54,9 +51,6 @@ namespace FlaxEditor.GUI
///
/// Gets or sets the input field text.
///
- ///
- /// The text.
- ///
public string Text
{
get => _inputField.Text;
@@ -138,6 +132,9 @@ namespace FlaxEditor.GUI
Hide();
}
+ ///
+ protected override bool UseAutomaticDirectionFix => false;
+
///
public override bool OnKeyDown(KeyboardKeys key)
{
diff --git a/Source/Editor/GUI/Popups/TypeSearchPopup.cs b/Source/Editor/GUI/Popups/TypeSearchPopup.cs
index f1300d299..a35b2a1c5 100644
--- a/Source/Editor/GUI/Popups/TypeSearchPopup.cs
+++ b/Source/Editor/GUI/Popups/TypeSearchPopup.cs
@@ -79,7 +79,7 @@ namespace FlaxEditor.GUI
var type = allTypes[i];
if (_isValid(type))
{
- var attributes = type.GetAttributes(false);
+ var attributes = type.GetAttributes(true);
if (attributes.FirstOrDefault(x => x is HideInEditorAttribute) == null)
{
AddItem(new TypeItemView(type, attributes));
diff --git a/Source/Editor/GUI/Timeline/GUI/GradientEditor.cs b/Source/Editor/GUI/Timeline/GUI/GradientEditor.cs
index 1fc3578cb..916568571 100644
--- a/Source/Editor/GUI/Timeline/GUI/GradientEditor.cs
+++ b/Source/Editor/GUI/Timeline/GUI/GradientEditor.cs
@@ -63,7 +63,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
var isMouseOver = IsMouseOver;
var color = Gradient._data[Index].Value;
var icons = Editor.Instance.Icons;
- var icon = icons.VisjectBoxClose;
+ var icon = icons.VisjectBoxClosed32;
Render2D.DrawSprite(icon, new Rectangle(0.0f, 0.0f, 10.0f, 10.0f), isMouseOver ? Color.Gray : Color.Black);
Render2D.DrawSprite(icon, new Rectangle(1.0f, 1.0f, 8.0f, 8.0f), color);
diff --git a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs
index 079ef9957..93d9261e5 100644
--- a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs
+++ b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs
@@ -26,7 +26,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
public override void Draw()
{
var style = Style.Current;
- var icon = Editor.Instance.Icons.VisjectArrowClose;
+ var icon = Editor.Instance.Icons.VisjectArrowClosed32;
var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y;
Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1);
diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs
index e0e4083c1..58e6a0f32 100644
--- a/Source/Editor/GUI/Timeline/Timeline.cs
+++ b/Source/Editor/GUI/Timeline/Timeline.cs
@@ -738,7 +738,7 @@ namespace FlaxEditor.GUI.Timeline
_playbackNavigation[0] = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Rewind to timeline start (Home)",
- Brush = new SpriteBrush(icons.Step32),
+ Brush = new SpriteBrush(icons.Skip64),
Enabled = false,
Visible = false,
Rotation = 180.0f,
@@ -750,7 +750,7 @@ namespace FlaxEditor.GUI.Timeline
_playbackNavigation[1] = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Seek back to the previous keyframe (Page Down)",
- Brush = new SpriteBrush(icons.Next32),
+ Brush = new SpriteBrush(icons.Shift64),
Enabled = false,
Visible = false,
Rotation = 180.0f,
@@ -766,7 +766,7 @@ namespace FlaxEditor.GUI.Timeline
_playbackNavigation[2] = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Move one frame back (Left Arrow)",
- Brush = new SpriteBrush(icons.ArrowLeft32),
+ Brush = new SpriteBrush(icons.Left32),
Enabled = false,
Visible = false,
Parent = playbackButtonsPanel
@@ -779,7 +779,7 @@ namespace FlaxEditor.GUI.Timeline
_playbackStop = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Stop playback",
- Brush = new SpriteBrush(icons.Stop32),
+ Brush = new SpriteBrush(icons.Stop64),
Visible = false,
Enabled = false,
Parent = playbackButtonsPanel
@@ -792,7 +792,7 @@ namespace FlaxEditor.GUI.Timeline
_playbackPlay = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Play/pause playback (Space)",
- Brush = new SpriteBrush(icons.Play32),
+ Brush = new SpriteBrush(icons.Play64),
Visible = false,
Tag = false, // Set to true if image is set to Pause, false if Play
Parent = playbackButtonsPanel
@@ -805,7 +805,7 @@ namespace FlaxEditor.GUI.Timeline
_playbackNavigation[3] = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Move one frame forward (Right Arrow)",
- Brush = new SpriteBrush(icons.ArrowRight32),
+ Brush = new SpriteBrush(icons.Right32),
Enabled = false,
Visible = false,
Parent = playbackButtonsPanel
@@ -816,7 +816,7 @@ namespace FlaxEditor.GUI.Timeline
_playbackNavigation[4] = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Seek to the next keyframe (Page Up)",
- Brush = new SpriteBrush(icons.Next32),
+ Brush = new SpriteBrush(icons.Shift64),
Enabled = false,
Visible = false,
Parent = playbackButtonsPanel
@@ -831,7 +831,7 @@ namespace FlaxEditor.GUI.Timeline
_playbackNavigation[5] = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Rewind to timeline end (End)",
- Brush = new SpriteBrush(icons.Step32),
+ Brush = new SpriteBrush(icons.Skip64),
Enabled = false,
Visible = false,
Parent = playbackButtonsPanel
@@ -1189,7 +1189,7 @@ namespace FlaxEditor.GUI.Timeline
{
_playbackPlay.Visible = true;
_playbackPlay.Enabled = _canPlayPauseStop;
- _playbackPlay.Brush = new SpriteBrush(icons.Play32);
+ _playbackPlay.Brush = new SpriteBrush(icons.Play64);
_playbackPlay.Tag = false;
}
if (_positionHandle != null)
@@ -1215,7 +1215,7 @@ namespace FlaxEditor.GUI.Timeline
{
_playbackPlay.Visible = true;
_playbackPlay.Enabled = _canPlayPauseStop;
- _playbackPlay.Brush = new SpriteBrush(icons.Pause32);
+ _playbackPlay.Brush = new SpriteBrush(icons.Pause64);
_playbackPlay.Tag = true;
}
if (_positionHandle != null)
@@ -1241,7 +1241,7 @@ namespace FlaxEditor.GUI.Timeline
{
_playbackPlay.Visible = true;
_playbackPlay.Enabled = _canPlayPauseStop;
- _playbackPlay.Brush = new SpriteBrush(icons.Play32);
+ _playbackPlay.Brush = new SpriteBrush(icons.Play64);
_playbackPlay.Tag = false;
}
if (_positionHandle != null)
@@ -1438,6 +1438,7 @@ namespace FlaxEditor.GUI.Timeline
ArrangeTracks();
PerformLayout(true);
UnlockChildrenRecursive();
+ PerformLayout(true);
Profiler.EndEvent();
ClearEditedFlag();
diff --git a/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs b/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs
index 031226a02..98cb3979f 100644
--- a/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/AudioTrack.cs
@@ -322,7 +322,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
IsScrollable = false,
Color = Style.Current.ForegroundGrey,
Margin = new Margin(1),
- Brush = new SpriteBrush(icons.ArrowRight32),
+ Brush = new SpriteBrush(icons.Right32),
Offsets = new Margin(-buttonSize - 2 + _muteCheckbox.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize),
Parent = this,
};
@@ -335,7 +335,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
IsScrollable = false,
Color = Style.Current.ForegroundGrey,
Margin = new Margin(3),
- Brush = new SpriteBrush(icons.Add48),
+ Brush = new SpriteBrush(icons.Add32),
Offsets = new Margin(-buttonSize - 2 + rightKey.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize),
Parent = this,
};
@@ -348,7 +348,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
IsScrollable = false,
Color = Style.Current.ForegroundGrey,
Margin = new Margin(1),
- Brush = new SpriteBrush(icons.ArrowLeft32),
+ Brush = new SpriteBrush(icons.Left32),
Offsets = new Margin(-buttonSize - 2 + addKey.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize),
Parent = this,
};
diff --git a/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs b/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs
index 0c3ab7851..fc9b17601 100644
--- a/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs
@@ -695,7 +695,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
IsScrollable = false,
Color = Style.Current.ForegroundGrey,
Margin = new Margin(1),
- Brush = new SpriteBrush(icons.Camera32),
+ Brush = new SpriteBrush(icons.Camera64),
Offsets = new Margin(-buttonSize - 2 + _selectActor.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize),
Parent = this,
};
diff --git a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs
index 250fb73c3..302570fae 100644
--- a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs
@@ -128,7 +128,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
IsScrollable = false,
Color = Style.Current.ForegroundGrey,
Margin = new Margin(1),
- Brush = new SpriteBrush(icons.ArrowRight32),
+ Brush = new SpriteBrush(icons.Right64),
Offsets = new Margin(-buttonSize - 2 + uiLeft, buttonSize, buttonSize * -0.5f, buttonSize),
Parent = this,
};
@@ -138,9 +138,9 @@ namespace FlaxEditor.GUI.Timeline.Tracks
AutoFocus = true,
AnchorPreset = AnchorPresets.MiddleRight,
IsScrollable = false,
- Color = Style.Current.ForegroundGrey,
+ Color = Style.Current.Foreground,
Margin = new Margin(3),
- Brush = new SpriteBrush(icons.Add48),
+ Brush = new SpriteBrush(icons.Add64),
Offsets = new Margin(-buttonSize - 2 + _rightKey.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize),
Parent = this,
};
@@ -150,9 +150,9 @@ namespace FlaxEditor.GUI.Timeline.Tracks
AutoFocus = true,
AnchorPreset = AnchorPresets.MiddleRight,
IsScrollable = false,
- Color = Style.Current.ForegroundGrey,
+ Color = Style.Current.Foreground,
Margin = new Margin(1),
- Brush = new SpriteBrush(icons.ArrowLeft32),
+ Brush = new SpriteBrush(icons.Left64),
Offsets = new Margin(-buttonSize - 2 + _addKey.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize),
Parent = this,
};
diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs
index afda8484e..f490de430 100644
--- a/Source/Editor/Gizmo/TransformGizmo.cs
+++ b/Source/Editor/Gizmo/TransformGizmo.cs
@@ -50,6 +50,41 @@ namespace FlaxEditor.Gizmo
{
}
+ ///
+ /// Helper function, recursively finds the Prefab Root of node or null.
+ ///
+ /// The node from which to start.
+ /// The prefab root or null.
+ public ActorNode GetPrefabRootInParent(ActorNode node)
+ {
+ if (!node.HasPrefabLink)
+ return null;
+ if (node.Actor.IsPrefabRoot)
+ return node;
+ if (node.ParentNode is ActorNode parAct)
+ return GetPrefabRootInParent(parAct);
+ return null;
+ }
+
+ ///
+ /// Recursively walks up from the node up to ceiling node(inclusive) or selection(exclusive).
+ ///
+ /// The node from which to start
+ /// The ceiling(inclusive)
+ /// The node to select.
+ public ActorNode WalkUpAndFindActorNodeBeforeSelection(ActorNode node, ActorNode ceiling)
+ {
+ if (node == ceiling || _selection.Contains(node))
+ return node;
+ if (node.ParentNode is ActorNode parentNode)
+ {
+ if (_selection.Contains(node.ParentNode))
+ return node;
+ return WalkUpAndFindActorNodeBeforeSelection(parentNode, ceiling);
+ }
+ return node;
+ }
+
///
public override void Pick()
{
@@ -100,6 +135,16 @@ namespace FlaxEditor.Gizmo
}
}
+ // Select prefab root and then go down until you find the actual item in which case select the prefab root again
+ if (hit is ActorNode actorNode)
+ {
+ ActorNode prefabRoot = GetPrefabRootInParent(actorNode);
+ if (prefabRoot != null && actorNode != prefabRoot)
+ {
+ hit = WalkUpAndFindActorNodeBeforeSelection(actorNode, prefabRoot);
+ }
+ }
+
bool addRemove = Owner.IsControlDown;
bool isSelected = sceneEditing.Selection.Contains(hit);
diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs b/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs
index c1f7d1d0d..f8277999e 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs
@@ -26,7 +26,7 @@ namespace FlaxEditor.Gizmo
// Return arithmetic average or whatever it means
return center / count;
}
-
+
private bool IntersectsRotateCircle(Vector3 normal, ref Ray ray, out float distance)
{
var plane = new Plane(Vector3.Zero, normal);
@@ -51,7 +51,7 @@ namespace FlaxEditor.Gizmo
Vector3.Transform(ref ray.Position, ref invGizmoWorld, out localRay.Position);
// Find gizmo collisions with mouse
- float closestintersection = float.MaxValue;
+ float closestIntersection = float.MaxValue;
float intersection;
_activeAxis = Axis.None;
switch (_activeMode)
@@ -59,42 +59,42 @@ namespace FlaxEditor.Gizmo
case Mode.Translate:
{
// Axis boxes collision
- if (XAxisBox.Intersects(ref localRay, out intersection) && intersection < closestintersection)
+ if (XAxisBox.Intersects(ref localRay, out intersection) && intersection < closestIntersection)
{
_activeAxis = Axis.X;
- closestintersection = intersection;
+ closestIntersection = intersection;
}
- if (YAxisBox.Intersects(ref localRay, out intersection) && intersection < closestintersection)
+ if (YAxisBox.Intersects(ref localRay, out intersection) && intersection < closestIntersection)
{
_activeAxis = Axis.Y;
- closestintersection = intersection;
+ closestIntersection = intersection;
}
- if (ZAxisBox.Intersects(ref localRay, out intersection) && intersection < closestintersection)
+ if (ZAxisBox.Intersects(ref localRay, out intersection) && intersection < closestIntersection)
{
_activeAxis = Axis.Z;
- closestintersection = intersection;
+ closestIntersection = intersection;
}
// Quad planes collision
- if (closestintersection >= float.MaxValue)
- closestintersection = float.MinValue;
- if (XYBox.Intersects(ref localRay, out intersection) && intersection > closestintersection)
+ if (closestIntersection >= float.MaxValue)
+ closestIntersection = float.MinValue;
+ if (XYBox.Intersects(ref localRay, out intersection) && intersection > closestIntersection)
{
_activeAxis = Axis.XY;
- closestintersection = intersection;
+ closestIntersection = intersection;
}
- if (XZBox.Intersects(ref localRay, out intersection) && intersection > closestintersection)
+ if (XZBox.Intersects(ref localRay, out intersection) && intersection > closestIntersection)
{
_activeAxis = Axis.ZX;
- closestintersection = intersection;
+ closestIntersection = intersection;
}
- if (YZBox.Intersects(ref localRay, out intersection) && intersection > closestintersection)
+ if (YZBox.Intersects(ref localRay, out intersection) && intersection > closestIntersection)
{
_activeAxis = Axis.YZ;
- closestintersection = intersection;
+ closestIntersection = intersection;
}
break;
@@ -103,20 +103,20 @@ namespace FlaxEditor.Gizmo
case Mode.Rotate:
{
// Circles
- if (IntersectsRotateCircle(Vector3.UnitX, ref localRay, out intersection) && intersection < closestintersection)
+ if (IntersectsRotateCircle(Vector3.UnitX, ref localRay, out intersection) && intersection < closestIntersection)
{
_activeAxis = Axis.X;
- closestintersection = intersection;
+ closestIntersection = intersection;
}
- if (IntersectsRotateCircle(Vector3.UnitY, ref localRay, out intersection) && intersection < closestintersection)
+ if (IntersectsRotateCircle(Vector3.UnitY, ref localRay, out intersection) && intersection < closestIntersection)
{
_activeAxis = Axis.Y;
- closestintersection = intersection;
+ closestIntersection = intersection;
}
- if (IntersectsRotateCircle(Vector3.UnitZ, ref localRay, out intersection) && intersection < closestintersection)
+ if (IntersectsRotateCircle(Vector3.UnitZ, ref localRay, out intersection) && intersection < closestIntersection)
{
_activeAxis = Axis.Z;
- closestintersection = intersection;
+ closestIntersection = intersection;
}
// Center
@@ -132,27 +132,27 @@ namespace FlaxEditor.Gizmo
case Mode.Scale:
{
// Spheres collision
- if (ScaleXSphere.Intersects(ref ray, out intersection) && intersection < closestintersection)
+ if (ScaleXSphere.Intersects(ref ray, out intersection) && intersection < closestIntersection)
{
_activeAxis = Axis.X;
- closestintersection = intersection;
+ closestIntersection = intersection;
}
- if (ScaleYSphere.Intersects(ref ray, out intersection) && intersection < closestintersection)
+ if (ScaleYSphere.Intersects(ref ray, out intersection) && intersection < closestIntersection)
{
_activeAxis = Axis.Y;
- closestintersection = intersection;
+ closestIntersection = intersection;
}
- if (ScaleZSphere.Intersects(ref ray, out intersection) && intersection < closestintersection)
+ if (ScaleZSphere.Intersects(ref ray, out intersection) && intersection < closestIntersection)
{
_activeAxis = Axis.Z;
- closestintersection = intersection;
+ closestIntersection = intersection;
}
// Center
- if (CenterBox.Intersects(ref ray, out intersection) && intersection < closestintersection)
+ if (CenterBox.Intersects(ref ray, out intersection) && intersection < closestIntersection)
{
_activeAxis = Axis.Center;
- closestintersection = intersection;
+ closestIntersection = intersection;
}
break;
diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs
index 8c997bc2c..e123bd3f6 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.cs
@@ -174,7 +174,7 @@ namespace FlaxEditor.Gizmo
_axisAlignedWorld = _screenScaleMatrix * Matrix.CreateWorld(Position, Vector3.Backward, Vector3.Up);
// Assign world
- if (_activeTransformSpace == TransformSpace.World)
+ if (_activeTransformSpace == TransformSpace.World && _activeMode != Mode.Scale)
{
_gizmoWorld = _axisAlignedWorld;
@@ -297,29 +297,6 @@ namespace FlaxEditor.Gizmo
else if (_activeMode == Mode.Scale)
{
// Scale
- if (_activeTransformSpace == TransformSpace.World && _activeAxis != Axis.Center)
- {
- var deltaLocal = delta;
- Quaternion orientation = GetSelectedObject(0).Orientation;
- delta = Vector3.Transform(delta, orientation);
-
- // Fix axis sign of delta movement for rotated object in some cases (eg. rotated object by 90 deg on Y axis and scale in world space with Red/X axis)
- switch (_activeAxis)
- {
- case Axis.X:
- if (deltaLocal.X < 0)
- delta *= -1;
- break;
- case Axis.Y:
- if (deltaLocal.Y < 0)
- delta *= -1;
- break;
- case Axis.Z:
- if (deltaLocal.Z < 0)
- delta *= -1;
- break;
- }
- }
_scaleDelta = delta;
}
}
diff --git a/Source/Editor/History/UndoActionObject.cs b/Source/Editor/History/UndoActionObject.cs
index cac710bff..a87637f4f 100644
--- a/Source/Editor/History/UndoActionObject.cs
+++ b/Source/Editor/History/UndoActionObject.cs
@@ -4,6 +4,8 @@ using System;
using System.Collections.Generic;
using FlaxEditor.Utilities;
using FlaxEngine;
+using Newtonsoft.Json;
+using JsonSerializer = FlaxEngine.Json.JsonSerializer;
namespace FlaxEditor.History
{
@@ -114,6 +116,8 @@ namespace FlaxEditor.History
public object TargetInstance;
}
+ internal static JsonSerializerSettings JsonSettings;
+
// For objects that cannot be referenced in undo action like: FlaxEngine.Object or SceneGraphNode we store them in DataStorage,
// otherwise here:
private readonly object TargetInstance;
@@ -177,6 +181,24 @@ namespace FlaxEditor.History
};
}
+ ///
+ public override DataStorage Data
+ {
+ protected set
+ {
+ // Inject objects typename serialization to prevent data type mismatch when loading from saved state
+ var settings = JsonSettings;
+ if (settings == null)
+ {
+ settings = JsonSerializer.CreateDefaultSettings(false);
+ settings.TypeNameHandling = TypeNameHandling.All;
+ JsonSettings = settings;
+ }
+ _data = JsonConvert.SerializeObject(value, Formatting.Indented, settings);
+ //Editor.Log(_data);
+ }
+ }
+
///
public override string ActionString { get; }
diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp
index 7b178b956..a312b38c4 100644
--- a/Source/Editor/Managed/ManagedEditor.Internal.cpp
+++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp
@@ -96,10 +96,9 @@ struct InternalTextureOptions
to->Sprites.EnsureCapacity(count);
for (int32 i = 0; i < count; i++)
{
- Sprite sprite;
+ Sprite& sprite = to->Sprites.AddOne();
sprite.Area = mono_array_get(from->SpriteAreas, Rectangle, i);
sprite.Name = MUtils::ToString(mono_array_get(from->SpriteNames, MonoString*, i));
- to->Sprites.Add(sprite);
}
}
}
@@ -125,7 +124,7 @@ struct InternalTextureOptions
{
const auto domain = mono_domain_get();
int32 count = from->Sprites.Count();
- auto rectClass = Scripting::FindClass("FlaxEngine.Rectangle");
+ auto rectClass = Rectangle::TypeInitializer.GetType().ManagedClass;
ASSERT(rectClass != nullptr);
to->SpriteAreas = mono_array_new(domain, rectClass->GetNative(), count);
to->SpriteNames = mono_array_new(domain, mono_get_string_class(), count);
@@ -521,13 +520,14 @@ public:
return AssetsImportingManager::Create(AssetsImportingManager::CreateVisualScriptTag, outputPath, &baseTypename);
}
- static bool CanImport(MonoString* extensionObj)
+ static MonoString* CanImport(MonoString* extensionObj)
{
String extension;
MUtils::ToString(extensionObj, extension);
if (extension.Length() > 0 && extension[0] == '.')
extension.Remove(0, 1);
- return AssetsImportingManager::GetImporter(extension) != nullptr;
+ const AssetImporter* importer = AssetsImportingManager::GetImporter(extension);
+ return importer ? MUtils::ToString(importer->ResultExtension) : nullptr;
}
static bool Import(MonoString* inputPathObj, MonoString* outputPathObj, void* arg)
@@ -715,7 +715,7 @@ public:
return str;
}
- static bool CookMeshCollision(MonoString* pathObj, CollisionDataType type, Model* modelObj, int32 modelLodIndex, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
+ static bool CookMeshCollision(MonoString* pathObj, CollisionDataType type, ModelBase* modelObj, int32 modelLodIndex, uint32 materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
{
#if COMPILE_WITH_PHYSICS_COOKING
CollisionCooking::Argument arg;
@@ -725,9 +725,9 @@ public:
arg.Type = type;
arg.Model = modelObj;
arg.ModelLodIndex = modelLodIndex;
+ arg.MaterialSlotsMask = materialSlotsMask;
arg.ConvexFlags = convexFlags;
arg.ConvexVertexLimit = convexVertexLimit;
-
return CreateCollisionData::CookMeshCollision(path, arg);
#else
LOG(Warning, "Collision cooking is disabled.");
@@ -743,8 +743,8 @@ public:
const auto& debugLines = collisionData->GetDebugLines();
const int32 linesCount = debugLines.Count() / 2;
- *triangles = mono_array_new(mono_domain_get(), StdTypesContainer::Instance()->Vector3Class->GetNative(), debugLines.Count());
- *indices = mono_array_new(mono_domain_get(), mono_get_int32_class(), linesCount * 3);
+ mono_gc_wbarrier_generic_store(triangles, (MonoObject*)mono_array_new(mono_domain_get(), StdTypesContainer::Instance()->Vector3Class->GetNative(), debugLines.Count()));
+ mono_gc_wbarrier_generic_store(indices, (MonoObject*)mono_array_new(mono_domain_get(), mono_get_int32_class(), linesCount * 3));
// Use one triangle per debug line
for (int32 i = 0; i < debugLines.Count(); i++)
diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs
index c90a386c1..ec71b8564 100644
--- a/Source/Editor/Modules/ContentDatabaseModule.cs
+++ b/Source/Editor/Modules/ContentDatabaseModule.cs
@@ -911,6 +911,8 @@ namespace FlaxEditor.Modules
Proxy.Add(new ParticleSystemProxy());
Proxy.Add(new SceneAnimationProxy());
Proxy.Add(new CSharpScriptProxy());
+ Proxy.Add(new CppAssetProxy());
+ Proxy.Add(new CppStaticClassProxy());
Proxy.Add(new CppScriptProxy());
Proxy.Add(new SceneProxy());
Proxy.Add(new PrefabProxy());
@@ -923,29 +925,34 @@ namespace FlaxEditor.Modules
Proxy.Add(new SkeletonMaskProxy());
Proxy.Add(new GameplayGlobalsProxy());
Proxy.Add(new VisualScriptProxy());
+ Proxy.Add(new LocalizedStringTableProxy());
Proxy.Add(new FileProxy());
Proxy.Add(new SpawnableJsonAssetProxy());
// Settings
- Proxy.Add(new SettingsProxy(typeof(GameSettings)));
- Proxy.Add(new SettingsProxy(typeof(TimeSettings)));
- Proxy.Add(new SettingsProxy(typeof(LayersAndTagsSettings)));
- Proxy.Add(new SettingsProxy(typeof(PhysicsSettings)));
- Proxy.Add(new SettingsProxy(typeof(GraphicsSettings)));
- Proxy.Add(new SettingsProxy(typeof(NavigationSettings)));
- Proxy.Add(new SettingsProxy(typeof(BuildSettings)));
- Proxy.Add(new SettingsProxy(typeof(InputSettings)));
- Proxy.Add(new SettingsProxy(typeof(WindowsPlatformSettings)));
- Proxy.Add(new SettingsProxy(typeof(UWPPlatformSettings)));
- Proxy.Add(new SettingsProxy(typeof(LinuxPlatformSettings)));
+ Proxy.Add(new SettingsProxy(typeof(GameSettings), Editor.Instance.Icons.GameSettings128));
+ Proxy.Add(new SettingsProxy(typeof(TimeSettings), Editor.Instance.Icons.TimeSettings128));
+ Proxy.Add(new SettingsProxy(typeof(LayersAndTagsSettings), Editor.Instance.Icons.LayersTagsSettings128));
+ Proxy.Add(new SettingsProxy(typeof(PhysicsSettings), Editor.Instance.Icons.PhysicsSettings128));
+ Proxy.Add(new SettingsProxy(typeof(GraphicsSettings), Editor.Instance.Icons.GraphicsSettings128));
+ Proxy.Add(new SettingsProxy(typeof(NavigationSettings), Editor.Instance.Icons.NavigationSettings128));
+ Proxy.Add(new SettingsProxy(typeof(LocalizationSettings), Editor.Instance.Icons.Document128));
+ Proxy.Add(new SettingsProxy(typeof(BuildSettings), Editor.Instance.Icons.BuildSettings128));
+ Proxy.Add(new SettingsProxy(typeof(InputSettings), Editor.Instance.Icons.InputSettings128));
+ Proxy.Add(new SettingsProxy(typeof(WindowsPlatformSettings), Editor.Instance.Icons.WindowsSettings128));
+ Proxy.Add(new SettingsProxy(typeof(UWPPlatformSettings), Editor.Instance.Icons.UWPSettings128));
+ Proxy.Add(new SettingsProxy(typeof(LinuxPlatformSettings), Editor.Instance.Icons.LinuxSettings128));
var typePS4PlatformSettings = TypeUtils.GetManagedType(GameSettings.PS4PlatformSettingsTypename);
if (typePS4PlatformSettings != null)
- Proxy.Add(new SettingsProxy(typePS4PlatformSettings));
+ Proxy.Add(new SettingsProxy(typePS4PlatformSettings, Editor.Instance.Icons.PlaystationSettings128));
var typeXboxScarlettPlatformSettings = TypeUtils.GetManagedType(GameSettings.XboxScarlettPlatformSettingsTypename);
if (typeXboxScarlettPlatformSettings != null)
- Proxy.Add(new SettingsProxy(typeXboxScarlettPlatformSettings));
- Proxy.Add(new SettingsProxy(typeof(AndroidPlatformSettings)));
- Proxy.Add(new SettingsProxy(typeof(AudioSettings)));
+ Proxy.Add(new SettingsProxy(typeXboxScarlettPlatformSettings, Editor.Instance.Icons.XBoxScarletIcon128));
+ Proxy.Add(new SettingsProxy(typeof(AndroidPlatformSettings), Editor.Instance.Icons.AndroidSettings128));
+ var typeSwitchPlatformSettings = TypeUtils.GetManagedType(GameSettings.SwitchPlatformSettingsTypename);
+ if (typeSwitchPlatformSettings != null)
+ Proxy.Add(new SettingsProxy(typeSwitchPlatformSettings, Editor.Instance.Icons.Document128));
+ Proxy.Add(new SettingsProxy(typeof(AudioSettings), Editor.Instance.Icons.AudioSettings128));
// Last add generic json (won't override other json proxies)
Proxy.Add(new GenericJsonAssetProxy());
diff --git a/Source/Editor/Modules/ContentEditingModule.cs b/Source/Editor/Modules/ContentEditingModule.cs
index 0d0e65ca9..6d0299a43 100644
--- a/Source/Editor/Modules/ContentEditingModule.cs
+++ b/Source/Editor/Modules/ContentEditingModule.cs
@@ -118,6 +118,25 @@ namespace FlaxEditor.Modules
return false;
}
+ // Check proxy name restrictions
+ if (item is NewItem ni)
+ {
+ if (!ni.Proxy.IsFileNameValid(shortName))
+ {
+ hint = "Name does not follow " + ni.Proxy.Name + " name restrictions !";
+ return false;
+ }
+ }
+ else
+ {
+ var proxy = Editor.ContentDatabase.GetProxy(item);
+ if (proxy != null && !proxy.IsFileNameValid(shortName))
+ {
+ hint = "Name does not follow " + proxy.Name + " name restrictions !";
+ return false;
+ }
+ }
+
// Cache data
string sourcePath = item.Path;
string sourceFolder = System.IO.Path.GetDirectoryName(sourcePath);
diff --git a/Source/Editor/Modules/ContentFindingModule.cs b/Source/Editor/Modules/ContentFindingModule.cs
index 9be15e0d1..45748f7f4 100644
--- a/Source/Editor/Modules/ContentFindingModule.cs
+++ b/Source/Editor/Modules/ContentFindingModule.cs
@@ -309,6 +309,7 @@ namespace FlaxEditor.Modules
{ "FlaxEditor.Content.Settings.InputSettings", "Settings" },
{ "FlaxEditor.Content.Settings.LayersAndTagsSettings", "Settings" },
{ "FlaxEditor.Content.Settings.NavigationSettings", "Settings" },
+ { "FlaxEditor.Content.Settings.LocalizationSettings", "Settings" },
{ "FlaxEditor.Content.Settings.PhysicsSettings", "Settings" },
{ "FlaxEditor.Content.Settings.TimeSettings", "Settings" },
{ "FlaxEditor.Content.Settings.UWPPlatformSettings", "Settings" },
diff --git a/Source/Editor/Modules/ContentImportingModule.cs b/Source/Editor/Modules/ContentImportingModule.cs
index d5ec4b69f..424868fd8 100644
--- a/Source/Editor/Modules/ContentImportingModule.cs
+++ b/Source/Editor/Modules/ContentImportingModule.cs
@@ -193,12 +193,10 @@ namespace FlaxEditor.Modules
var extension = System.IO.Path.GetExtension(inputPath) ?? string.Empty;
// Check if given file extension is a binary asset (.flax files) and can be imported by the engine
- bool isBinaryAsset = Editor.CanImport(extension);
- string outputExtension;
- if (isBinaryAsset)
+ bool isBuilt = Editor.CanImport(extension, out var outputExtension);
+ if (isBuilt)
{
- // Flax it up!
- outputExtension = ".flax";
+ outputExtension = '.' + outputExtension;
if (!targetLocation.CanHaveAssets)
{
@@ -234,7 +232,7 @@ namespace FlaxEditor.Modules
var shortName = System.IO.Path.GetFileNameWithoutExtension(inputPath);
var outputPath = System.IO.Path.Combine(targetLocation.Path, shortName + outputExtension);
- Import(inputPath, outputPath, isBinaryAsset, skipSettingsDialog, settings);
+ Import(inputPath, outputPath, isBuilt, skipSettingsDialog, settings);
}
///
@@ -243,10 +241,10 @@ namespace FlaxEditor.Modules
///
/// The input path.
/// The output path.
- /// True if output file is a binary asset.
+ /// True if use in-built importer (engine backend).
/// True if skip any popup dialogs showing for import options adjusting. Can be used when importing files from code.
/// Import settings to override. Use null to skip this value.
- private void Import(string inputPath, string outputPath, bool isBinaryAsset, bool skipSettingsDialog = false, object settings = null)
+ private void Import(string inputPath, string outputPath, bool isInBuilt, bool skipSettingsDialog = false, object settings = null)
{
lock (_requests)
{
@@ -254,7 +252,7 @@ namespace FlaxEditor.Modules
{
InputPath = inputPath,
OutputPath = outputPath,
- IsBinaryAsset = isBinaryAsset,
+ IsInBuilt = isInBuilt,
SkipSettingsDialog = skipSettingsDialog,
Settings = settings,
});
diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs
index 6334225fd..86479b696 100644
--- a/Source/Editor/Modules/SceneModule.cs
+++ b/Source/Editor/Modules/SceneModule.cs
@@ -22,14 +22,25 @@ namespace FlaxEditor.Modules
///
public class ScenesRootNode : RootNode
{
+ private readonly Editor _editor;
+
+ ///
+ public ScenesRootNode()
+ {
+ _editor = Editor.Instance;
+ }
+
///
public override void Spawn(Actor actor, Actor parent)
{
- Editor.Instance.SceneEditing.Spawn(actor, parent);
+ _editor.SceneEditing.Spawn(actor, parent);
}
///
public override Undo Undo => Editor.Instance.Undo;
+
+ ///
+ public override List Selection => _editor.SceneEditing.Selection;
}
///
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index c64ceaa11..c5145a7aa 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -190,7 +190,7 @@ namespace FlaxEditor.Modules
if (isDuringBreakpointHang)
{
play.Checked = false;
- play.Icon = Editor.Icons.Stop32;
+ play.Icon = Editor.Icons.Stop64;
pause.Enabled = false;
pause.Checked = true;
pause.AutoCheck = false;
@@ -199,7 +199,7 @@ namespace FlaxEditor.Modules
else if (isPlayMode)
{
play.Checked = false;
- play.Icon = Editor.Icons.Stop32;
+ play.Icon = Editor.Icons.Stop64;
pause.Enabled = true;
pause.Checked = Editor.StateMachine.PlayingState.IsPaused;
pause.AutoCheck = false;
@@ -208,7 +208,7 @@ namespace FlaxEditor.Modules
else
{
play.Checked = Editor.Simulation.IsPlayModeRequested;
- play.Icon = Editor.Icons.Play32;
+ play.Icon = Editor.Icons.Play64;
pause.Enabled = canEnterPlayMode;
pause.AutoCheck = true;
step.Enabled = false;
@@ -501,20 +501,20 @@ namespace FlaxEditor.Modules
Parent = mainWindow,
};
- _toolStripSaveAll = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Save32, Editor.SaveAll).LinkTooltip("Save all (Ctrl+S)");
+ _toolStripSaveAll = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Save64, Editor.SaveAll).LinkTooltip("Save all (Ctrl+S)");
ToolStrip.AddSeparator();
- _toolStripUndo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Undo32, Editor.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
- _toolStripRedo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Redo32, Editor.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
+ _toolStripUndo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Undo64, Editor.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
+ _toolStripRedo = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Redo64, Editor.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
ToolStrip.AddSeparator();
_toolStripTranslate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Translate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip("Change Gizmo tool mode to Translate (1)");
_toolStripRotate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Rotate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)");
_toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)");
ToolStrip.AddSeparator();
- _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build32, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)");
+ _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)");
ToolStrip.AddSeparator();
- _toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play32, Editor.Simulation.RequestPlayOrStopPlay).LinkTooltip("Start/Stop game (F5)");
- _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause32, Editor.Simulation.RequestResumeOrPause).LinkTooltip("Pause/Resume game(F6)");
- _toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Step32, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game");
+ _toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, Editor.Simulation.RequestPlayOrStopPlay).LinkTooltip("Start/Stop game (F5)");
+ _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip("Pause/Resume game(F6)");
+ _toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game");
UpdateToolstrip();
}
diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs
index f38f6fe3b..5267da01f 100644
--- a/Source/Editor/Modules/WindowsModule.cs
+++ b/Source/Editor/Modules/WindowsModule.cs
@@ -707,8 +707,9 @@ namespace FlaxEditor.Modules
var dpiScale = Platform.DpiScale;
var settings = CreateWindowSettings.Default;
settings.Title = "Flax Editor";
- settings.Size = new Vector2(1300 * dpiScale, 900 * dpiScale);
+ settings.Size = Platform.DesktopSize;
settings.StartPosition = WindowStartPosition.CenterScreen;
+ settings.ShowAfterFirstPaint = true;
#if PLATFORM_WINDOWS
if (!Editor.Instance.Options.Options.Interface.UseNativeWindowSystem)
diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs
index 85174030f..c260ae8b8 100644
--- a/Source/Editor/Options/GeneralOptions.cs
+++ b/Source/Editor/Options/GeneralOptions.cs
@@ -68,6 +68,24 @@ namespace FlaxEditor.Options
CompileScripts,
}
+ ///
+ /// Order of script members show in editor
+ ///
+ public enum MembersOrder
+ {
+ ///
+ /// Shows properties/fields in alphabetical order
+ ///
+ [Tooltip("Shows properties/fields in alphabetical order")]
+ Alphabetical,
+
+ ///
+ /// Shows properties/fields in declaration order
+ ///
+ [Tooltip("Shows properties/fields in declaration order")]
+ Declaration
+ }
+
///
/// Gets or sets the scene to load on editor startup.
///
@@ -116,6 +134,13 @@ namespace FlaxEditor.Options
[EditorDisplay("Scripting", "Force Script Compilation On Startup"), EditorOrder(501), Tooltip("Determines whether automatically compile game scripts before starting the editor.")]
public bool ForceScriptCompilationOnStartup { get; set; } = true;
+ ///
+ /// Gets or sets an order of script properties/fields in properties panel.
+ ///
+ [DefaultValue(MembersOrder.Alphabetical)]
+ [EditorDisplay("Scripting", "Script Members Order"), EditorOrder(503), Tooltip("Order of script properties/fields in properties panel")]
+ public MembersOrder ScriptMembersOrder { get; set; } = MembersOrder.Alphabetical;
+
///
/// Gets or sets a value indicating whether automatically save the Visual Script asset editors when starting the play mode in editor.
///
diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs
index 62496823c..ea42ee99b 100644
--- a/Source/Editor/Options/OptionsModule.cs
+++ b/Source/Editor/Options/OptionsModule.cs
@@ -150,7 +150,10 @@ namespace FlaxEditor.Options
private void Save()
{
// Update file
- Editor.SaveJsonAsset(_optionsFilePath, Options);
+ if (Editor.SaveJsonAsset(_optionsFilePath, Options))
+ {
+ MessageBox.Show(string.Format("Failed to save editor option to '{0}'. Ensure that directory exists and program has access to it.", _optionsFilePath), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
// Special case for editor analytics
var editorAnalyticsTrackingFile = Path.Combine(Editor.LocalCachePath, "noTracking");
@@ -245,10 +248,11 @@ namespace FlaxEditor.Options
Cross = Editor.Icons.Cross12,
CheckBoxIntermediate = Editor.Icons.CheckBoxIntermediate12,
CheckBoxTick = Editor.Icons.CheckBoxTick12,
- StatusBarSizeGrip = Editor.Icons.StatusBarSizeGrip12,
- Translate = Editor.Icons.Translate16,
- Rotate = Editor.Icons.Rotate16,
- Scale = Editor.Icons.Scale16,
+ StatusBarSizeGrip = Editor.Icons.WindowDrag12,
+ Translate = Editor.Icons.Translate32,
+ Rotate = Editor.Icons.Rotate32,
+ Scale = Editor.Icons.Scale32,
+ Scalar = Editor.Icons.Scalar32,
SharedTooltip = new Tooltip()
};
diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs
index 5558ac89d..0a8d78c26 100644
--- a/Source/Editor/Options/ViewportOptions.cs
+++ b/Source/Editor/Options/ViewportOptions.cs
@@ -18,6 +18,13 @@ namespace FlaxEditor.Options
[EditorDisplay("General"), EditorOrder(100), Tooltip("The mouse movement sensitivity scale applied when using the viewport camera.")]
public float MouseSensitivity { get; set; } = 1.0f;
+ ///
+ /// Gets or sets the mouse wheel sensitivity applied to zoom in orthographic mode.
+ ///
+ [DefaultValue(1.0f), Limit(0.01f, 100.0f)]
+ [EditorDisplay("General"), EditorOrder(101), Tooltip("The mouse wheel sensitivity applied to zoom in orthographic mode.")]
+ public float MouseWheelSensitivity { get; set; } = 1.0f;
+
///
/// Gets or sets the default movement speed for the viewport camera (must match the dropdown menu values in the viewport).
///
diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs
index 318d8379b..6703c30a0 100644
--- a/Source/Editor/SceneGraph/ActorNode.cs
+++ b/Source/Editor/SceneGraph/ActorNode.cs
@@ -194,7 +194,7 @@ namespace FlaxEditor.SceneGraph
{
get
{
- var scene = _actor.Scene;
+ var scene = _actor ? _actor.Scene : null;
return scene != null ? SceneGraphFactory.FindNode(scene.ID) as SceneNode : null;
}
}
@@ -235,7 +235,6 @@ namespace FlaxEditor.SceneGraph
{
if (!(value is ActorNode))
throw new InvalidOperationException("ActorNode can have only ActorNode as a parent node.");
-
base.ParentNode = value;
}
}
@@ -263,6 +262,12 @@ namespace FlaxEditor.SceneGraph
return _actor.IntersectsItself(ray.Ray, out distance, out normal);
}
+ ///
+ public override void GetEditorSphere(out BoundingSphere sphere)
+ {
+ Editor.GetActorEditorSphere(_actor, out sphere);
+ }
+
///
public override void OnDebugDraw(ViewportDebugDrawData data)
{
@@ -283,6 +288,13 @@ namespace FlaxEditor.SceneGraph
{
}
+ ///
+ /// Action called after pasting actor in editor.
+ ///
+ public virtual void PostPaste()
+ {
+ }
+
///
protected override void OnParentChanged()
{
@@ -293,6 +305,7 @@ namespace FlaxEditor.SceneGraph
// (eg. we build new node for spawned actor and link it to the game)
if (_treeNode.Parent != null && !_treeNode.Parent.IsLayoutLocked)
{
+ _treeNode.IndexInParent = _actor.OrderInParent;
_treeNode.Parent.SortChildren();
// Update UI
@@ -315,5 +328,11 @@ namespace FlaxEditor.SceneGraph
base.Dispose();
}
+
+ ///
+ public override string ToString()
+ {
+ return _actor ? _actor.ToString() : base.ToString();
+ }
}
}
diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs
index 3d41d0904..7f9e4ca70 100644
--- a/Source/Editor/SceneGraph/Actors/SplineNode.cs
+++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs
@@ -29,7 +29,7 @@ namespace FlaxEditor.SceneGraph.Actors
public override bool CanBeSelectedDirectly => true;
- public override bool CanDuplicate => true;
+ public override bool CanDuplicate => !Root?.Selection.Contains(ParentNode) ?? true;
public override bool CanDelete => true;
@@ -282,7 +282,13 @@ namespace FlaxEditor.SceneGraph.Actors
{
// Remove unused points
while (srcCount > dstCount)
- ActorChildNodes[srcCount-- - 1].Dispose();
+ {
+ var node = ActorChildNodes[srcCount-- - 1];
+ // TODO: support selection interface inside SceneGraph nodes (eg. on Root) so prefab editor can handle this too
+ if (Editor.Instance.SceneEditing.Selection.Contains(node))
+ Editor.Instance.SceneEditing.Deselect();
+ node.Dispose();
+ }
// Add new points
var id = ID;
@@ -361,6 +367,15 @@ namespace FlaxEditor.SceneGraph.Actors
}
}
+ ///
+ public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal)
+ {
+ // Select only spline points
+ normal = Vector3.Up;
+ distance = float.MaxValue;
+ return false;
+ }
+
///
public override void OnDispose()
{
diff --git a/Source/Editor/SceneGraph/Actors/UIControlNode.cs b/Source/Editor/SceneGraph/Actors/UIControlNode.cs
index b3f8f5d09..c4f7d1c17 100644
--- a/Source/Editor/SceneGraph/Actors/UIControlNode.cs
+++ b/Source/Editor/SceneGraph/Actors/UIControlNode.cs
@@ -25,5 +25,20 @@ namespace FlaxEditor.SceneGraph.Actors
if (Actor is UIControl uiControl)
DebugDraw.DrawWireBox(uiControl.Bounds, Color.BlueViolet);
}
+
+ ///
+ public override void PostPaste()
+ {
+ base.PostPaste();
+
+ var control = ((UIControl)Actor).Control;
+ if (control != null)
+ {
+ if (control.Parent != null)
+ control.Parent.PerformLayout();
+ else
+ control.PerformLayout();
+ }
+ }
}
}
diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
index 7b4b35ed1..5e2792dc4 100644
--- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
+++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
@@ -86,6 +86,10 @@ namespace FlaxEditor.SceneGraph.GUI
}
parent.SortChildren();
}
+ else if (Actor)
+ {
+ _orderInParent = Actor.OrderInParent;
+ }
}
internal void OnNameChanged()
@@ -537,7 +541,7 @@ namespace FlaxEditor.SceneGraph.GUI
var customAction = targetActor.HasPrefabLink ? new ReparentAction(targetActor) : null;
using (new UndoBlock(ActorNode.Root.Undo, targetActor, "Change actor parent", customAction))
{
- targetActor.SetParent(newParent, worldPositionLock);
+ targetActor.SetParent(newParent, worldPositionLock, true);
targetActor.OrderInParent = newOrder;
}
}
@@ -550,7 +554,7 @@ namespace FlaxEditor.SceneGraph.GUI
for (int i = 0; i < targetActors.Count; i++)
{
var targetActor = targetActors[i];
- targetActor.SetParent(newParent, worldPositionLock);
+ targetActor.SetParent(newParent, worldPositionLock, true);
targetActor.OrderInParent = newOrder;
}
}
diff --git a/Source/Editor/SceneGraph/RootNode.cs b/Source/Editor/SceneGraph/RootNode.cs
index 10af88489..485c656b1 100644
--- a/Source/Editor/SceneGraph/RootNode.cs
+++ b/Source/Editor/SceneGraph/RootNode.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
+using System.Collections.Generic;
using FlaxEditor.SceneGraph.Actors;
using FlaxEngine;
@@ -146,5 +147,10 @@ namespace FlaxEditor.SceneGraph
/// Gets the undo.
///
public abstract Undo Undo { get; }
+
+ ///
+ /// Gets the list of selected scene graph nodes in the editor context.
+ ///
+ public abstract List Selection { get; }
}
}
diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs
index 01a832c5a..bc8fe84aa 100644
--- a/Source/Editor/SceneGraph/SceneGraphNode.cs
+++ b/Source/Editor/SceneGraph/SceneGraphNode.cs
@@ -57,7 +57,9 @@ namespace FlaxEditor.SceneGraph
///
public virtual RootNode Root => ParentNode?.Root;
- ///
+ ///
+ /// Gets or sets the transform of the node.
+ ///
public abstract Transform Transform { get; set; }
///
@@ -110,18 +112,9 @@ namespace FlaxEditor.SceneGraph
{
if (parentNode != value)
{
- if (parentNode != null)
- {
- parentNode.ChildNodes.Remove(this);
- }
-
+ parentNode?.ChildNodes.Remove(this);
parentNode = value;
-
- if (parentNode != null)
- {
- parentNode.ChildNodes.Add(this);
- }
-
+ parentNode?.ChildNodes.Add(this);
OnParentChanged();
}
}
@@ -309,6 +302,20 @@ namespace FlaxEditor.SceneGraph
return false;
}
+ ///
+ /// Gets the object bounding sphere (including child actors).
+ ///
+ /// The bounding sphere.
+ public virtual void GetEditorSphere(out BoundingSphere sphere)
+ {
+ sphere = new BoundingSphere(Transform.Translation, 15.0f);
+ for (int i = 0; i < ChildNodes.Count; i++)
+ {
+ ChildNodes[i].GetEditorSphere(out var childSphere);
+ BoundingSphere.Merge(ref sphere, ref childSphere, out sphere);
+ }
+ }
+
///
/// Called when selected nodes should draw debug shapes using interface.
///
@@ -358,6 +365,7 @@ namespace FlaxEditor.SceneGraph
///
/// Gets or sets the node state.
///
+ [NoSerialize]
public virtual StateData State
{
get => throw new NotImplementedException();
diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
index ac226fd2c..64f157db0 100644
--- a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
+++ b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
@@ -8,6 +8,9 @@
#include "Editor/Scripting/ScriptsBuilder.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
+#if PLATFORM_LINUX
+#include
+#endif
VisualStudioCodeEditor::VisualStudioCodeEditor(const String& execPath, const bool isInsiders)
: _execPath(execPath)
diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs
index 53c84a1b5..870d68eba 100644
--- a/Source/Editor/Scripting/ScriptType.cs
+++ b/Source/Editor/Scripting/ScriptType.cs
@@ -40,6 +40,27 @@ namespace FlaxEditor.Scripting
///
public string Name => _managed?.Name ?? _custom?.Name;
+ ///
+ /// Gets a metadata token for sorting so it may not be the actual token.
+ ///
+ public int MetadataToken
+ {
+ get
+ {
+ int standardToken = _managed?.MetadataToken ?? _custom?.MetadataToken ?? 0;
+ if (_managed is PropertyInfo && _managed.DeclaringType != null)
+ {
+ var field = _managed.DeclaringType.GetField(string.Format("<{0}>k__BackingField", Name), BindingFlags.Instance | BindingFlags.NonPublic);
+ if (field == null || field.MetadataToken == 0)
+ {
+ return standardToken;
+ }
+ return field.MetadataToken;
+ }
+ return standardToken;
+ }
+ }
+
///
/// Gets a value indicating whether the type is declared public.
///
@@ -1444,6 +1465,11 @@ namespace FlaxEditor.Scripting
///
string Name { get; }
+ ///
+ /// Gets a metadata token for sorting so it may not be the actual token.
+ ///
+ int MetadataToken { get; }
+
///
/// Gets a value indicating whether the type is declared public.
///
diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp
index 1c489b2a9..71323dc34 100644
--- a/Source/Editor/Scripting/ScriptsBuilder.cpp
+++ b/Source/Editor/Scripting/ScriptsBuilder.cpp
@@ -9,6 +9,7 @@
#include "Engine/Core/Types/StringBuilder.h"
#include "Engine/Debug/Exceptions/FileNotFoundException.h"
#include "Engine/Engine/Engine.h"
+#include "Engine/Engine/Globals.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/FileSystemWatcher.h"
#include "Engine/Threading/ThreadPool.h"
@@ -341,7 +342,21 @@ void ScriptsBuilder::GetBinariesConfiguration(const Char*& target, const Char*&
target = platform = architecture = configuration = nullptr;
return;
}
- target = Editor::Project->EditorTarget.GetText();
+
+ // Pick game target
+ if (Editor::Project->EditorTarget.HasChars())
+ {
+ target = Editor::Project->EditorTarget.Get();
+ }
+ else if (Editor::Project->GameTarget.HasChars())
+ {
+ target = Editor::Project->GameTarget.Get();
+ }
+ else
+ {
+ target = TEXT("");
+ LOG(Error, "Missing editor/game targets in project. Please specify EditorTarget and GameTarget properties in .flaxproj file.");
+ }
#if PLATFORM_WINDOWS
platform = TEXT("Windows");
@@ -551,19 +566,13 @@ bool ScriptsBuilderService::Init()
}
// Verify project
- if (project->EditorTarget.IsEmpty() || project->GameTarget.IsEmpty())
+ if (project->EditorTarget.IsEmpty())
{
- const String& name = project->Name;
- String codeName;
- for (int32 i = 0; i < name.Length(); i++)
- {
- Char c = name[i];
- if (StringUtils::IsAlnum(c) && c != ' ' && c != '.')
- codeName += c;
- }
- project->GameTarget = codeName + TEXT("Target");
- project->EditorTarget = codeName + TEXT("EditorTarget");
- LOG(Warning, "Missing EditorTarget property in opened project, using deducted target name {0}", Editor::Project->EditorTarget);
+ LOG(Warning, "Missing {0} property in opened project", TEXT("EditorTarget"));
+ }
+ if (project->GameTarget.IsEmpty())
+ {
+ LOG(Warning, "Missing {0} property in opened project", TEXT("GameTarget"));
}
// Remove any remaining files from previous Editor run hot-reloads
diff --git a/Source/Editor/Surface/Archetypes/Comparisons.cs b/Source/Editor/Surface/Archetypes/Comparisons.cs
index d77fd5ff7..44c49925d 100644
--- a/Source/Editor/Surface/Archetypes/Comparisons.cs
+++ b/Source/Editor/Surface/Archetypes/Comparisons.cs
@@ -137,7 +137,7 @@ namespace FlaxEditor.Surface.Archetypes
{
int* dataValues = (int*)dataPtr;
for (int i = 0; i < entries.Count; i++)
- dataValues[i] = entries[i].Value;
+ dataValues[i] = (int)entries[i].Value;
}
}
else
diff --git a/Source/Editor/Surface/Archetypes/Flow.cs b/Source/Editor/Surface/Archetypes/Flow.cs
index b8c426a5a..ffcfb719a 100644
--- a/Source/Editor/Surface/Archetypes/Flow.cs
+++ b/Source/Editor/Surface/Archetypes/Flow.cs
@@ -177,7 +177,7 @@ namespace FlaxEditor.Surface.Archetypes
{
int* dataValues = (int*)dataPtr;
for (int i = 0; i < entries.Count; i++)
- dataValues[i] = entries[i].Value;
+ dataValues[i] = (int)entries[i].Value;
}
}
else
diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs
index a47b41a82..4e017248a 100644
--- a/Source/Editor/Surface/Archetypes/Function.cs
+++ b/Source/Editor/Surface/Archetypes/Function.cs
@@ -576,7 +576,7 @@ namespace FlaxEditor.Surface.Archetypes
for (int i = 0; i < _parameters.Length; i++)
{
writer.Write(_parameters[i].Name); // Parameter name
- Utilities.Utils.WriteVariantType(writer, TypeUtils.GetType(_parameters[i].Type)); // Box type
+ Utilities.VariantUtils.WriteVariantType(writer, TypeUtils.GetType(_parameters[i].Type)); // Box type
}
SetValue(2, stream.ToArray());
}
@@ -594,7 +594,7 @@ namespace FlaxEditor.Surface.Archetypes
for (int i = 0; i < parametersCount; i++)
{
var parameterName = reader.ReadString(); // Parameter name
- var boxType = Utilities.Utils.ReadVariantType(reader); // Box type
+ var boxType = Utilities.VariantUtils.ReadVariantType(reader); // Box type
MakeBox(i + 1, parameterName, boxType);
}
}
@@ -778,14 +778,14 @@ namespace FlaxEditor.Surface.Archetypes
{
reader.ReadByte(); // Version
signature.IsStatic = reader.ReadBoolean(); // Is Static
- signature.ReturnType = Utilities.Utils.ReadVariantScriptType(reader); // Return type
+ signature.ReturnType = Utilities.VariantUtils.ReadVariantScriptType(reader); // Return type
var parametersCount = reader.ReadInt32(); // Parameters count
signature.Params = parametersCount != 0 ? new SignatureParamInfo[parametersCount] : Utils.GetEmptyArray();
for (int i = 0; i < parametersCount; i++)
{
ref var param = ref signature.Params[i];
param.Name = Utilities.Utils.ReadStr(reader, 11); // Parameter name
- param.Type = Utilities.Utils.ReadVariantScriptType(reader); // Parameter type
+ param.Type = Utilities.VariantUtils.ReadVariantScriptType(reader); // Parameter type
param.IsOut = reader.ReadByte() != 0; // Is parameter out
}
}
@@ -799,14 +799,14 @@ namespace FlaxEditor.Surface.Archetypes
{
reader.ReadByte(); // Version
signature.IsStatic = reader.ReadBoolean(); // Is Static
- signature.ReturnType = Utilities.Utils.ReadVariantScriptType(reader); // Return type
+ signature.ReturnType = Utilities.VariantUtils.ReadVariantScriptType(reader); // Return type
var parametersCount = reader.ReadInt32(); // Parameters count
signature.Params = parametersCount != 0 ? new SignatureParamInfo[parametersCount] : Utils.GetEmptyArray();
for (int i = 0; i < parametersCount; i++)
{
ref var param = ref signature.Params[i];
param.Name = reader.ReadString(); // Parameter name
- param.Type = Utilities.Utils.ReadVariantScriptType(reader); // Parameter type
+ param.Type = Utilities.VariantUtils.ReadVariantScriptType(reader); // Parameter type
param.IsOut = reader.ReadByte() != 0; // Is parameter out
}
}
@@ -823,13 +823,13 @@ namespace FlaxEditor.Surface.Archetypes
{
writer.Write((byte)4); // Version
writer.Write(methodInfo.IsStatic); // Is Static
- Utilities.Utils.WriteVariantType(writer, methodInfo.ValueType); // Return type
+ Utilities.VariantUtils.WriteVariantType(writer, methodInfo.ValueType); // Return type
writer.Write(parameters.Length); // Parameters count
for (int i = 0; i < parameters.Length; i++)
{
ref var param = ref parameters[i];
Utilities.Utils.WriteStr(writer, param.Name, 11); // Parameter name
- Utilities.Utils.WriteVariantType(writer, param.Type); // Parameter type
+ Utilities.VariantUtils.WriteVariantType(writer, param.Type); // Parameter type
writer.Write((byte)(param.IsOut ? 1 : 0)); // Is parameter out
}
return stream.ToArray();
@@ -1434,14 +1434,14 @@ namespace FlaxEditor.Surface.Archetypes
if (_signature.IsVirtual)
flags |= Flags.Virtual;
writer.Write((byte)flags); // Flags
- Utilities.Utils.WriteVariantType(writer, _signature.ReturnType); // Return Type
+ Utilities.VariantUtils.WriteVariantType(writer, _signature.ReturnType); // Return Type
var parametersCount = _signature.Parameters?.Length ?? 0;
writer.Write(parametersCount); // Parameters count
for (int i = 0; i < parametersCount; i++)
{
ref var param = ref _signature.Parameters[i];
Utilities.Utils.WriteStrAnsi(writer, param.Name, 13); // Parameter name
- Utilities.Utils.WriteVariantType(writer, param.Type); // Parameter type
+ Utilities.VariantUtils.WriteVariantType(writer, param.Type); // Parameter type
writer.Write((byte)0); // Is parameter out
writer.Write((byte)0); // Has default value
}
@@ -1470,13 +1470,13 @@ namespace FlaxEditor.Surface.Archetypes
var flags = (Flags)reader.ReadByte(); // Flags
_signature.IsStatic = (flags & Flags.Static) == Flags.Static;
_signature.IsVirtual = (flags & Flags.Virtual) == Flags.Virtual;
- _signature.ReturnType = Utilities.Utils.ReadVariantScriptType(reader); // Return Type
+ _signature.ReturnType = Utilities.VariantUtils.ReadVariantScriptType(reader); // Return Type
var parametersCount = reader.ReadInt32(); // Parameters count
_signature.Parameters = new Parameter[parametersCount];
for (int i = 0; i < parametersCount; i++)
{
var paramName = Utilities.Utils.ReadStrAnsi(reader, 13); // Parameter name
- var paramType = Utilities.Utils.ReadVariantScriptType(reader); // Parameter type
+ var paramType = Utilities.VariantUtils.ReadVariantScriptType(reader); // Parameter type
var isOut = reader.ReadByte() != 0; // Is parameter out
var hasDefaultValue = reader.ReadByte() != 0; // Has default value
_signature.Parameters[i] = new Parameter
@@ -1993,7 +1993,7 @@ namespace FlaxEditor.Surface.Archetypes
}
else if (_isBind)
{
- _helperButton.Brush = new SpriteBrush(Editor.Instance.Icons.Add48);
+ _helperButton.Brush = new SpriteBrush(Editor.Instance.Icons.Add64);
_helperButton.Color = Color.Red;
_helperButton.TooltipText = "Add new handler function and bind it to this event";
_helperButton.Enabled = _signature != null;
diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs
index 95f503338..0e9fa4ff0 100644
--- a/Source/Editor/Surface/Archetypes/Packing.cs
+++ b/Source/Editor/Surface/Archetypes/Packing.cs
@@ -171,7 +171,7 @@ namespace FlaxEditor.Surface.Archetypes
for (int i = 0; i < fieldsLength; i++)
{
Utilities.Utils.WriteStr(writer, fields[i].Name, 11); // Field type
- Utilities.Utils.WriteVariantType(writer, fields[i].ValueType); // Field type
+ Utilities.VariantUtils.WriteVariantType(writer, fields[i].ValueType); // Field type
}
Values[1] = stream.ToArray();
}
@@ -188,7 +188,7 @@ namespace FlaxEditor.Surface.Archetypes
for (int i = 0; i < fieldsLength; i++)
{
var fieldName = Utilities.Utils.ReadStr(reader, 11); // Field name
- var fieldType = Utilities.Utils.ReadVariantType(reader); // Field type
+ var fieldType = Utilities.VariantUtils.ReadVariantType(reader); // Field type
MakeBox(i + 1, fieldName, new ScriptType(fieldType));
}
}
diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs
index 52ffbb5cb..005af20cc 100644
--- a/Source/Editor/Surface/Archetypes/Tools.cs
+++ b/Source/Editor/Surface/Archetypes/Tools.cs
@@ -104,7 +104,7 @@ namespace FlaxEditor.Surface.Archetypes
color *= 1.3f;
color.A = 1.0f;
var icons = Editor.Instance.Icons;
- var icon = isSelected ? icons.VisjectArrowClose : icons.VisjectArrowOpen;
+ var icon = isSelected ? icons.VisjectArrowClosed32 : icons.VisjectArrowOpen32;
Render2D.PushTransform(ref arrowTransform);
Render2D.DrawSprite(icon, arrowRect, color);
@@ -672,6 +672,24 @@ namespace FlaxEditor.Surface.Archetypes
}
}
+ private class ThisNode : SurfaceNode
+ {
+ ///
+ public ThisNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
+ : base(id, context, nodeArch, groupArch)
+ {}
+
+ ///
+ public override void OnLoaded()
+ {
+ base.OnLoaded();
+ var vss = (VisualScriptSurface)this.Context.Surface;
+ var type = TypeUtils.GetType(vss.Script.ScriptTypeName);
+ var box = (OutputBox)GetBox(0);
+ box.CurrentType = type ? type : new ScriptType(typeof(VisualScript));
+ }
+ }
+
private class AssetReferenceNode : SurfaceNode
{
///
@@ -1366,6 +1384,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 19,
Title = "This Instance",
+ Create = (id, context, arch, groupArch) => new ThisNode(id, context, arch, groupArch),
Description = "Gets the reference to this script object instance (self).",
Flags = NodeFlags.VisualScriptGraph,
Size = new Vector2(140, 20),
diff --git a/Source/Editor/Surface/ContextMenu/ContentFinder.cs b/Source/Editor/Surface/ContextMenu/ContentFinder.cs
index 76e8d2e33..fc0f43472 100644
--- a/Source/Editor/Surface/ContextMenu/ContentFinder.cs
+++ b/Source/Editor/Surface/ContextMenu/ContentFinder.cs
@@ -108,11 +108,13 @@ namespace FlaxEditor.Surface.ContextMenu
{
_resultPanel.DisposeChildren();
+ var dpiScale = DpiScale;
+
if (items.Count == 0)
{
Height = _searchBox.Height + 1;
_resultPanel.ScrollBars = ScrollBars.None;
- RootWindow.Window.ClientSize = new Vector2(RootWindow.Window.ClientSize.X, Height * Platform.DpiScale);
+ RootWindow.Window.ClientSize = new Vector2(RootWindow.Window.ClientSize.X, Height * dpiScale);
return;
}
@@ -146,7 +148,7 @@ namespace FlaxEditor.Surface.ContextMenu
MatchedItems.Add(searchItem);
}
- RootWindow.Window.ClientSize = new Vector2(RootWindow.Window.ClientSize.X, Height * Platform.DpiScale);
+ RootWindow.Window.ClientSize = new Vector2(RootWindow.Window.ClientSize.X, Height * dpiScale);
PerformLayout();
}
diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs
index 2a460013c..6a8745fa6 100644
--- a/Source/Editor/Surface/Elements/Box.cs
+++ b/Source/Editor/Surface/Elements/Box.cs
@@ -578,6 +578,7 @@ namespace FlaxEditor.Surface.Elements
BreakConnection(connectedBox);
action.End();
Surface.Undo.AddAction(action);
+ Surface.MarkAsEdited();
}
else
{
diff --git a/Source/Editor/Surface/Elements/EnumValue.cs b/Source/Editor/Surface/Elements/EnumValue.cs
index 9513abd78..dbd1620ab 100644
--- a/Source/Editor/Surface/Elements/EnumValue.cs
+++ b/Source/Editor/Surface/Elements/EnumValue.cs
@@ -38,7 +38,7 @@ namespace FlaxEditor.Surface.Elements
///
protected override void OnValueChanged()
{
- if ((int)ParentNode.Values[Archetype.ValueIndex] != Value)
+ if ((int)ParentNode.Values[Archetype.ValueIndex] != (int)Value)
{
// Edit value
ParentNode.SetValue(Archetype.ValueIndex, Value);
diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs
index ea6e9e34a..c830cf499 100644
--- a/Source/Editor/Surface/Elements/InputBox.cs
+++ b/Source/Editor/Surface/Elements/InputBox.cs
@@ -9,6 +9,8 @@ using FlaxEditor.GUI.Input;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using Newtonsoft.Json;
+using JsonSerializer = FlaxEngine.Json.JsonSerializer;
namespace FlaxEditor.Surface.Elements
{
@@ -981,6 +983,121 @@ namespace FlaxEditor.Surface.Elements
}
}
+ ///
+ public override bool OnMouseUp(Vector2 location, MouseButton button)
+ {
+ if (button == MouseButton.Right && Archetype.ValueIndex != -1)
+ {
+ var menu = new FlaxEditor.GUI.ContextMenu.ContextMenu();
+ menu.AddButton("Copy value", OnCopyValue);
+ var paste = menu.AddButton("Paste value", OnPasteValue);
+ try
+ {
+ GetClipboardValue(out _, false);
+ }
+ catch
+ {
+ paste.Enabled = false;
+ }
+
+ menu.Show(this, location);
+ return true;
+ }
+
+ return base.OnMouseUp(location, button);
+ }
+
+ private bool GetClipboardValue(out object result, bool deserialize)
+ {
+ result = null;
+ var text = Clipboard.Text;
+ if (string.IsNullOrEmpty(text))
+ return false;
+
+ object obj;
+ var type = CurrentType;
+ if (new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(type))
+ {
+ // Object reference
+ if (text.Length != 32)
+ return false;
+ JsonSerializer.ParseID(text, out var id);
+ obj = FlaxEngine.Object.Find(ref id);
+ }
+ else
+ {
+ // Default
+ obj = JsonConvert.DeserializeObject(text, TypeUtils.GetType(type), JsonSerializer.Settings);
+ }
+
+ if (obj == null || type.IsInstanceOfType(obj))
+ {
+ result = obj;
+ return true;
+ }
+
+ return false;
+ }
+
+ private void OnCopyValue()
+ {
+ var value = ParentNode.Values[Archetype.ValueIndex];
+
+ try
+ {
+ string text;
+ if (value == null)
+ {
+ // Missing value
+ var type = CurrentType;
+ if (type.Type == typeof(bool))
+ text = "false";
+ else if (type.Type == typeof(byte) || type.Type == typeof(sbyte) || type.Type == typeof(char) || type.Type == typeof(short) || type.Type == typeof(ushort) || type.Type == typeof(int) || type.Type == typeof(uint) || type.Type == typeof(long) || type.Type == typeof(ulong))
+ text = "0";
+ else if (type.Type == typeof(float) || type.Type == typeof(double))
+ text = "0.0";
+ else if (type.Type == typeof(Vector2) || type.Type == typeof(Vector3) || type.Type == typeof(Vector4) || type.Type == typeof(Color))
+ text = JsonSerializer.Serialize(TypeUtils.GetDefaultValue(type));
+ else if (type.Type == typeof(string))
+ text = "";
+ else
+ text = "null";
+ }
+ else if (value is FlaxEngine.Object asObject)
+ {
+ // Object reference
+ text = JsonSerializer.GetStringID(asObject);
+ }
+ else
+ {
+ // Default
+ text = JsonSerializer.Serialize(value);
+ }
+ Clipboard.Text = text;
+ }
+ catch (Exception ex)
+ {
+ Editor.LogWarning(ex);
+ Editor.LogError("Cannot copy property. See log for more info.");
+ }
+ }
+
+ private void OnPasteValue()
+ {
+ try
+ {
+ if (GetClipboardValue(out var value, true))
+ {
+ ParentNode.SetValue(Archetype.ValueIndex, value);
+ }
+ }
+ catch (Exception ex)
+ {
+ Editor.LogWarning(ex);
+ Editor.LogError("Cannot paste property value. See log for more info.");
+ }
+ }
+
///
/// Creates the default value editor control.
///
diff --git a/Source/Editor/Surface/SurfaceStyle.cs b/Source/Editor/Surface/SurfaceStyle.cs
index 18de19118..db25b9f09 100644
--- a/Source/Editor/Surface/SurfaceStyle.cs
+++ b/Source/Editor/Surface/SurfaceStyle.cs
@@ -224,10 +224,10 @@ namespace FlaxEditor.Surface
},
Icons =
{
- BoxOpen = editor.Icons.VisjectBoxOpen,
- BoxClose = editor.Icons.VisjectBoxClose,
- ArrowOpen = editor.Icons.VisjectArrowOpen,
- ArrowClose = editor.Icons.VisjectArrowClose,
+ BoxOpen = editor.Icons.VisjectBoxOpen32,
+ BoxClose = editor.Icons.VisjectBoxClosed32,
+ ArrowOpen = editor.Icons.VisjectArrowOpen32,
+ ArrowClose = editor.Icons.VisjectArrowClosed32,
},
Background = editor.UI.VisjectSurfaceBackground,
};
diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs
index 996f9d2e0..9b96aa165 100644
--- a/Source/Editor/Surface/VisjectSurface.Input.cs
+++ b/Source/Editor/Surface/VisjectSurface.Input.cs
@@ -298,7 +298,6 @@ namespace FlaxEditor.Surface
rerouteNode.GetBoxes().First(b => b.IsOutput).CreateConnection(inputBox);
addConnectionsAction.End();
-
Undo.AddAction(new MultiUndoAction(spawnNodeAction, disconnectBoxesAction, addConnectionsAction));
}
else
@@ -565,11 +564,10 @@ namespace FlaxEditor.Surface
if (key == KeyboardKeys.ArrowUp || key == KeyboardKeys.ArrowDown)
{
Box selectedBox = GetSelectedBox(SelectedNodes);
- if (selectedBox == null) return true;
+ if (selectedBox == null)
+ return true;
- Box toSelect = (key == KeyboardKeys.ArrowUp) ?
- selectedBox?.ParentNode.GetPreviousBox(selectedBox) :
- selectedBox?.ParentNode.GetNextBox(selectedBox);
+ Box toSelect = (key == KeyboardKeys.ArrowUp) ? selectedBox?.ParentNode.GetPreviousBox(selectedBox) : selectedBox?.ParentNode.GetNextBox(selectedBox);
if (toSelect != null && toSelect.IsOutput == selectedBox.IsOutput)
{
@@ -581,10 +579,12 @@ namespace FlaxEditor.Surface
if (key == KeyboardKeys.Tab)
{
Box selectedBox = GetSelectedBox(SelectedNodes);
- if (selectedBox == null) return true;
+ if (selectedBox == null)
+ return true;
int connectionCount = selectedBox.Connections.Count;
- if (connectionCount == 0) return true;
+ if (connectionCount == 0)
+ return true;
if (Root.GetKey(KeyboardKeys.Shift))
{
@@ -596,11 +596,11 @@ namespace FlaxEditor.Surface
}
}
-
if (key == KeyboardKeys.ArrowRight || key == KeyboardKeys.ArrowLeft)
{
Box selectedBox = GetSelectedBox(SelectedNodes);
- if (selectedBox == null) return true;
+ if (selectedBox == null)
+ return true;
Box toSelect = null;
@@ -633,7 +633,6 @@ namespace FlaxEditor.Surface
{
Select(toSelect.ParentNode);
toSelect.ParentNode.SelectBox(toSelect);
-
}
return true;
}
@@ -825,10 +824,7 @@ namespace FlaxEditor.Surface
xLocation += -120 - distanceBetweenNodes.X;
}
- return new Vector2(
- xLocation,
- yLocation
- );
+ return new Vector2(xLocation, yLocation);
}
private bool IntersectsConnection(Vector2 mousePosition, out InputBox inputBox, out OutputBox outputBox)
diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs
index b5c502ba5..810f125ae 100644
--- a/Source/Editor/Surface/VisjectSurfaceWindow.cs
+++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs
@@ -759,12 +759,12 @@ namespace FlaxEditor.Surface
_propertiesEditor.Modified += OnPropertyEdited;
// Toolstrip
- _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save32, Save).LinkTooltip("Save");
+ _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo32, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
- _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo32, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
_toolstrip.AddSeparator();
- _toolstrip.AddButton(editor.Icons.PageScale32, ShowWholeGraph).LinkTooltip("Show whole graph");
+ _toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph");
// Setup input actions
InputActions.Add(options => options.Undo, _undo.PerformUndo);
diff --git a/Source/Editor/Undo/Actions/PasteActorsAction.cs b/Source/Editor/Undo/Actions/PasteActorsAction.cs
index c65aa6bf0..c77feb71d 100644
--- a/Source/Editor/Undo/Actions/PasteActorsAction.cs
+++ b/Source/Editor/Undo/Actions/PasteActorsAction.cs
@@ -140,22 +140,27 @@ namespace FlaxEditor.Actions
for (int i = 0; i < nodeParents.Count; i++)
{
- // Fix name collisions (only for parents)
var node = nodeParents[i];
var parent = node.Actor?.Parent;
if (parent != null)
{
+ // Fix name collisions
string name = node.Name;
Actor[] children = parent.Children;
if (children.Any(x => x.Name == name))
{
- // Generate new name
node.Actor.Name = StringUtils.IncrementNameNumber(name, x => children.All(y => y.Name != x));
}
}
Editor.Instance.Scene.MarkSceneEdited(node.ParentScene);
}
+
+ for (int i = 0; i < nodeParents.Count; i++)
+ {
+ var node = nodeParents[i];
+ node.PostPaste();
+ }
}
///
diff --git a/Source/Editor/Undo/UndoActionBase.cs b/Source/Editor/Undo/UndoActionBase.cs
index 5de052a45..6648096c6 100644
--- a/Source/Editor/Undo/UndoActionBase.cs
+++ b/Source/Editor/Undo/UndoActionBase.cs
@@ -4,7 +4,6 @@ using System;
using FlaxEditor.SceneGraph;
using FlaxEngine;
using Newtonsoft.Json;
-using JsonSerializer = FlaxEngine.Json.JsonSerializer;
namespace FlaxEditor
{
@@ -28,7 +27,6 @@ namespace FlaxEditor
var id = Guid.Parse((string)reader.Value);
return SceneGraphFactory.FindNode(id);
}
-
return null;
}
@@ -56,19 +54,11 @@ namespace FlaxEditor
///
/// Gets or sets the serialized undo data.
///
- ///
- /// The data.
- ///
[NoSerialize]
- public TData Data
+ public virtual TData Data
{
- get => JsonConvert.DeserializeObject(_data, JsonSerializer.Settings);
- protected set => _data = JsonConvert.SerializeObject(value, Formatting.None, JsonSerializer.Settings);
- /*protected set
- {
- _data = JsonConvert.SerializeObject(value, Formatting.Indented, JsonSerializer.Settings);
- Debug.Info(_data);
- }*/
+ get => JsonConvert.DeserializeObject(_data, FlaxEngine.Json.JsonSerializer.Settings);
+ protected set => _data = JsonConvert.SerializeObject(value, Formatting.None, FlaxEngine.Json.JsonSerializer.Settings);
}
///
diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp
index aa26388c1..85d409b92 100644
--- a/Source/Editor/Utilities/EditorUtilities.cpp
+++ b/Source/Editor/Utilities/EditorUtilities.cpp
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "EditorUtilities.h"
+#include "Engine/Engine/Globals.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Core/Log.h"
diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs
index a13d441d9..3eab59f80 100644
--- a/Source/Editor/Utilities/Utils.cs
+++ b/Source/Editor/Utilities/Utils.cs
@@ -2,15 +2,15 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
+using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.GUI.Tree;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
-using Newtonsoft.Json;
namespace FlaxEditor.Utilities
{
@@ -29,46 +29,6 @@ namespace FlaxEditor.Utilities
"PB"
};
- internal enum VariantType
- {
- Null = 0,
- Void,
-
- Bool,
- Int,
- Uint,
- Int64,
- Uint64,
- Float,
- Double,
- Pointer,
-
- String,
- Object,
- Structure,
- Asset,
- Blob,
- Enum,
-
- Vector2,
- Vector3,
- Vector4,
- Color,
- Guid,
- BoundingBox,
- BoundingSphere,
- Quaternion,
- Transform,
- Rectangle,
- Ray,
- Matrix,
-
- Array,
- Dictionary,
- ManagedObject,
- Typename,
- }
-
///
/// The name of the Flax Engine C# assembly name.
///
@@ -399,151 +359,6 @@ namespace FlaxEditor.Utilities
stream.Write(bytes);
}
- internal static VariantType ToVariantType(this Type type)
- {
- VariantType variantType;
- if (type == null)
- variantType = VariantType.Null;
- else if (type == typeof(void))
- variantType = VariantType.Void;
- else if (type == typeof(bool))
- variantType = VariantType.Bool;
- else if (type == typeof(int))
- variantType = VariantType.Int;
- else if (type == typeof(uint))
- variantType = VariantType.Uint;
- else if (type == typeof(long))
- variantType = VariantType.Int64;
- else if (type == typeof(ulong))
- variantType = VariantType.Uint64;
- else if (type.IsEnum)
- variantType = VariantType.Enum;
- else if (type == typeof(float))
- variantType = VariantType.Float;
- else if (type == typeof(double))
- variantType = VariantType.Double;
- else if (type == typeof(IntPtr))
- variantType = VariantType.Pointer;
- else if (type == typeof(string))
- variantType = VariantType.String;
- else if (type == typeof(Type) || type == typeof(ScriptType))
- variantType = VariantType.Typename;
- else if (typeof(Asset).IsAssignableFrom(type))
- variantType = VariantType.Asset;
- else if (typeof(FlaxEngine.Object).IsAssignableFrom(type))
- variantType = VariantType.Object;
- else if (type == typeof(BoundingBox))
- variantType = VariantType.BoundingBox;
- else if (type == typeof(Transform))
- variantType = VariantType.Transform;
- else if (type == typeof(Ray))
- variantType = VariantType.Ray;
- else if (type == typeof(Matrix))
- variantType = VariantType.Matrix;
- else if (type == typeof(Vector2))
- variantType = VariantType.Vector2;
- else if (type == typeof(Vector3))
- variantType = VariantType.Vector3;
- else if (type == typeof(Vector4))
- variantType = VariantType.Vector4;
- else if (type == typeof(Color))
- variantType = VariantType.Color;
- else if (type == typeof(Guid))
- variantType = VariantType.Guid;
- else if (type == typeof(Quaternion))
- variantType = VariantType.Quaternion;
- else if (type == typeof(Rectangle))
- variantType = VariantType.Rectangle;
- else if (type == typeof(BoundingSphere))
- variantType = VariantType.BoundingSphere;
- else if (type.IsValueType)
- variantType = VariantType.Structure;
- else if (type == typeof(byte[]))
- variantType = VariantType.Blob;
- else if (type == typeof(object[]))
- variantType = VariantType.Array;
- else if (type == typeof(Dictionary
/// Material resource name
- MaterialShader(const String& name);
+ MaterialShader(const StringView& name);
public:
@@ -90,7 +90,7 @@ public:
/// Stream with compiled shader data
/// Loaded material info structure
/// The created and loaded material or null if failed.
- static MaterialShader* Create(const String& name, MemoryReadStream& shaderCacheStream, const MaterialInfo& info);
+ static MaterialShader* Create(const StringView& name, MemoryReadStream& shaderCacheStream, const MaterialInfo& info);
///
/// Creates the dummy material used by the Null rendering backend to mock object but not perform any rendering.
diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp
index abc28a0de..2c5dc147a 100644
--- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp
+++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp
@@ -2,6 +2,7 @@
#include "MaterialShaderFeatures.h"
#include "Engine/Graphics/RenderTask.h"
+#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Renderer/ShadowsPass.h"
#if USE_EDITOR
diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.h b/Source/Engine/Graphics/Materials/ParticleMaterialShader.h
index a38bf56f0..28a52ddf5 100644
--- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.h
+++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.h
@@ -54,7 +54,7 @@ public:
/// Init
///
/// Material resource name
- ParticleMaterialShader(const String& name)
+ ParticleMaterialShader(const StringView& name)
: MaterialShader(name)
{
}
diff --git a/Source/Engine/Graphics/Materials/PostFxMaterialShader.h b/Source/Engine/Graphics/Materials/PostFxMaterialShader.h
index 85767736f..0fdc5d416 100644
--- a/Source/Engine/Graphics/Materials/PostFxMaterialShader.h
+++ b/Source/Engine/Graphics/Materials/PostFxMaterialShader.h
@@ -31,7 +31,7 @@ public:
/// Init
///
/// Material resource name
- PostFxMaterialShader(const String& name)
+ PostFxMaterialShader(const StringView& name)
: MaterialShader(name)
{
}
diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.h b/Source/Engine/Graphics/Materials/TerrainMaterialShader.h
index f69fc48ba..519233807 100644
--- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.h
+++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.h
@@ -48,7 +48,7 @@ public:
/// Init
///
/// Material resource name
- TerrainMaterialShader(const String& name)
+ TerrainMaterialShader(const StringView& name)
: MaterialShader(name)
{
}
diff --git a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.h b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.h
index e38944342..5cf69fecb 100644
--- a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.h
+++ b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.h
@@ -19,7 +19,7 @@ public:
/// Init
///
/// Material resource name
- VolumeParticleMaterialShader(const String& name)
+ VolumeParticleMaterialShader(const StringView& name)
: MaterialShader(name)
{
}
diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp
index 5d7f712a8..dddbd6a3e 100644
--- a/Source/Engine/Graphics/Models/Mesh.cpp
+++ b/Source/Engine/Graphics/Models/Mesh.cpp
@@ -5,6 +5,8 @@
#include "Engine/Content/Assets/Material.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Graphics/GPUContext.h"
+#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Graphics/RenderTask.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Serialization/MemoryReadStream.h"
@@ -131,22 +133,29 @@ namespace
}
}
+bool Mesh::HasVertexColors() const
+{
+ return _vertexBuffers[2] != nullptr && _vertexBuffers[2]->IsAllocated();
+}
+
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, void* ib, bool use16BitIndices)
{
+ auto model = (Model*)_model;
+
Unload();
// Setup GPU resources
- _model->LODs[_lodIndex]._verticesCount -= _vertices;
+ model->LODs[_lodIndex]._verticesCount -= _vertices;
const bool failed = Load(vertexCount, triangleCount, vb0, vb1, vb2, ib, use16BitIndices);
if (!failed)
{
- _model->LODs[_lodIndex]._verticesCount += _vertices;
+ model->LODs[_lodIndex]._verticesCount += _vertices;
// Calculate mesh bounds
SetBounds(BoundingBox::FromPoints((Vector3*)vb0, vertexCount));
// Send event (actors using this model can update bounds, etc.)
- _model->onLoaded();
+ model->onLoaded();
}
return failed;
@@ -214,17 +223,6 @@ Mesh::~Mesh()
SAFE_DELETE_GPU_RESOURCE(_indexBuffer);
}
-void Mesh::SetMaterialSlotIndex(int32 value)
-{
- if (value < 0 || value >= _model->MaterialSlots.Count())
- {
- LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count());
- return;
- }
-
- _materialSlotIndex = value;
-}
-
bool Mesh::Load(uint32 vertices, uint32 triangles, void* vb0, void* vb1, void* vb2, void* ib, bool use16BitIndexBuffer)
{
// Cache data
@@ -443,7 +441,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float
// TODO: cache vertexOffset within the model LOD per-mesh
uint32 vertexOffset = 0;
for (int32 meshIndex = 0; meshIndex < _index; meshIndex++)
- vertexOffset += _model->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount();
+ vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount();
drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex];
drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType);
}
@@ -464,7 +462,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float
renderContext.List->AddDrawCall(drawModes, info.Flags, drawCall, entry.ReceiveDecals);
}
-bool Mesh::ExtractData(MeshBufferType type, BytesContainer& result) const
+bool Mesh::DownloadDataGPU(MeshBufferType type, BytesContainer& result) const
{
GPUBuffer* buffer = nullptr;
switch (type)
@@ -485,7 +483,7 @@ bool Mesh::ExtractData(MeshBufferType type, BytesContainer& result) const
return buffer && buffer->DownloadData(result);
}
-Task* Mesh::ExtractDataAsync(MeshBufferType type, BytesContainer& result) const
+Task* Mesh::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const
{
GPUBuffer* buffer = nullptr;
switch (type)
@@ -506,6 +504,15 @@ Task* Mesh::ExtractDataAsync(MeshBufferType type, BytesContainer& result) const
return buffer ? buffer->DownloadDataAsync(result) : nullptr;
}
+bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result) const
+{
+#if !BUILD_RELEASE
+ // TODO: implement this
+ LOG(Error, "Mesh::DownloadDataCPU not implemented.");
+#endif
+ return true;
+}
+
ScriptingObject* Mesh::GetParentModel()
{
return _model;
@@ -661,7 +668,7 @@ bool Mesh::DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 typeI)
// TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer
BytesContainer data;
- auto task = mesh->ExtractDataAsync(bufferType, data);
+ auto task = mesh->DownloadDataGPUAsync(bufferType, data);
if (task == nullptr)
return true;
diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h
index 83c37c530..ca5174f7b 100644
--- a/Source/Engine/Graphics/Models/Mesh.h
+++ b/Source/Engine/Graphics/Models/Mesh.h
@@ -2,38 +2,29 @@
#pragma once
-#include "Engine/Core/Math/BoundingBox.h"
-#include "Engine/Core/Math/BoundingSphere.h"
-#include "Engine/Scripting/ScriptingObject.h"
-#include "Engine/Renderer/RenderList.h"
-#include "Engine/Graphics/RenderTask.h"
+#include "MeshBase.h"
#include "ModelInstanceEntry.h"
#include "Config.h"
#include "Types.h"
+#include "Engine/Level/Types.h"
#if USE_PRECISE_MESH_INTERSECTS
#include "CollisionProxy.h"
#endif
+struct GeometryDrawStateData;
+class Lightmap;
class GPUBuffer;
///
/// Represents part of the model that is made of vertices and can be rendered using custom material and transformation.
///
-API_CLASS(NoSpawn) class FLAXENGINE_API Mesh : public PersistentScriptingObject
+API_CLASS(NoSpawn) class FLAXENGINE_API Mesh : public MeshBase
{
-DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(Mesh, PersistentScriptingObject);
+DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(Mesh, MeshBase);
protected:
-
- Model* _model;
int32 _index;
int32 _lodIndex;
- int32 _materialSlotIndex;
- bool _use16BitIndexBuffer;
bool _hasLightmapUVs;
- BoundingBox _box;
- BoundingSphere _sphere;
- uint32 _vertices;
- uint32 _triangles;
GPUBuffer* _vertexBuffers[3];
GPUBuffer* _indexBuffer;
#if USE_PRECISE_MESH_INTERSECTS
@@ -41,7 +32,6 @@ protected:
#endif
public:
-
Mesh(const Mesh& other)
: Mesh()
{
@@ -56,13 +46,12 @@ public:
~Mesh();
public:
-
///
/// Gets the model owning this mesh.
///
FORCE_INLINE Model* GetModel() const
{
- return _model;
+ return (Model*)_model;
}
///
@@ -81,35 +70,6 @@ public:
return _index;
}
- ///
- /// Gets the index of the material slot to use during this mesh rendering.
- ///
- API_PROPERTY() FORCE_INLINE int32 GetMaterialSlotIndex() const
- {
- return _materialSlotIndex;
- }
-
- ///
- /// Sets the index of the material slot to use during this mesh rendering.
- ///
- API_PROPERTY() void SetMaterialSlotIndex(int32 value);
-
- ///
- /// Gets the triangle count.
- ///
- API_PROPERTY() FORCE_INLINE int32 GetTriangleCount() const
- {
- return _triangles;
- }
-
- ///
- /// Gets the vertex count.
- ///
- API_PROPERTY() FORCE_INLINE int32 GetVertexCount() const
- {
- return _vertices;
- }
-
///
/// Gets the index buffer.
///
@@ -138,23 +98,11 @@ public:
return _vertexBuffers[0] != nullptr;
}
- ///
- /// Determines whether this mesh is using 16 bit index buffer, otherwise it's 32 bit.
- ///
- /// True if this mesh is using 16 bit index buffer, otherwise 32 bit index buffer.
- API_PROPERTY() FORCE_INLINE bool Use16BitIndexBuffer() const
- {
- return _use16BitIndexBuffer;
- }
-
///
/// Determines whether this mesh has a vertex colors buffer.
///
/// True if this mesh has a vertex colors buffers.
- API_PROPERTY() FORCE_INLINE bool HasVertexColors() const
- {
- return _vertexBuffers[2] != nullptr && _vertexBuffers[2]->IsAllocated();
- }
+ API_PROPERTY() bool HasVertexColors() const;
///
/// Determines whether this mesh contains valid lightmap texture coordinates data.
@@ -165,34 +113,6 @@ public:
return _hasLightmapUVs;
}
- ///
- /// Sets the mesh bounds.
- ///
- /// The bounding box.
- void SetBounds(const BoundingBox& box)
- {
- _box = box;
- BoundingSphere::FromBox(box, _sphere);
- }
-
- ///
- /// Gets the box.
- ///
- /// The bounding box.
- API_PROPERTY() FORCE_INLINE const BoundingBox& GetBox() const
- {
- return _box;
- }
-
- ///
- /// Gets the sphere.
- ///
- /// The bounding sphere.
- API_PROPERTY() FORCE_INLINE const BoundingSphere& GetSphere() const
- {
- return _sphere;
- }
-
#if USE_PRECISE_MESH_INTERSECTS
///
@@ -207,7 +127,6 @@ public:
#endif
public:
-
///
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
///
@@ -286,7 +205,6 @@ public:
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, Vector3* vertices, uint32* triangles, Vector3* normals = nullptr, Vector3* tangents = nullptr, Vector2* uvs = nullptr, Color32* colors = nullptr);
public:
-
///
/// Updates the model mesh index buffer (used by the virtual models created with Init rather than Load).
///
@@ -319,7 +237,6 @@ public:
bool UpdateTriangles(uint32 triangleCount, void* ib, bool use16BitIndices);
public:
-
///
/// Initializes instance of the class.
///
@@ -351,7 +268,6 @@ public:
void Unload();
public:
-
///
/// Determines if there is an intersection between the mesh and a ray in given world
///
@@ -372,7 +288,6 @@ public:
}
public:
-
///
/// Gets the draw call geometry for this mesh. Sets the index and vertex buffers.
///
@@ -472,25 +387,12 @@ public:
void Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const;
public:
-
- ///
- /// Extract mesh buffer data (cannot be called from the main thread!).
- ///
- /// Buffer type
- /// The result data
- /// True if failed, otherwise false
- bool ExtractData(MeshBufferType type, BytesContainer& result) const;
-
- ///
- /// Extracts mesh buffer data in the async task.
- ///
- /// Buffer type
- /// The result data
- /// Created async task used to gather the buffer data.
- Task* ExtractDataAsync(MeshBufferType type, BytesContainer& result) const;
+ // [MeshBase]
+ bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const override;
+ Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const override;
+ bool DownloadDataCPU(MeshBufferType type, BytesContainer& result) const override;
private:
-
// Internal bindings
API_FUNCTION(NoProxy) ScriptingObject* GetParentModel();
API_FUNCTION(NoProxy) bool UpdateMeshInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj);
diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h
new file mode 100644
index 000000000..bedb57c90
--- /dev/null
+++ b/Source/Engine/Graphics/Models/MeshBase.h
@@ -0,0 +1,134 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Core/Math/BoundingBox.h"
+#include "Engine/Core/Math/BoundingSphere.h"
+#include "Engine/Core/Types/DataContainer.h"
+#include "Engine/Graphics/Models/Types.h"
+#include "Engine/Scripting/ScriptingObject.h"
+
+class Task;
+class ModelBase;
+
+///
+/// Base class for model resources meshes.
+///
+API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API MeshBase : public PersistentScriptingObject
+{
+DECLARE_SCRIPTING_TYPE_MINIMAL(MeshBase);
+protected:
+
+ ModelBase* _model;
+ bool _use16BitIndexBuffer;
+ BoundingBox _box;
+ BoundingSphere _sphere;
+ uint32 _vertices;
+ uint32 _triangles;
+ int32 _materialSlotIndex;
+
+ explicit MeshBase(const SpawnParams& params)
+ : PersistentScriptingObject(params)
+ {
+ }
+
+public:
+
+ ///
+ /// Gets the model owning this mesh.
+ ///
+ FORCE_INLINE ModelBase* GetModelBase() const
+ {
+ return _model;
+ }
+
+ ///
+ /// Gets the triangle count.
+ ///
+ /// The triangles
+ API_PROPERTY() FORCE_INLINE int32 GetTriangleCount() const
+ {
+ return _triangles;
+ }
+
+ ///
+ /// Gets the vertex count.
+ ///
+ /// The vertices
+ API_PROPERTY() FORCE_INLINE int32 GetVertexCount() const
+ {
+ return _vertices;
+ }
+
+ ///
+ /// Gets the box.
+ ///
+ /// The bounding box.
+ API_PROPERTY() FORCE_INLINE const BoundingBox& GetBox() const
+ {
+ return _box;
+ }
+
+ ///
+ /// Gets the sphere.
+ ///
+ /// The bounding sphere.
+ API_PROPERTY() FORCE_INLINE const BoundingSphere& GetSphere() const
+ {
+ return _sphere;
+ }
+
+ ///
+ /// Determines whether this mesh is using 16 bit index buffer, otherwise it's 32 bit.
+ ///
+ /// True if this mesh is using 16 bit index buffer, otherwise 32 bit index buffer.
+ API_PROPERTY() FORCE_INLINE bool Use16BitIndexBuffer() const
+ {
+ return _use16BitIndexBuffer;
+ }
+
+ ///
+ /// Gets the index of the material slot to use during this mesh rendering.
+ ///
+ API_PROPERTY() FORCE_INLINE int32 GetMaterialSlotIndex() const
+ {
+ return _materialSlotIndex;
+ }
+
+ ///
+ /// Sets the index of the material slot to use during this mesh rendering.
+ ///
+ API_PROPERTY() void SetMaterialSlotIndex(int32 value);
+
+ ///
+ /// Sets the mesh bounds.
+ ///
+ /// The bounding box.
+ void SetBounds(const BoundingBox& box);
+
+public:
+
+ ///
+ /// Extract mesh buffer data from GPU. Cannot be called from the main thread.
+ ///
+ /// Buffer type
+ /// The result data
+ /// True if failed, otherwise false
+ virtual bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const = 0;
+
+ ///
+ /// Extracts mesh buffer data from GPU in the async task.
+ ///
+ /// Buffer type
+ /// The result data
+ /// Created async task used to gather the buffer data.
+ virtual Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const = 0;
+
+ ///
+ /// Extract mesh buffer data from CPU. Cached internally.
+ ///
+ /// Buffer type
+ /// The result data
+ /// True if failed, otherwise false
+ virtual bool DownloadDataCPU(MeshBufferType type, BytesContainer& result) const = 0;
+};
diff --git a/Source/Engine/Graphics/Models/ModelData.Tool.cpp b/Source/Engine/Graphics/Models/ModelData.Tool.cpp
index 8439a30d1..8faa9dcaa 100644
--- a/Source/Engine/Graphics/Models/ModelData.Tool.cpp
+++ b/Source/Engine/Graphics/Models/ModelData.Tool.cpp
@@ -6,6 +6,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Core/Utilities.h"
#include "Engine/Core/Types/DateTime.h"
+#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Collections/BitArray.h"
#include "Engine/Tools/ModelTool/ModelTool.h"
#include "Engine/Tools/ModelTool/VertexTriangleAdjacency.h"
diff --git a/Source/Engine/Graphics/Models/SkeletonData.h b/Source/Engine/Graphics/Models/SkeletonData.h
index 5f336d8be..3e5700304 100644
--- a/Source/Engine/Graphics/Models/SkeletonData.h
+++ b/Source/Engine/Graphics/Models/SkeletonData.h
@@ -5,6 +5,8 @@
#include "Engine/Core/Math/Transform.h"
#include "Engine/Core/Math/Matrix.h"
#include "Engine/Core/Types/String.h"
+#include "Engine/Core/Types/StringView.h"
+#include "Engine/Core/Collections/Array.h"
///
/// Describes a single skeleton node data. Used by the runtime.
diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp
index 9e26867ab..f2d1134d9 100644
--- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp
+++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp
@@ -4,6 +4,8 @@
#include "ModelInstanceEntry.h"
#include "Engine/Content/Assets/Material.h"
#include "Engine/Content/Assets/SkinnedModel.h"
+#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Graphics/RenderTask.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Serialization/MemoryReadStream.h"
@@ -34,17 +36,6 @@ SkinnedMesh::~SkinnedMesh()
SAFE_DELETE_GPU_RESOURCE(_indexBuffer);
}
-void SkinnedMesh::SetMaterialSlotIndex(int32 value)
-{
- if (value < 0 || value >= _model->MaterialSlots.Count())
- {
- LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count());
- return;
- }
-
- _materialSlotIndex = value;
-}
-
bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, void* vb0, void* ib, bool use16BitIndexBuffer)
{
// Cache data
@@ -101,6 +92,8 @@ void SkinnedMesh::Unload()
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, void* ib, bool use16BitIndices)
{
+ auto model = (SkinnedModel*)_model;
+
// Setup GPU resources
const bool failed = Load(vertexCount, triangleCount, vb, ib, use16BitIndices);
if (!failed)
@@ -109,7 +102,7 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0Skinne
SetBounds(BoundingBox::FromPoints((Vector3*)vb, vertexCount));
// Send event (actors using this model can update bounds, etc.)
- _model->onLoaded();
+ model->onLoaded();
}
return failed;
@@ -218,7 +211,7 @@ bool SkinnedMesh::DownloadDataGPU(MeshBufferType type, BytesContainer& result) c
return buffer && buffer->DownloadData(result);
}
-Task* SkinnedMesh::DownloadDataAsyncGPU(MeshBufferType type, BytesContainer& result) const
+Task* SkinnedMesh::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const
{
GPUBuffer* buffer = nullptr;
switch (type)
@@ -535,7 +528,7 @@ bool SkinnedMesh::DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 type
{
// Get data from GPU
// TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer
- auto task = mesh->DownloadDataAsyncGPU(bufferType, data);
+ auto task = mesh->DownloadDataGPUAsync(bufferType, data);
if (task == nullptr)
return true;
task->Start();
diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h
index bbdf38014..eb854189e 100644
--- a/Source/Engine/Graphics/Models/SkinnedMesh.h
+++ b/Source/Engine/Graphics/Models/SkinnedMesh.h
@@ -2,34 +2,25 @@
#pragma once
-#include "Engine/Core/Math/BoundingBox.h"
-#include "Engine/Core/Math/BoundingSphere.h"
-#include "Engine/Scripting/ScriptingObject.h"
-#include "Engine/Renderer/RenderList.h"
-#include "Engine/Graphics/RenderTask.h"
-#include "ModelInstanceEntry.h"
+#include "MeshBase.h"
#include "Types.h"
#include "BlendShape.h"
+struct GeometryDrawStateData;
+struct RenderContext;
class GPUBuffer;
+class SkinnedMeshDrawData;
///
/// Represents part of the skinned model that is made of vertices and can be rendered using custom material, transformation and skeleton bones hierarchy.
///
-API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedMesh : public PersistentScriptingObject
+API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedMesh : public MeshBase
{
-DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedMesh, PersistentScriptingObject);
+DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedMesh, MeshBase);
protected:
- SkinnedModel* _model;
int32 _index;
int32 _lodIndex;
- int32 _materialSlotIndex;
- bool _use16BitIndexBuffer;
- BoundingBox _box;
- BoundingSphere _sphere;
- uint32 _vertices;
- uint32 _triangles;
GPUBuffer* _vertexBuffer;
GPUBuffer* _indexBuffer;
mutable Array _cachedIndexBuffer;
@@ -40,7 +31,7 @@ public:
SkinnedMesh(const SkinnedMesh& other)
: SkinnedMesh()
{
-#if !!BUILD_RELEASE
+#if !BUILD_RELEASE
CRASH; // Not used
#endif
}
@@ -58,7 +49,7 @@ public:
/// The skinned model
FORCE_INLINE SkinnedModel* GetSkinnedModel() const
{
- return _model;
+ return (SkinnedModel*)_model;
}
///
@@ -70,39 +61,6 @@ public:
return _index;
}
- ///
- /// Gets the material slot index.
- ///
- /// The material slot index
- API_PROPERTY() FORCE_INLINE int32 GetMaterialSlotIndex() const
- {
- return _materialSlotIndex;
- }
-
- ///
- /// Sets the index of the material slot index.
- ///
- /// The value.
- API_PROPERTY() void SetMaterialSlotIndex(int32 value);
-
- ///
- /// Gets the triangle count.
- ///
- /// The triangles
- API_PROPERTY() FORCE_INLINE int32 GetTriangleCount() const
- {
- return _triangles;
- }
-
- ///
- /// Gets the vertex count.
- ///
- /// The vertices
- API_PROPERTY() FORCE_INLINE int32 GetVertexCount() const
- {
- return _vertices;
- }
-
///
/// Determines whether this mesh is initialized (has vertex and index buffers initialized).
///
@@ -112,15 +70,6 @@ public:
return _vertexBuffer != nullptr;
}
- ///
- /// Determines whether this mesh is using 16 bit index buffer, otherwise it's 32 bit.
- ///
- /// True if this mesh is using 16 bit index buffer, otherwise 32 bit index buffer.
- API_PROPERTY() FORCE_INLINE bool Use16BitIndexBuffer() const
- {
- return _use16BitIndexBuffer;
- }
-
///
/// Blend shapes used by this mesh.
///
@@ -194,36 +143,6 @@ public:
/// True if failed, otherwise false.
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, void* ib, bool use16BitIndices);
-public:
-
- ///
- /// Sets the mesh bounds.
- ///
- /// The bounding box.
- void SetBounds(const BoundingBox& box)
- {
- _box = box;
- BoundingSphere::FromBox(box, _sphere);
- }
-
- ///
- /// Gets the box.
- ///
- /// The bounding box.
- API_PROPERTY() FORCE_INLINE BoundingBox GetBox() const
- {
- return _box;
- }
-
- ///
- /// Gets the sphere.
- ///
- /// The bounding sphere.
- API_PROPERTY() FORCE_INLINE BoundingSphere GetSphere() const
- {
- return _sphere;
- }
-
public:
///
@@ -319,29 +238,10 @@ public:
public:
- ///
- /// Extract mesh buffer data from the GPU (cannot be called from the main thread!).
- ///
- /// Buffer type
- /// The result data
- /// True if failed, otherwise false
- bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const;
-
- ///
- /// Extracts mesh buffer data from the GPU in the async task.
- ///
- /// Buffer type
- /// The result data
- /// Created async task used to gather the buffer data.
- Task* DownloadDataAsyncGPU(MeshBufferType type, BytesContainer& result) const;
-
- ///
- /// Extract mesh buffer data from the CPU.
- ///
- /// Buffer type
- /// The result data
- /// True if failed, otherwise false
- bool DownloadDataCPU(MeshBufferType type, BytesContainer& result) const;
+ // [MeshBase]
+ bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const override;
+ Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const override;
+ bool DownloadDataCPU(MeshBufferType type, BytesContainer& result) const override;
private:
diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp b/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp
index db0e8b86e..e2a43d085 100644
--- a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp
+++ b/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp
@@ -5,6 +5,12 @@
#include "Engine/Content/Assets/Model.h"
#include "Engine/Serialization/MemoryReadStream.h"
+bool SkinnedModelLOD::HasAnyMeshInitialized() const
+{
+ // Note: we initialize all meshes at once so the last one can be used to check it.
+ return Meshes.HasItems() && Meshes.Last().IsInitialized();
+}
+
bool SkinnedModelLOD::Load(MemoryReadStream& stream)
{
// Load LOD for each mesh
diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.h b/Source/Engine/Graphics/Models/SkinnedModelLOD.h
index 673bbb4e2..758db0a1b 100644
--- a/Source/Engine/Graphics/Models/SkinnedModelLOD.h
+++ b/Source/Engine/Graphics/Models/SkinnedModelLOD.h
@@ -33,12 +33,7 @@ public:
///
/// Determines whether any mesh has been initialized.
///
- /// True if any mesh has been initialized, otherwise false.
- FORCE_INLINE bool HasAnyMeshInitialized() const
- {
- // Note: we initialize all meshes at once so the last one can be used to check it.
- return Meshes.HasItems() && Meshes.Last().IsInitialized();
- }
+ bool HasAnyMeshInitialized() const;
public:
diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h
index 400deb3c1..e1607906f 100644
--- a/Source/Engine/Graphics/PostProcessSettings.h
+++ b/Source/Engine/Graphics/PostProcessSettings.h
@@ -3,6 +3,7 @@
#pragma once
#include "Engine/Core/Math/Vector3.h"
+#include "Engine/Core/Math/Vector4.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Serialization/ISerializable.h"
#include "Engine/Content/Assets/Texture.h"
diff --git a/Source/Engine/Graphics/RenderTask.h b/Source/Engine/Graphics/RenderTask.h
index 5d6a47861..86fa20856 100644
--- a/Source/Engine/Graphics/RenderTask.h
+++ b/Source/Engine/Graphics/RenderTask.h
@@ -84,7 +84,7 @@ public:
///
/// The index of the frame when this task was last time rendered.
///
- uint64 LastUsedFrame = 0;
+ API_FIELD(ReadOnly) uint64 LastUsedFrame = 0;
///
/// Action fired on task rendering.
@@ -251,7 +251,7 @@ public:
///
/// The scene rendering camera. Can be used to override the rendering view properties based on the current camera setup.
///
- API_FIELD() Camera* Camera = nullptr;
+ API_FIELD() ScriptingObjectReference Camera;
///
/// The render view description.
diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp
index 4fc5e2e1d..142906192 100644
--- a/Source/Engine/Graphics/RenderTools.cpp
+++ b/Source/Engine/Graphics/RenderTools.cpp
@@ -6,6 +6,7 @@
#include "PixelFormat.h"
#include "RenderView.h"
#include "GPUDevice.h"
+#include "RenderTask.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Content/Assets/SkinnedModel.h"
#include "Engine/Engine/Time.h"
@@ -553,3 +554,20 @@ float ViewToCenterLessRadius(const RenderView& view, const Vector3& center, floa
// Calculate result
return viewToCenter - radius;
}
+
+void MeshBase::SetMaterialSlotIndex(int32 value)
+{
+ if (value < 0 || value >= _model->MaterialSlots.Count())
+ {
+ LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count());
+ return;
+ }
+
+ _materialSlotIndex = value;
+}
+
+void MeshBase::SetBounds(const BoundingBox& box)
+{
+ _box = box;
+ BoundingSphere::FromBox(box, _sphere);
+}
diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp
index 4d8b8cf1d..8ce11ec70 100644
--- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp
+++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp
@@ -4,6 +4,8 @@
#include "ShaderStorage.h"
#include "ShaderCacheManager.h"
#include "Engine/Engine/CommandLine.h"
+#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Graphics/Shaders/GPUShader.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/ShadowsOfMordor/AtlasChartsPacker.h"
@@ -31,6 +33,16 @@ ShaderStorage::CachingMode ShaderStorage::GetCachingMode()
#endif
+bool ShaderAssetBase::IsNullRenderer()
+{
+ return GPUDevice::Instance->GetRendererType() == RendererType::Null;
+}
+
+int32 ShaderAssetBase::GetCacheChunkIndex()
+{
+ return GetCacheChunkIndex(GPUDevice::Instance->GetShaderProfile());
+}
+
int32 ShaderAssetBase::GetCacheChunkIndex(ShaderProfile profile)
{
int32 result;
@@ -61,6 +73,28 @@ int32 ShaderAssetBase::GetCacheChunkIndex(ShaderProfile profile)
return result;
}
+bool ShaderAssetBase::initBase(AssetInitData& initData)
+{
+ // Validate version
+ if (initData.SerializedVersion != ShaderStorage::Header::Version)
+ {
+ LOG(Warning, "Invalid shader serialized version.");
+ return true;
+ }
+
+ // Validate data
+ if (initData.CustomData.Length() != sizeof(_shaderHeader))
+ {
+ LOG(Warning, "Invalid shader header.");
+ return true;
+ }
+
+ // Load header 'as-is'
+ Platform::MemoryCopy(&_shaderHeader, initData.CustomData.Get(), sizeof(_shaderHeader));
+
+ return false;
+}
+
#if USE_EDITOR
bool ShaderAssetBase::Save()
diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.h b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.h
index ab1f79aee..a2b81bf5e 100644
--- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.h
+++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.h
@@ -4,7 +4,6 @@
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Content/BinaryAsset.h"
-#include "Engine/Graphics/GPUDevice.h"
#include "ShaderStorage.h"
///
@@ -18,17 +17,15 @@ protected:
public:
- ///
- /// Gets internal shader cache chunk index
- ///
- /// Chunk index
- FORCE_INLINE static int32 GetCacheChunkIndex()
- {
- return GetCacheChunkIndex(GPUDevice::Instance->GetShaderProfile());
- }
+ static bool IsNullRenderer();
///
- /// Gets internal shader cache chunk index
+ /// Gets internal shader cache chunk index (for current GPU device shader profile).
+ ///
+ static int32 GetCacheChunkIndex();
+
+ ///
+ /// Gets internal shader cache chunk index.
///
/// Shader profile
/// Chunk index
@@ -47,11 +44,12 @@ public:
#endif
protected:
-
+
+ bool initBase(AssetInitData& initData);
+
///
/// Gets the parent asset.
///
- /// The asset.
virtual BinaryAsset* GetShaderAsset() const = 0;
#if USE_EDITOR
@@ -110,7 +108,7 @@ protected:
};
///
-/// Base class for assets that can contain shader
+/// Base class for binary assets that can contain shader.
///
template
class ShaderAssetTypeBase : public BaseType, public ShaderAssetBase
@@ -121,11 +119,6 @@ public:
protected:
- ///
- /// Init
- ///
- /// Asset scripting class metadata
- /// Asset information
explicit ShaderAssetTypeBase(const ScriptingObjectSpawnParams& params, const AssetInfo* info)
: BaseType(params, info)
{
@@ -134,38 +127,22 @@ protected:
protected:
// [BaseType]
- BinaryAsset* GetShaderAsset() const override
- {
- return (BinaryAsset*)this;
- }
bool init(AssetInitData& initData) override
{
- // Validate version
- if (initData.SerializedVersion != ShadersSerializedVersion)
- {
- LOG(Warning, "Invalid shader serialized version.");
- return true;
- }
-
- // Validate data
- if (initData.CustomData.Length() != sizeof(_shaderHeader))
- {
- LOG(Warning, "Invalid shader header.");
- return true;
- }
-
- // Load header 'as-is'
- Platform::MemoryCopy(&_shaderHeader, initData.CustomData.Get(), sizeof(_shaderHeader));
-
- return false;
+ return initBase(initData);
}
-
AssetChunksFlag getChunksToPreload() const override
{
AssetChunksFlag result = 0;
const auto cachingMode = ShaderStorage::GetCachingMode();
- if (cachingMode == ShaderStorage::CachingMode::AssetInternal && GPUDevice::Instance->GetRendererType() != RendererType::Null)
+ if (cachingMode == ShaderStorage::CachingMode::AssetInternal && !IsNullRenderer())
result |= GET_CHUNK_FLAG(GetCacheChunkIndex());
return result;
}
+
+ // [ShaderAssetBase]
+ BinaryAsset* GetShaderAsset() const override
+ {
+ return (BinaryAsset*)this;
+ }
};
diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp
index 92fa47e8c..cd63b3ec4 100644
--- a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp
+++ b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp
@@ -233,7 +233,7 @@ bool ShaderCacheManagerService::Init()
{
LOG(Warning, "Shaders cache database is invalid. Performing reset.");
- if (FileSystem::DeleteDirectory(rootDir))
+ if (FileSystem::DirectoryExists(rootDir) && FileSystem::DeleteDirectory(rootDir))
{
LOG(Warning, "Failed to reset the shaders cache database.");
}
diff --git a/Source/Engine/Graphics/Textures/ITextureOwner.h b/Source/Engine/Graphics/Textures/ITextureOwner.h
index 8f66298ba..196b76664 100644
--- a/Source/Engine/Graphics/Textures/ITextureOwner.h
+++ b/Source/Engine/Graphics/Textures/ITextureOwner.h
@@ -18,8 +18,7 @@ public:
///
/// Gets the texture owner mutex used to synchronize texture logic.
///
- /// The mutex.
- virtual CriticalSection* GetOwnerLocker() const = 0;
+ virtual CriticalSection& GetOwnerLocker() const = 0;
///
/// Get texture mip map data
diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp
index 15b9c540c..d5a1e9339 100644
--- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp
+++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp
@@ -33,6 +33,23 @@ StreamingTexture::~StreamingTexture()
ASSERT(_streamingTasksCount == 0);
}
+Vector2 StreamingTexture::Size() const
+{
+ return _texture->Size();
+}
+
+int32 StreamingTexture::TextureMipIndexToTotalIndex(int32 textureMipIndex) const
+{
+ const int32 missingMips = TotalMipLevels() - _texture->MipLevels();
+ return textureMipIndex + missingMips;
+}
+
+int32 StreamingTexture::TotalIndexToTextureMipIndex(int32 mipIndex) const
+{
+ const int32 missingMips = TotalMipLevels() - _texture->MipLevels();
+ return mipIndex - missingMips;
+}
+
bool StreamingTexture::Create(const TextureHeader& header)
{
// Validate header (further validation is performed by the Texture.Init)
@@ -90,13 +107,27 @@ uint64 StreamingTexture::GetTotalMemoryUsage() const
return CalculateTextureMemoryUsage(_header.Format, _header.Width, _header.Height, _header.MipLevels) * arraySize;
}
+String StreamingTexture::ToString() const
+{
+ return _texture->ToString();
+}
+
+int32 StreamingTexture::GetCurrentResidency() const
+{
+ return _texture->ResidentMipLevels();
+}
+
+int32 StreamingTexture::GetAllocatedResidency() const
+{
+ return _texture->MipLevels();
+}
+
bool StreamingTexture::CanBeUpdated() const
{
// Streaming Texture cannot be updated if:
// - is not initialized
// - mip data uploading job running
// - resize texture job running
-
return IsInitialized() && Platform::AtomicRead(&_streamingTasksCount) == 0;
}
@@ -146,7 +177,6 @@ protected:
return Result::Ok;
}
-
void OnEnd() override
{
Platform::InterlockedDecrement(&_streamingTexture->_streamingTasksCount);
@@ -154,7 +184,6 @@ protected:
// Base
GPUTask::OnEnd();
}
-
void OnSync() override
{
Swap(_streamingTexture->_texture, _newTexture);
@@ -298,7 +327,6 @@ protected:
return Result::Ok;
}
-
void OnEnd() override
{
_dataLock.Release();
diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.h b/Source/Engine/Graphics/Textures/StreamingTexture.h
index 3b6e94959..1dda71ba4 100644
--- a/Source/Engine/Graphics/Textures/StreamingTexture.h
+++ b/Source/Engine/Graphics/Textures/StreamingTexture.h
@@ -2,7 +2,6 @@
#pragma once
-#include "GPUTexture.h"
#include "ITextureOwner.h"
#include "Engine/Streaming/StreamableResource.h"
#include "Types.h"
@@ -14,7 +13,6 @@ class FLAXENGINE_API StreamingTexture : public Object, public StreamableResource
{
friend class StreamTextureMipTask;
friend class StreamTextureResizeTask;
-
protected:
ITextureOwner* _owner;
@@ -25,16 +23,7 @@ protected:
public:
- ///
- /// Init
- ///
- /// Parent object
- /// Texture object name
StreamingTexture(ITextureOwner* owner, const String& name);
-
- ///
- /// Destructor
- ///
~StreamingTexture();
public:
@@ -42,31 +31,23 @@ public:
///
/// Gets the owner.
///
- /// The owner.
FORCE_INLINE ITextureOwner* GetOwner() const
{
return _owner;
}
///
- /// Gets texture object handle
+ /// Gets texture object handle.
///
- /// Texture object
FORCE_INLINE GPUTexture* GetTexture() const
{
return _texture;
}
///
- /// Gets texture size of Vector2::Zero if not loaded
+ /// Gets texture size of Vector2::Zero if not loaded.
///
- /// Texture size
- FORCE_INLINE Vector2 Size() const
- {
- return _texture->Size();
- }
-
-public:
+ Vector2 Size() const;
///
/// Gets a value indicating whether this instance is initialized.
@@ -88,7 +69,6 @@ public:
///
/// Gets total texture height (in texels)
///
- /// Texture width
FORCE_INLINE int32 TotalHeight() const
{
return _header.Height;
@@ -97,7 +77,6 @@ public:
///
/// Gets total texture array size
///
- /// Texture array size
FORCE_INLINE int32 TotalArraySize() const
{
return IsCubeMap() ? 6 : 1;
@@ -106,7 +85,6 @@ public:
///
/// Gets total texture mip levels count
///
- /// Texture mip levels
FORCE_INLINE int32 TotalMipLevels() const
{
return _header.MipLevels;
@@ -115,7 +93,6 @@ public:
///
/// Returns texture format type
///
- /// Texture format type
FORCE_INLINE TextureFormatType GetFormatType() const
{
return _header.Type;
@@ -124,7 +101,6 @@ public:
///
/// Returns true if it's a cube map texture
///
- /// True if i's a cubemap
FORCE_INLINE bool IsCubeMap() const
{
return _header.IsCubeMap;
@@ -133,7 +109,6 @@ public:
///
/// Returns true if texture cannot be used during GPU resources streaming system
///
- /// True if texture cannot be used during GPU resources streaming system
FORCE_INLINE bool NeverStream() const
{
return _header.NeverStream;
@@ -142,7 +117,6 @@ public:
///
/// Gets the texture header.
///
- /// Header
FORCE_INLINE const TextureHeader* GetHeader() const
{
return &_header;
@@ -163,22 +137,14 @@ public:
///
/// Index of the texture mip.
/// The index of the mip map.
- FORCE_INLINE int32 TextureMipIndexToTotalIndex(int32 textureMipIndex) const
- {
- const int32 missingMips = TotalMipLevels() - _texture->MipLevels();
- return textureMipIndex + missingMips;
- }
+ int32 TextureMipIndexToTotalIndex(int32 textureMipIndex) const;
///
/// Converts absolute mip map index to the allocated texture mip index.
///
/// Index of the texture mip.
/// The index of the mip map.
- FORCE_INLINE int32 TotalIndexToTextureMipIndex(int32 mipIndex) const
- {
- const int32 missingMips = TotalMipLevels() - _texture->MipLevels();
- return mipIndex - missingMips;
- }
+ int32 TotalIndexToTextureMipIndex(int32 mipIndex) const;
public:
@@ -211,27 +177,15 @@ public:
public:
// [Object]
- String ToString() const override
- {
- return _texture->ToString();
- }
+ String ToString() const override;
// [StreamableResource]
int32 GetMaxResidency() const override
{
return _header.MipLevels;
}
-
- int32 GetCurrentResidency() const override
- {
- return _texture->ResidentMipLevels();
- }
-
- int32 GetAllocatedResidency() const override
- {
- return _texture->MipLevels();
- }
-
+ int32 GetCurrentResidency() const override;
+ int32 GetAllocatedResidency() const override;
bool CanBeUpdated() const override;
Task* UpdateAllocation(int32 residency) override;
Task* CreateStreamingTask(int32 residency) override;
diff --git a/Source/Engine/Graphics/Textures/TextureBase.cpp b/Source/Engine/Graphics/Textures/TextureBase.cpp
index 83e781a5a..d21d61f2f 100644
--- a/Source/Engine/Graphics/Textures/TextureBase.cpp
+++ b/Source/Engine/Graphics/Textures/TextureBase.cpp
@@ -3,10 +3,11 @@
#include "TextureBase.h"
#include "TextureData.h"
#include "Engine/Graphics/GPUDevice.h"
-#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
-#include "Engine/Debug/Exceptions/InvalidOperationException.h"
+#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
+#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
+#include "Engine/Debug/Exceptions/InvalidOperationException.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
@@ -22,6 +23,36 @@ TextureBase::TextureBase(const SpawnParams& params, const AssetInfo* info)
{
}
+Vector2 TextureBase::Size() const
+{
+ return Vector2(static_cast(_texture.TotalWidth()), static_cast(_texture.TotalHeight()));
+}
+
+int32 TextureBase::GetArraySize() const
+{
+ return StreamingTexture()->TotalArraySize();
+}
+
+int32 TextureBase::GetMipLevels() const
+{
+ return StreamingTexture()->TotalMipLevels();
+}
+
+int32 TextureBase::GetResidentMipLevels() const
+{
+ return GetTexture()->ResidentMipLevels();
+}
+
+uint64 TextureBase::GetCurrentMemoryUsage() const
+{
+ return GetTexture()->GetMemoryUsage();
+}
+
+uint64 TextureBase::GetTotalMemoryUsage() const
+{
+ return StreamingTexture()->GetTotalMemoryUsage();
+}
+
BytesContainer TextureBase::GetMipData(int32 mipIndex, int32& rowPitch, int32& slicePitch)
{
BytesContainer result;
@@ -193,9 +224,9 @@ int32 TextureBase::calculateChunkIndex(int32 mipIndex) const
return mipIndex;
}
-CriticalSection* TextureBase::GetOwnerLocker() const
+CriticalSection& TextureBase::GetOwnerLocker() const
{
- return &_parent->Locker;
+ return _parent->Locker;
}
void TextureBase::unload(bool isReloading)
diff --git a/Source/Engine/Graphics/Textures/TextureBase.h b/Source/Engine/Graphics/Textures/TextureBase.h
index 2cb895a23..7839f940a 100644
--- a/Source/Engine/Graphics/Textures/TextureBase.h
+++ b/Source/Engine/Graphics/Textures/TextureBase.h
@@ -6,6 +6,8 @@
#include "StreamingTexture.h"
#include "Engine/Core/Log.h"
+class TextureData;
+
///
/// Base class for , , and other assets that can contain texture data.
///
@@ -103,50 +105,32 @@ public:
///
/// Gets the total size of the texture. Actual resident size may be different due to dynamic content streaming. Returns Vector2::Zero if texture is not loaded.
///
- API_PROPERTY() FORCE_INLINE Vector2 Size() const
- {
- return Vector2(static_cast(_texture.TotalWidth()), static_cast(_texture.TotalHeight()));
- }
+ API_PROPERTY() Vector2 Size() const;
///
/// Gets the total array size of the texture.
///
- API_PROPERTY() int32 GetArraySize() const
- {
- return StreamingTexture()->TotalArraySize();
- }
+ API_PROPERTY() int32 GetArraySize() const;
///
/// Gets the total mip levels count of the texture. Actual resident mipmaps count may be different due to dynamic content streaming.
///
- API_PROPERTY() int32 GetMipLevels() const
- {
- return StreamingTexture()->TotalMipLevels();
- }
+ API_PROPERTY() int32 GetMipLevels() const;
///
/// Gets the current mip levels count of the texture that are on GPU ready to use.
///
- API_PROPERTY() int32 GetResidentMipLevels() const
- {
- return GetTexture()->ResidentMipLevels();
- }
+ API_PROPERTY() int32 GetResidentMipLevels() const;
///
/// Gets the amount of the memory used by this resource. Exact value may differ due to memory alignment and resource allocation policy.
///
- API_PROPERTY() uint64 GetCurrentMemoryUsage() const
- {
- return GetTexture()->GetMemoryUsage();
- }
+ API_PROPERTY() uint64 GetCurrentMemoryUsage() const;
///
/// Gets the total memory usage that texture may have in use (if loaded to the maximum quality). Exact value may differ due to memory alignment and resource allocation policy.
///
- API_PROPERTY() uint64 GetTotalMemoryUsage() const
- {
- return StreamingTexture()->GetTotalMemoryUsage();
- }
+ API_PROPERTY() uint64 GetTotalMemoryUsage() const;
public:
@@ -186,7 +170,7 @@ private:
public:
// [ITextureOwner]
- CriticalSection* GetOwnerLocker() const override;
+ CriticalSection& GetOwnerLocker() const override;
Task* RequestMipDataAsync(int32 mipIndex) override;
FlaxStorage::LockData LockData() override;
void GetMipData(int32 mipIndex, BytesContainer& data) const override;
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp
index 6ebd55384..e9d83bfc3 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp
+++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUSwapChainDX11.cpp
@@ -99,8 +99,6 @@ void GPUSwapChainDX11::SetFullscreen(bool isFullscreen)
swapChainDesc.BufferDesc = outputDX.DesktopViewMode;
}
- releaseBackBuffer();
-
if (FAILED(_swapChain->ResizeTarget(&swapChainDesc.BufferDesc)))
{
LOG(Warning, "Swapchain resize failed.");
@@ -110,10 +108,6 @@ void GPUSwapChainDX11::SetFullscreen(bool isFullscreen)
{
LOG(Warning, "Cannot change fullscreen mode for '{0}' to {1}.", ToString(), isFullscreen);
}
-
- VALIDATE_DIRECTX_RESULT(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, _width, _height, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags));
-
- getBackBuffer();
}
#else
LOG(Info, "Cannot change fullscreen mode on this platform");
@@ -208,7 +202,7 @@ bool GPUSwapChainDX11::Resize(int32 width, int32 height)
ASSERT(_swapChain);
// Disable DXGI changes to the window
- VALIDATE_DIRECTX_RESULT(dxgi->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER));
+ VALIDATE_DIRECTX_RESULT(dxgi->MakeWindowAssociation(_windowHandle, 0));
#else
auto dxgiFactory = (IDXGIFactory2*)_device->GetDXGIFactory();
VALIDATE_DIRECTX_RESULT(dxgiFactory->CreateSwapChainForCoreWindow(_device->GetDevice(), static_cast(_windowHandle), &swapChainDesc, nullptr, &_swapChain));
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp
index 669b217e4..b8ac22bba 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp
+++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUSwapChainDX12.cpp
@@ -124,8 +124,6 @@ void GPUSwapChainDX12::SetFullscreen(bool isFullscreen)
swapChainDesc.BufferDesc = outputDX.DesktopViewMode;
}
- releaseBackBuffer();
-
if (FAILED(_swapChain->ResizeTarget(&swapChainDesc.BufferDesc)))
{
LOG(Warning, "Swapchain resize failed.");
@@ -136,10 +134,6 @@ void GPUSwapChainDX12::SetFullscreen(bool isFullscreen)
LOG(Warning, "Cannot change fullscreen mode for '{0}' to {1}.", ToString(), isFullscreen);
}
- VALIDATE_DIRECTX_RESULT(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, _width, _height, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags));
-
- getBackBuffer();
-
_isFullscreen = isFullscreen;
}
#else
@@ -223,7 +217,7 @@ bool GPUSwapChainDX12::Resize(int32 width, int32 height)
_backBuffers.Resize(swapChainDesc.BufferCount);
// Disable DXGI changes to the window
- dxgiFactory->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER);
+ dxgiFactory->MakeWindowAssociation(_windowHandle, 0);
}
else
{
diff --git a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp
index f9a39574c..ac56a2a9f 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp
@@ -43,7 +43,7 @@ void CmdBufferVulkan::End()
{
ASSERT(IsOutsideRenderPass());
-#if GPU_ALLOW_PROFILE_EVENTS
+#if GPU_ALLOW_PROFILE_EVENTS && VK_EXT_debug_utils
// End remaining events
while (_eventsBegin--)
vkCmdEndDebugUtilsLabelEXT(GetHandle());
@@ -89,6 +89,7 @@ void CmdBufferVulkan::AcquirePoolSet()
void CmdBufferVulkan::BeginEvent(const Char* name)
{
+#if VK_EXT_debug_utils
if (!vkCmdBeginDebugUtilsLabelEXT)
return;
@@ -108,15 +109,18 @@ void CmdBufferVulkan::BeginEvent(const Char* name)
RenderToolsVulkan::ZeroStruct(label, VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT);
label.pLabelName = buffer;
vkCmdBeginDebugUtilsLabelEXT(GetHandle(), &label);
+#endif
}
void CmdBufferVulkan::EndEvent()
{
+#if VK_EXT_debug_utils
if (_eventsBegin == 0 || !vkCmdEndDebugUtilsLabelEXT)
return;
_eventsBegin--;
vkCmdEndDebugUtilsLabelEXT(GetHandle());
+#endif
}
#endif
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp
index 1cd90b11b..2acd2712c 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.cpp
@@ -17,14 +17,17 @@ void GPUBufferViewVulkan::Init(GPUDeviceVulkan* device, GPUBufferVulkan* owner,
Buffer = buffer;
Size = size;
- VkBufferViewCreateInfo viewInfo;
- RenderToolsVulkan::ZeroStruct(viewInfo, VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO);
- viewInfo.buffer = Buffer;
- viewInfo.format = RenderToolsVulkan::ToVulkanFormat(format);
- viewInfo.offset = 0;
- viewInfo.range = Size;
-
- VALIDATE_VULKAN_RESULT(vkCreateBufferView(device->Device, &viewInfo, nullptr, &View));
+ if (owner->IsShaderResource() && !(owner->GetDescription().Flags & GPUBufferFlags::Structured))
+ {
+ VkBufferViewCreateInfo viewInfo;
+ RenderToolsVulkan::ZeroStruct(viewInfo, VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO);
+ viewInfo.buffer = Buffer;
+ viewInfo.format = RenderToolsVulkan::ToVulkanFormat(format);
+ viewInfo.offset = 0;
+ viewInfo.range = Size;
+ ASSERT_LOW_LAYER(viewInfo.format != VK_FORMAT_UNDEFINED);
+ VALIDATE_VULKAN_RESULT(vkCreateBufferView(device->Device, &viewInfo, nullptr, &View));
+ }
}
void GPUBufferViewVulkan::Release()
@@ -32,19 +35,18 @@ void GPUBufferViewVulkan::Release()
if (View != VK_NULL_HANDLE)
{
Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::BufferView, View);
-
View = VK_NULL_HANDLE;
-
-#if BUILD_DEBUG
- Device = nullptr;
- Owner = nullptr;
- Buffer = VK_NULL_HANDLE;
-#endif
}
+#if BUILD_DEBUG
+ Device = nullptr;
+ Owner = nullptr;
+ Buffer = VK_NULL_HANDLE;
+#endif
}
void GPUBufferViewVulkan::DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView)
{
+ ASSERT_LOW_LAYER(View != VK_NULL_HANDLE);
bufferView = &View;
context->AddBufferBarrier(Owner, VK_ACCESS_SHADER_READ_BIT);
@@ -52,6 +54,7 @@ void GPUBufferViewVulkan::DescriptorAsUniformTexelBuffer(GPUContextVulkan* conte
void GPUBufferViewVulkan::DescriptorAsStorageBuffer(GPUContextVulkan* context, VkBuffer& buffer, VkDeviceSize& offset, VkDeviceSize& range)
{
+ ASSERT_LOW_LAYER(Buffer);
buffer = Buffer;
offset = 0;
range = Size;
@@ -88,7 +91,7 @@ bool GPUBufferVulkan::OnInit()
bufferInfo.size = _desc.Size;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
- if (useSRV)
+ if (useSRV && !(_desc.Flags & GPUBufferFlags::Structured))
bufferInfo.usage |= VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT;
if (useUAV || _desc.Flags & GPUBufferFlags::RawBuffer)
bufferInfo.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
@@ -165,7 +168,7 @@ bool GPUBufferVulkan::OnInit()
}
}
// Check if need to bind buffer to the shaders
- else if (useSRV)
+ else if (useSRV || useUAV)
{
// Create buffer view
_view.Init(_device, this, _buffer, _desc.Size, _desc.Format);
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
index aa169e8f5..a0b64a3bb 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
@@ -27,6 +27,7 @@
#include "Engine/Core/Utilities.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Engine/Engine.h"
+#include "Engine/Engine/Globals.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Utilities/StringConverter.h"
#include "Engine/Profiler/ProfilerCPU.h"
@@ -52,6 +53,8 @@ VkDebugReportCallbackEXT MsgCallback = VK_NULL_HANDLE;
extern VulkanValidationLevel ValidationLevel;
+#if VK_EXT_debug_report
+
static VKAPI_ATTR VkBool32 VKAPI_PTR DebugReportFunction(VkDebugReportFlagsEXT msgFlags, VkDebugReportObjectTypeEXT objType, uint64_t srcObject, size_t location, int32 msgCode, const char* layerPrefix, const char* msg, void* userData)
{
const Char* msgPrefix = TEXT("UNKNOWN");
@@ -126,6 +129,8 @@ static VKAPI_ATTR VkBool32 VKAPI_PTR DebugReportFunction(VkDebugReportFlagsEXT m
return VK_FALSE;
}
+#endif
+
#if VK_EXT_debug_utils
static VKAPI_ATTR VkBool32 VKAPI_PTR DebugUtilsCallback(VkDebugUtilsMessageSeverityFlagBitsEXT msgSeverity, VkDebugUtilsMessageTypeFlagsEXT msgType, const VkDebugUtilsMessengerCallbackDataEXT* callbackData, void* userData)
@@ -140,8 +145,8 @@ static VKAPI_ATTR VkBool32 VKAPI_PTR DebugUtilsCallback(VkDebugUtilsMessageSever
case 2: // Fragment shader writes to output location 0 with no matching attachment
case 3: // Attachment 2 not written by fragment shader
case 5: // SPIR-V module not valid: MemoryBarrier: Vulkan specification requires Memory Semantics to have one of the following bits set: Acquire, Release, AcquireRelease or SequentiallyConsistent
-#if PLATFORM_ANDROID
case -1666394502: // After query pool creation, each query must be reset before it is used. Queries must also be reset between uses.
+#if PLATFORM_ANDROID
case 602160055: // Attachment 4 not written by fragment shader; undefined values will be written to attachment. TODO: investigate it for PS_GBuffer shader from Deferred material with USE_LIGHTMAP=1
#endif
return VK_FALSE;
@@ -270,6 +275,7 @@ void SetupDebugLayerCallback()
if (SupportsDebugCallbackExt)
#endif
{
+#if VK_EXT_debug_report
if (vkCreateDebugReportCallbackEXT)
{
VkDebugReportCallbackCreateInfoEXT createInfo;
@@ -303,6 +309,7 @@ void SetupDebugLayerCallback()
{
LOG(Warning, "GetProcAddr: Unable to find vkDbgCreateMsgCallback; debug reporting skipped!");
}
+#endif
}
else
{
@@ -324,8 +331,10 @@ void RemoveDebugLayerCallback()
if (MsgCallback != VK_NULL_HANDLE)
#endif
{
+#if VK_EXT_debug_report
if (vkDestroyDebugReportCallbackEXT)
vkDestroyDebugReportCallbackEXT(GPUDeviceVulkan::Instance, MsgCallback, nullptr);
+#endif
MsgCallback = VK_NULL_HANDLE;
}
}
@@ -344,7 +353,7 @@ DeferredDeletionQueueVulkan::~DeferredDeletionQueueVulkan()
void DeferredDeletionQueueVulkan::ReleaseResources(bool deleteImmediately)
{
- ScopeLock lock(&_locker);
+ ScopeLock lock(_locker);
const uint64 checkFrame = Engine::FrameCount - VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT;
for (int32 i = 0; i < _entries.Count(); i++)
{
@@ -1032,7 +1041,7 @@ void StagingManagerVulkan::Dispose()
ScopeLock lock(_locker);
#if !BUILD_RELEASE
- LOG(Info, "Vulakn staging buffers peek memory usage: {0}, allocs: {1}, frees: {2}", Utilities::BytesToText(_allBuffersPeekSize), Utilities::BytesToText(_allBuffersAllocSize), Utilities::BytesToText(_allBuffersFreeSize));
+ LOG(Info, "Vulkan staging buffers peek memory usage: {0}, allocs: {1}, frees: {2}", Utilities::BytesToText(_allBuffersPeekSize), Utilities::BytesToText(_allBuffersAllocSize), Utilities::BytesToText(_allBuffersFreeSize));
#endif
// Release buffers and clear memory
@@ -1085,13 +1094,17 @@ GPUDevice* GPUDeviceVulkan::Create()
}
#endif
+ VkResult result;
+
+#if !PLATFORM_SWITCH
// Initialize bindings
- VkResult result = volkInitialize();
+ result = volkInitialize();
if (result != VK_SUCCESS)
{
LOG(Warning, "Graphics Device init failed with error {0}", RenderToolsVulkan::GetVkErrorString(result));
return nullptr;
}
+#endif
// Engine registration
const StringAsANSI<256> appName(*Globals::ProductName);
@@ -1170,10 +1183,12 @@ GPUDevice* GPUDeviceVulkan::Create()
return nullptr;
}
+#if !PLATFORM_SWITCH
// Setup bindings
volkLoadInstance(Instance);
+#endif
- // Setup debug layer
+// Setup debug layer
#if VULKAN_USE_DEBUG_LAYER
SetupDebugLayerCallback();
#endif
@@ -1658,8 +1673,10 @@ bool GPUDeviceVulkan::Init()
// Create the device
VALIDATE_VULKAN_RESULT(vkCreateDevice(gpu, &deviceInfo, nullptr, &Device));
+#if !PLATFORM_SWITCH
// Optimize bindings
volkLoadDevice(Device);
+#endif
// Create queues
if (graphicsQueueFamilyIndex == -1)
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs b/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs
index 68eb5ffcc..4779beb4f 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs
+++ b/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs
@@ -108,7 +108,15 @@ public class GraphicsDeviceVulkan : GraphicsDeviceBaseModule
options.PublicDefinitions.Add("GRAPHICS_API_VULKAN");
- options.PrivateDependencies.Add("volk");
options.PrivateDependencies.Add("VulkanMemoryAllocator");
+
+ if (options.Platform.Target == TargetPlatform.Switch)
+ {
+ options.SourcePaths.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Switch", "Engine", "GraphicsDevice", "Vulkan"));
+ }
+ else
+ {
+ options.PrivateDependencies.Add("volk");
+ }
}
}
diff --git a/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h b/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h
index 3e00a9e87..fec251eca 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h
@@ -8,6 +8,19 @@
#include "Engine/Core/Math/Math.h"
+#if PLATFORM_SWITCH
+
+#define VK_USE_PLATFORM_VI_NN 1
+#include
+#undef VK_EXT_debug_utils
+#undef VK_EXT_debug_report
+#undef VK_EXT_validation_cache
+#define VMA_DEDICATED_ALLOCATION 0
+#pragma clang diagnostic ignored "-Wpointer-bool-conversion"
+#pragma clang diagnostic ignored "-Wtautological-pointer-compare"
+
+#else
+
#if PLATFORM_WIN32
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
#endif
@@ -17,6 +30,8 @@
// License: MIT
#include
+#endif
+
// Use Vulkan Memory Allocator for buffer and image memory allocations
// Source: https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator/
// License: MIT
diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp
index e974396a4..d24c5742d 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp
@@ -241,7 +241,9 @@ String RenderToolsVulkan::GetVkErrorString(VkResult result)
VKERR(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR);
VKERR(VK_ERROR_VALIDATION_FAILED_EXT);
VKERR(VK_ERROR_INVALID_SHADER_NV);
+#if VK_HEADER_VERSION >= 89
VKERR(VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT);
+#endif
VKERR(VK_ERROR_FRAGMENTATION_EXT);
VKERR(VK_ERROR_NOT_PERMITTED_EXT);
#if VK_HEADER_VERSION < 140
diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h
index cf9b7e9cf..ec5d882bc 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h
@@ -78,6 +78,9 @@ public:
case 0:
stageFlags = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
break;
+ case VK_ACCESS_INDIRECT_COMMAND_READ_BIT:
+ stageFlags = VK_PIPELINE_STAGE_TRANSFER_BIT;
+ break;
case VK_ACCESS_TRANSFER_WRITE_BIT:
stageFlags = VK_PIPELINE_STAGE_TRANSFER_BIT;
break;
diff --git a/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatform.h
index 8e585c78e..bc216190b 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatform.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatform.h
@@ -8,4 +8,6 @@
#include "Linux/LinuxVulkanPlatform.h"
#elif PLATFORM_ANDROID
#include "Android/AndroidVulkanPlatform.h"
+#elif PLATFORM_SWITCH
+#include "Platforms/Switch/Engine/GraphicsDevice/Vulkan/SwitchVulkanPlatform.h"
#endif
diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp
index 485d5a891..d7796691b 100644
--- a/Source/Engine/Input/Input.cpp
+++ b/Source/Engine/Input/Input.cpp
@@ -339,14 +339,14 @@ bool Input::GetKeyUp(const KeyboardKeys key)
Vector2 Input::GetMousePosition()
{
- return Mouse ? Engine::ScreenToGameViewport(Mouse->GetPosition()) : Vector2::Minimum;
+ return Mouse ? Screen::ScreenToGameViewport(Mouse->GetPosition()) : Vector2::Minimum;
}
void Input::SetMousePosition(const Vector2& position)
{
if (Mouse && Engine::HasGameViewportFocus())
{
- const auto pos = Engine::GameViewportToScreen(position);
+ const auto pos = Screen::GameViewportToScreen(position);
if (pos > Vector2::Minimum)
Mouse->SetMousePosition(pos);
}
diff --git a/Source/Engine/Input/InputDevice.h b/Source/Engine/Input/InputDevice.h
index 81b4eceb7..27f6125ba 100644
--- a/Source/Engine/Input/InputDevice.h
+++ b/Source/Engine/Input/InputDevice.h
@@ -113,7 +113,7 @@ public:
/// Captures the input since the last call and triggers the input events.
///
/// The input events queue.
- /// True if device has been disconnected, otherwise false.
+ /// True if device has been disconnected, otherwise false.
virtual bool Update(EventQueue& queue)
{
if (UpdateState())
@@ -126,7 +126,7 @@ public:
///
/// Updates only the current state of the device.
///
- /// True if device has been disconnected, otherwise false.
+ /// True if device has been disconnected, otherwise false.
virtual bool UpdateState()
{
return false;
diff --git a/Source/Engine/Input/InputSettings.h b/Source/Engine/Input/InputSettings.h
index 7771028e8..fd594f3ee 100644
--- a/Source/Engine/Input/InputSettings.h
+++ b/Source/Engine/Input/InputSettings.h
@@ -5,6 +5,7 @@
#include "Engine/Core/Config/Settings.h"
#include "Engine/Serialization/JsonTools.h"
#include "VirtualInput.h"
+#include "Engine/Core/Collections/Array.h"
///
/// Input settings container.
diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp
index 3753f6140..20ec4cb7a 100644
--- a/Source/Engine/Level/Actor.cpp
+++ b/Source/Engine/Level/Actor.cpp
@@ -15,7 +15,9 @@
#include "Engine/Core/Cache.h"
#include "Engine/Core/Collections/CollectionPoolCache.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
+#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderView.h"
+#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Serialization/ISerializeModifier.h"
#include "Engine/Serialization/Serialization.h"
@@ -203,6 +205,13 @@ void Actor::SetParent(Actor* value, bool worldPositionsStays, bool canBreakPrefa
LOG(Error, "Editing scene hierarchy is only allowed on a main thread.");
return;
}
+#if USE_EDITOR || !BUILD_RELEASE
+ if (Is())
+ {
+ LOG(Error, "Cannot change parent of the Scene. Use Level to manage scenes.");
+ return;
+ }
+#endif
// Peek the previous state
const Transform prevTransform = _transform;
@@ -340,6 +349,12 @@ void Actor::SetOrderInParent(int32 index)
}
}
+Actor* Actor::GetChild(int32 index) const
+{
+ CHECK_RETURN(index >= 0 && index < Children.Count(), nullptr);
+ return Children[index];
+}
+
Actor* Actor::GetChild(const StringView& name) const
{
for (int32 i = 0; i < Children.Count(); i++)
@@ -347,7 +362,6 @@ Actor* Actor::GetChild(const StringView& name) const
if (Children[i]->GetName() == name)
return Children[i];
}
-
return nullptr;
}
@@ -475,6 +489,12 @@ void Actor::SetName(const StringView& value)
Level::callActorEvent(Level::ActorEventType::OnActorNameChanged, this, nullptr);
}
+Script* Actor::GetScript(int32 index) const
+{
+ CHECK_RETURN(index >= 0 && index < Scripts.Count(), nullptr);
+ return Scripts[index];
+}
+
Script* Actor::GetScript(const MClass* type) const
{
CHECK_RETURN(type, nullptr);
@@ -945,28 +965,34 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
DESERIALIZE_MEMBER(Name, _name);
DESERIALIZE_MEMBER(Transform, _localTransform);
- Guid parentId = Guid::Empty;
- DESERIALIZE_MEMBER(ParentID, parentId);
- const auto parent = Scripting::FindObject(parentId);
- if (_parent != parent)
{
- if (IsDuringPlay())
+ const auto member = SERIALIZE_FIND_MEMBER(stream, "ParentID");
+ if (member != stream.MemberEnd())
{
- SetParent(parent, false, false);
+ Guid parentId;
+ Serialization::Deserialize(member->value, parentId, modifier);
+ const auto parent = Scripting::FindObject(parentId);
+ if (_parent != parent)
+ {
+ if (IsDuringPlay())
+ {
+ SetParent(parent, false, false);
+ }
+ else
+ {
+ if (_parent)
+ _parent->Children.RemoveKeepOrder(this);
+ _parent = parent;
+ if (_parent)
+ _parent->Children.Add(this);
+ OnParentChanged();
+ }
+ }
+ else if (!parent && parentId.IsValid())
+ {
+ LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString());
+ }
}
- else
- {
- if (_parent)
- _parent->Children.RemoveKeepOrder(this);
- _parent = parent;
- if (_parent)
- _parent->Children.Add(this);
- OnParentChanged();
- }
- }
- else if (!parent && parentId.IsValid())
- {
- LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString());
}
// StaticFlags update - added StaticFlags::Navigation
@@ -1133,20 +1159,28 @@ BoundingBox Actor::GetBoxWithChildren() const
#if USE_EDITOR
+BoundingBox Actor::GetEditorBox() const
+{
+ return GetBox();
+}
+
BoundingBox Actor::GetEditorBoxChildren() const
{
BoundingBox result = GetEditorBox();
-
for (int32 i = 0; i < Children.Count(); i++)
{
BoundingBox::Merge(result, Children[i]->GetEditorBoxChildren(), result);
}
-
return result;
}
#endif
+bool Actor::HasContentLoaded() const
+{
+ return true;
+}
+
void Actor::UnregisterObjectHierarchy()
{
if (IsRegistered())
@@ -1164,6 +1198,10 @@ void Actor::UnregisterObjectHierarchy()
}
}
+void Actor::Draw(RenderContext& renderContext)
+{
+}
+
void Actor::DrawGeneric(RenderContext& renderContext)
{
// Generic drawing uses only GBuffer Fill Pass and simple frustum culling (see SceneRendering for more optimized drawing)
@@ -1210,12 +1248,6 @@ void Actor::OnDebugDraw()
for (auto* script : Scripts)
if (script->GetEnabled())
script->OnDebugDraw();
-
- for (auto& child : Children)
- {
- if (child->GetIsActive())
- child->OnDebugDraw();
- }
}
void Actor::OnDebugDrawSelected()
@@ -1458,6 +1490,7 @@ void WriteObjectToBytes(SceneObject* obj, rapidjson_flax::StringBuffer& buffer,
bool Actor::ToBytes(const Array& actors, MemoryWriteStream& output)
{
+ PROFILE_CPU();
if (actors.IsEmpty())
{
// Cannot serialize empty list
@@ -1517,6 +1550,7 @@ Array Actor::ToBytes(const Array& actors)
bool Actor::FromBytes(const Span& data, Array& output, ISerializeModifier* modifier)
{
+ PROFILE_CPU();
output.Clear();
ASSERT(modifier);
@@ -1561,7 +1595,7 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM
// Order in parent
int32 orderInParent;
stream.ReadInt32(&orderInParent);
- order.At(i) = orderInParent;
+ order[i] = orderInParent;
// Load JSON
rapidjson_flax::Document document;
@@ -1601,7 +1635,6 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM
// Order in parent
int32 orderInParent;
stream.ReadInt32(&orderInParent);
- order.Add(orderInParent);
// Load JSON
rapidjson_flax::Document document;
@@ -1619,17 +1652,19 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM
Scripting::ObjectsLookupIdMapping.Set(nullptr);
// Link objects
- for (int32 i = 0; i < objectsCount; i++)
+ //for (int32 i = 0; i < objectsCount; i++)
{
- SceneObject* obj = sceneObjects->At(i);
- obj->PostLoad();
+ //SceneObject* obj = sceneObjects->At(i);
+ // TODO: post load or post spawn?
+ //obj->PostLoad();
}
// Update objects order
- for (int32 i = 0; i < objectsCount; i++)
+ //for (int32 i = 0; i < objectsCount; i++)
{
- SceneObject* obj = sceneObjects->At(i);
- obj->SetOrderInParent(order[i]);
+ //SceneObject* obj = sceneObjects->At(i);
+ // TODO: remove order from saved data?
+ //obj->SetOrderInParent(order[i]);
}
// Call events (only for parents because they will propagate events down the tree)
@@ -1646,6 +1681,10 @@ bool Actor::FromBytes(const Span& data, Array& output, ISerializeM
}
}
for (int32 i = 0; i < parents->Count(); i++)
+ {
+ parents->At(i)->PostSpawn();
+ }
+ for (int32 i = 0; i < parents->Count(); i++)
{
Actor* actor = parents->At(i);
actor->OnTransformChanged();
@@ -1687,6 +1726,7 @@ Array Actor::FromBytes(const Span& data, const Dictionary Actor::TryGetSerializedObjectsIds(const Span& data)
{
+ PROFILE_CPU();
Array result;
if (data.Length() > 0)
{
@@ -1707,6 +1747,7 @@ Array Actor::TryGetSerializedObjectsIds(const Span& data)
String Actor::ToJson()
{
+ PROFILE_CPU();
rapidjson_flax::StringBuffer buffer;
CompactJsonWriter writer(buffer);
writer.SceneObject(this);
@@ -1718,6 +1759,8 @@ String Actor::ToJson()
void Actor::FromJson(const StringAnsiView& json)
{
+ PROFILE_CPU();
+
// Load JSON
rapidjson_flax::Document document;
document.Parse(json.Get(), json.Length());
diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs
index 2d9c27b9a..fce009549 100644
--- a/Source/Engine/Level/Actor.cs
+++ b/Source/Engine/Level/Actor.cs
@@ -124,7 +124,7 @@ namespace FlaxEngine
public Actor AddChild(Type type)
{
var result = (Actor)New(type);
- result.SetParent(this, false);
+ result.SetParent(this, false, false);
return result;
}
@@ -136,7 +136,7 @@ namespace FlaxEngine
public T AddChild() where T : Actor
{
var result = New();
- result.SetParent(this, false);
+ result.SetParent(this, false, false);
return result;
}
@@ -173,7 +173,7 @@ namespace FlaxEngine
if (result == null)
{
result = New();
- result.SetParent(this, false);
+ result.SetParent(this, false, false);
}
return result;
}
diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h
index 69d4c8681..00aeb17ab 100644
--- a/Source/Engine/Level/Actor.h
+++ b/Source/Engine/Level/Actor.h
@@ -176,7 +176,7 @@ public:
/// New parent
/// Should actor world positions remain the same after parent change?
/// True if can break prefab link on changing the parent.
- void SetParent(Actor* value, bool worldPositionsStays, bool canBreakPrefabLink);
+ API_FUNCTION() void SetParent(Actor* value, bool worldPositionsStays, bool canBreakPrefabLink);
///
/// Gets amount of child actors.
@@ -192,10 +192,7 @@ public:
///
/// The child actor index.
/// The child actor (always valid).
- API_FUNCTION() FORCE_INLINE Actor* GetChild(int32 index) const
- {
- return Children[index];
- }
+ API_FUNCTION() Actor* GetChild(int32 index) const;
///
/// Gets the child actor with the given name.
@@ -266,10 +263,7 @@ public:
///
/// The script index.
/// The script (always valid).
- API_FUNCTION() FORCE_INLINE Script* GetScript(int32 index) const
- {
- return Scripts[index];
- }
+ API_FUNCTION() Script* GetScript(int32 index) const;
///
/// Gets the script of the given type from this actor.
@@ -629,10 +623,7 @@ public:
///
/// Gets actor bounding box (single actor, no children included) for editor tools.
///
- API_PROPERTY() virtual BoundingBox GetEditorBox() const
- {
- return GetBox();
- }
+ API_PROPERTY() virtual BoundingBox GetEditorBox() const;
///
/// Gets actor bounding box of the actor including all child actors for editor tools.
@@ -644,10 +635,7 @@ public:
///
/// Returns true if actor has loaded content.
///
- API_PROPERTY() virtual bool HasContentLoaded() const
- {
- return true;
- }
+ API_PROPERTY() virtual bool HasContentLoaded() const;
///
/// Calls UnregisterObject for all objects in the actor hierarchy.
@@ -660,9 +648,7 @@ public:
/// Draws this actor. Called by Scene Rendering service. This call is more optimized than generic Draw (eg. models are rendered during all passed but other actors are invoked only during GBufferFill pass).
///
/// The rendering context.
- virtual void Draw(RenderContext& renderContext)
- {
- }
+ virtual void Draw(RenderContext& renderContext);
///
/// Draws this actor. Called during custom actor rendering or any other generic rendering from code.
@@ -679,12 +665,12 @@ public:
#if USE_EDITOR
///
- /// Draws debug shapes for the actor and all child actors.
+ /// Draws debug shapes for the actor and all child scripts.
///
virtual void OnDebugDraw();
///
- /// Draws debug shapes for the selected actor and all child actors.
+ /// Draws debug shapes for the selected actor and all child scripts.
///
virtual void OnDebugDrawSelected();
diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp
index 85ee6d333..a62aad592 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.cpp
+++ b/Source/Engine/Level/Actors/AnimatedModel.cpp
@@ -8,10 +8,13 @@
#include "Editor/Editor.h"
#endif
#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Graphics/RenderTask.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Level/SceneObjectsFactory.h"
#include "Engine/Serialization/Serialization.h"
+extern Array UpdateBones;
+
AnimatedModel::AnimatedModel(const SpawnParams& params)
: ModelInstanceActor(params)
, _actualMode(AnimationUpdateMode::Never)
@@ -41,7 +44,8 @@ void AnimatedModel::UpdateAnimation()
|| !IsActiveInHierarchy()
|| SkinnedModel == nullptr
|| !SkinnedModel->IsLoaded()
- || _lastUpdateFrame == Engine::FrameCount)
+ || _lastUpdateFrame == Engine::FrameCount
+ || _masterPose)
return;
_lastUpdateFrame = Engine::FrameCount;
@@ -131,6 +135,22 @@ void AnimatedModel::GetCurrentPose(Array& nodesTransformation, bool worl
}
}
+void AnimatedModel::SetCurrentPose(const Array& nodesTransformation, bool worldSpace)
+{
+ if (GraphInstance.NodesPose.Count() == 0)
+ return;
+ CHECK(nodesTransformation.Count() == GraphInstance.NodesPose.Count());
+ GraphInstance.NodesPose = nodesTransformation;
+ if (worldSpace)
+ {
+ Matrix invWorld;
+ Matrix::Invert(_world, invWorld);
+ for (auto& m : GraphInstance.NodesPose)
+ m = invWorld * m;
+ }
+ OnAnimationUpdated();
+}
+
void AnimatedModel::GetNodeTransformation(int32 nodeIndex, Matrix& nodeTransformation, bool worldSpace) const
{
if (nodeIndex >= 0 && nodeIndex < GraphInstance.NodesPose.Count())
@@ -146,6 +166,35 @@ void AnimatedModel::GetNodeTransformation(const StringView& nodeName, Matrix& no
GetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace);
}
+int32 AnimatedModel::FindClosestNode(const Vector3& location, bool worldSpace) const
+{
+ const Vector3 pos = worldSpace ? _transform.WorldToLocal(location) : location;
+ int32 result = -1;
+ float closest = MAX_float;
+ for (int32 nodeIndex = 0; nodeIndex < GraphInstance.NodesPose.Count(); nodeIndex++)
+ {
+ const Vector3 node = GraphInstance.NodesPose[nodeIndex].GetTranslation();
+ const float dst = Vector3::DistanceSquared(node, pos);
+ if (dst < closest)
+ {
+ closest = dst;
+ result = nodeIndex;
+ }
+ }
+ return result;
+}
+
+void AnimatedModel::SetMasterPoseModel(AnimatedModel* masterPose)
+{
+ if (masterPose == _masterPose)
+ return;
+ if (_masterPose)
+ _masterPose->AnimationUpdated.Unbind(this);
+ _masterPose = masterPose;
+ if (_masterPose)
+ _masterPose->AnimationUpdated.Bind(this);
+}
+
#define CHECK_ANIM_GRAPH_PARAM_ACCESS() \
if (!AnimationGraph) \
{ \
@@ -233,6 +282,8 @@ void AnimatedModel::SetParameterValue(const Guid& id, const Variant& value)
LOG(Warning, "Failed to set animated model '{0}' missing parameter '{1}'", ToString(), id.ToString());
}
+#undef CHECK_ANIM_GRAPH_PARAM_ACCESS
+
float AnimatedModel::GetBlendShapeWeight(const StringView& name)
{
for (auto& e : _blendShapes.Weights)
@@ -339,6 +390,7 @@ void AnimatedModel::BeginPlay(SceneBeginData* data)
void AnimatedModel::EndPlay()
{
AnimationManager::RemoveFromUpdate(this);
+ SetMasterPoseModel(nullptr);
// Base
ModelInstanceActor::EndPlay();
@@ -383,7 +435,7 @@ void AnimatedModel::UpdateLocalBounds()
}
else
{
- box = BoundingBox(Vector3::Zero, Vector3::Zero);
+ box = BoundingBox(Vector3::Zero);
}
// Scale bounds
@@ -401,12 +453,38 @@ void AnimatedModel::UpdateBounds()
BoundingSphere::FromBox(_box, _sphere);
}
-void AnimatedModel::OnAnimUpdate()
+void AnimatedModel::OnAnimationUpdated()
{
+ ANIM_GRAPH_PROFILE_EVENT("OnAnimationUpdated");
+ auto& skeleton = SkinnedModel->Skeleton;
+
+ // Copy pose from the master
+ if (_masterPose && _masterPose->SkinnedModel->Skeleton.Nodes.Count() == skeleton.Nodes.Count())
+ {
+ ANIM_GRAPH_PROFILE_EVENT("Copy Master Pose");
+ const auto& masterInstance = _masterPose->GraphInstance;
+ GraphInstance.NodesPose = masterInstance.NodesPose;
+ GraphInstance.RootTransform = masterInstance.RootTransform;
+ GraphInstance.RootMotion = masterInstance.RootMotion;
+ }
+
+ // Calculate the final bones transformations and update skinning
+ {
+ ANIM_GRAPH_PROFILE_EVENT("Final Pose");
+ UpdateBones.Resize(skeleton.Bones.Count(), false);
+ for (int32 boneIndex = 0; boneIndex < skeleton.Bones.Count(); boneIndex++)
+ {
+ auto& bone = skeleton.Bones[boneIndex];
+ UpdateBones[boneIndex] = bone.OffsetMatrix * GraphInstance.NodesPose[bone.NodeIndex];
+ }
+ }
+ _skinningData.SetData(UpdateBones.Get(), !PerBoneMotionBlur);
+
UpdateBounds();
UpdateSockets();
ApplyRootMotion(GraphInstance.RootMotion);
_blendShapes.Update(SkinnedModel.Get());
+ AnimationUpdated();
}
void AnimatedModel::OnSkinnedModelChanged()
diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h
index d5d9b4424..df218b897 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.h
+++ b/Source/Engine/Level/Actors/AnimatedModel.h
@@ -6,6 +6,8 @@
#include "Engine/Content/Assets/SkinnedModel.h"
#include "Engine/Content/Assets/AnimationGraph.h"
#include "Engine/Graphics/Models/SkinnedMeshDrawData.h"
+#include "Engine/Renderer/DrawCall.h"
+#include "Engine/Core/Delegate.h"
///
/// Performs an animation and renders a skinned model.
@@ -63,6 +65,7 @@ private:
float _lastMinDstSqr;
uint64 _lastUpdateFrame;
BlendShapesInstance _blendShapes;
+ ScriptingObjectReference _masterPose;
public:
@@ -165,9 +168,14 @@ public:
API_FUNCTION() void ResetAnimation();
///
- /// Performs the full animation update.
+ /// Performs the full animation update. The actual update will be performed during gameplay tick.
///
API_FUNCTION() void UpdateAnimation();
+
+ ///
+ /// Called after animation gets updated (new skeleton pose).
+ ///
+ API_EVENT() Action AnimationUpdated;
///
/// Validates and creates a proper skinning data.
@@ -180,17 +188,19 @@ public:
API_FUNCTION() void PreInitSkinningData();
///
- /// Updates the child bone socket actors.
- ///
- API_FUNCTION() void UpdateSockets();
-
- ///
- /// Gets the per-node final transformations.
+ /// Gets the per-node final transformations (skeleton pose).
///
/// The output per-node final transformation matrices.
/// True if convert matrices into world-space, otherwise returned values will be in local-space of the actor.
API_FUNCTION() void GetCurrentPose(API_PARAM(Out) Array& nodesTransformation, bool worldSpace = false) const;
+ ///
+ /// Sets the per-node final transformations (skeleton pose).
+ ///
+ /// The per-node final transformation matrices.
+ /// True if convert matrices from world-space, otherwise values are in local-space of the actor.
+ API_FUNCTION() void SetCurrentPose(const Array& nodesTransformation, bool worldSpace = false);
+
///
/// Gets the node final transformation.
///
@@ -207,6 +217,20 @@ public:
/// True if convert matrices into world-space, otherwise returned values will be in local-space of the actor.
API_FUNCTION() void GetNodeTransformation(const StringView& nodeName, API_PARAM(Out) Matrix& nodeTransformation, bool worldSpace = false) const;
+ ///
+ /// Finds the closest node to a given location.
+ ///
+ /// The text location (in local-space of the actor or world-space depending on ).
+ /// True if convert input location is in world-space, otherwise it's in local-space of the actor.
+ /// The zero-based index of the found node. Returns -1 if skeleton is missing.
+ API_FUNCTION() int32 FindClosestNode(const Vector3& location, bool worldSpace = false) const;
+
+ ///
+ /// Sets the master pose model that will be used to copy the skeleton nodes animation. Useful for modular characters.
+ ///
+ /// The master pose actor to use.
+ API_FUNCTION() void SetMasterPoseModel(AnimatedModel* masterPose);
+
public:
///
@@ -275,27 +299,14 @@ public:
private:
- ///
- /// Applies the root motion delta to the target actor.
- ///
void ApplyRootMotion(const RootMotionData& rootMotionDelta);
-
- ///
- /// Synchronizes the parameters collection (may lost existing params data on collection change detected).
- ///
void SyncParameters();
- ///
- /// Updates the local bounds of the actor.
- ///
+ void Update();
void UpdateLocalBounds();
-
void UpdateBounds();
-
- ///
- /// Called after animation graph update.
- ///
- void OnAnimUpdate();
+ void UpdateSockets();
+ void OnAnimationUpdated();
void OnSkinnedModelChanged();
void OnSkinnedModelLoaded();
@@ -303,8 +314,6 @@ private:
void OnGraphChanged();
void OnGraphLoaded();
- void Update();
-
public:
// [ModelInstanceActor]
diff --git a/Source/Engine/Level/Actors/BoneSocket.cpp b/Source/Engine/Level/Actors/BoneSocket.cpp
index 028b1c507..5efe8deed 100644
--- a/Source/Engine/Level/Actors/BoneSocket.cpp
+++ b/Source/Engine/Level/Actors/BoneSocket.cpp
@@ -96,7 +96,7 @@ void BoneSocket::OnTransformChanged()
// Base
Actor::OnTransformChanged();
- _box = BoundingBox(_transform.Translation, _transform.Translation);
+ _box = BoundingBox(_transform.Translation);
_sphere = BoundingSphere(_transform.Translation, 0.0f);
}
diff --git a/Source/Engine/Level/Actors/BoxBrush.cpp b/Source/Engine/Level/Actors/BoxBrush.cpp
index f02dd5bd3..4330424c7 100644
--- a/Source/Engine/Level/Actors/BoxBrush.cpp
+++ b/Source/Engine/Level/Actors/BoxBrush.cpp
@@ -128,6 +128,13 @@ void BoxBrush::GetSurfaces(CSG::Surface surfaces[6])
}
}
+void BoxBrush::SetMaterial(int32 surfaceIndex, MaterialBase* material)
+{
+ CHECK(Math::IsInRange(surfaceIndex, 0, 5));
+ Surfaces[surfaceIndex].Material = material;
+ OnBrushModified();
+}
+
bool BoxBrush::Intersects(int32 surfaceIndex, const Ray& ray, float& distance, Vector3& normal) const
{
distance = MAX_float;
@@ -232,6 +239,26 @@ void BoxBrush::OnDebugDrawSelected()
#endif
+Scene* BoxBrush::GetBrushScene() const
+{
+ return GetScene();
+}
+
+Guid BoxBrush::GetBrushID() const
+{
+ return GetID();
+}
+
+bool BoxBrush::CanUseCSG() const
+{
+ return IsActiveInHierarchy();
+}
+
+CSG::Mode BoxBrush::GetBrushMode() const
+{
+ return _mode;
+}
+
void BoxBrush::GetSurfaces(Array& surfaces)
{
surfaces.Clear();
@@ -240,6 +267,11 @@ void BoxBrush::GetSurfaces(Array& surfaces)
GetSurfaces(surfaces.Get());
}
+int32 BoxBrush::GetSurfacesCount()
+{
+ return 6;
+}
+
void BoxBrush::OnTransformChanged()
{
// Base
@@ -272,7 +304,7 @@ void BoxBrush::OnParentChanged()
{
// Base
Actor::OnParentChanged();
-
+
if (!IsDuringPlay())
return;
diff --git a/Source/Engine/Level/Actors/BoxBrush.h b/Source/Engine/Level/Actors/BoxBrush.h
index e8fc63e3f..ae9be5b83 100644
--- a/Source/Engine/Level/Actors/BoxBrush.h
+++ b/Source/Engine/Level/Actors/BoxBrush.h
@@ -153,6 +153,13 @@ public:
/// Surfaces
void GetSurfaces(CSG::Surface surfaces[6]);
+ ///
+ /// Sets the brush surface material.
+ ///
+ /// The brush surface index.
+ /// The material.
+ API_FUNCTION() void SetMaterial(int32 surfaceIndex, MaterialBase* material);
+
public:
///
@@ -169,7 +176,7 @@ public:
/// Otherwise performs simple vs test.
/// For more efficient collisions detection and ray casting use physics.
///
- /// The brush surface index..
+ /// The brush surface index.
/// The ray to test.
/// When the method completes and returns true, contains the distance of the intersection (if any valid).
/// When the method completes, contains the intersection surface normal vector (if any valid).
@@ -179,7 +186,7 @@ public:
///
/// Gets the brush surface triangles array (group by 3 vertices).
///
- /// The brush surface index..
+ /// The brush surface index.
/// The output vertices buffer with triangles or empty if no data loaded.
API_FUNCTION() void GetVertices(int32 surfaceIndex, API_PARAM(Out) Array& outputData) const;
@@ -204,32 +211,12 @@ public:
#endif
// [CSG::Brush]
- Scene* GetBrushScene() const override
- {
- return GetScene();
- }
-
- Guid GetBrushID() const override
- {
- return GetID();
- }
-
- bool CanUseCSG() const override
- {
- return IsActiveInHierarchy();
- }
-
- CSG::Mode GetBrushMode() const override
- {
- return _mode;
- }
-
+ Scene* GetBrushScene() const override;
+ Guid GetBrushID() const override;
+ bool CanUseCSG() const override;
+ CSG::Mode GetBrushMode() const override;
void GetSurfaces(Array& surfaces) override;
-
- int32 GetSurfacesCount() override
- {
- return 6;
- }
+ int32 GetSurfacesCount() override;
protected:
diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp
index 95eebefc1..1cc7ba53b 100644
--- a/Source/Engine/Level/Actors/Camera.cpp
+++ b/Source/Engine/Level/Actors/Camera.cpp
@@ -5,24 +5,27 @@
#include "Engine/Core/Math/Viewport.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Content/Content.h"
-#include "Engine/Platform/Window.h"
#include "Engine/Serialization/Serialization.h"
-#include "Engine/Level/Scene/SceneRendering.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h"
+#include "Engine/Renderer/DrawCall.h"
+#include "Engine/Graphics/RenderTask.h"
+#include "Engine/Level/Scene/SceneRendering.h"
#else
#include "Engine/Engine/Engine.h"
+#include "Engine/Platform/Window.h"
#endif
Array Camera::Cameras;
Camera* Camera::CutSceneCamera = nullptr;
-Camera* Camera::OverrideMainCamera = nullptr;
+ScriptingObjectReference Camera::OverrideMainCamera;
Camera* Camera::GetMainCamera()
{
- if (OverrideMainCamera)
- return OverrideMainCamera;
+ Camera* overrideMainCamera = OverrideMainCamera.Get();
+ if (overrideMainCamera)
+ return overrideMainCamera;
if (CutSceneCamera)
return CutSceneCamera;
return Cameras.HasItems() ? Cameras.First() : nullptr;
@@ -101,12 +104,12 @@ void Camera::SetOrthographicScale(float value)
}
}
-void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& screenSpaceLocation) const
+void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& gameWindowSpaceLocation) const
{
- ProjectPoint(worldSpaceLocation, screenSpaceLocation, GetViewport());
+ ProjectPoint(worldSpaceLocation, gameWindowSpaceLocation, GetViewport());
}
-void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& screenSpaceLocation, const Viewport& viewport) const
+void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& cameraViewportSpaceLocation, const Viewport& viewport) const
{
Matrix v, p, vp;
GetMatrices(v, p, viewport);
@@ -114,7 +117,7 @@ void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Vector2& screenSpac
Vector3 clipSpaceLocation;
Vector3::Transform(worldSpaceLocation, vp, clipSpaceLocation);
viewport.Project(worldSpaceLocation, vp, clipSpaceLocation);
- screenSpaceLocation = Vector2(clipSpaceLocation);
+ cameraViewportSpaceLocation = Vector2(clipSpaceLocation);
}
Ray Camera::ConvertMouseToRay(const Vector2& mousePosition) const
diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h
index 7f40617dd..505479e98 100644
--- a/Source/Engine/Level/Actors/Camera.h
+++ b/Source/Engine/Level/Actors/Camera.h
@@ -8,6 +8,7 @@
#include "Engine/Core/Math/Viewport.h"
#include "Engine/Core/Math/Ray.h"
#include "Engine/Core/Types/LayersMask.h"
+#include "Engine/Scripting/ScriptingObjectReference.h"
#if USE_EDITOR
#include "Engine/Content/AssetReference.h"
#include "Engine/Graphics/Models/ModelInstanceEntry.h"
@@ -28,7 +29,7 @@ DECLARE_SCENE_OBJECT(Camera);
static Camera* CutSceneCamera;
// The overriden main camera.
- API_FIELD() static Camera* OverrideMainCamera;
+ API_FIELD() static ScriptingObjectReference OverrideMainCamera;
// Gets the main camera.
API_PROPERTY() static Camera* GetMainCamera();
@@ -174,19 +175,19 @@ public:
public:
///
- /// Projects the point from 3D world-space to the camera screen-space (in screen pixels for default viewport calculated from ).
+ /// Projects the point from 3D world-space to game window coordinates (in screen pixels for default viewport calculated from ).
///
/// The input world-space location (XYZ in world).
- /// The output screen-space location (XY in screen pixels).
- API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& screenSpaceLocation) const;
+ /// The output game window coordinates (XY in screen pixels).
+ API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& gameWindowSpaceLocation) const;
///
- /// Projects the point from 3D world-space to the camera screen-space (in screen pixels for given viewport).
+ /// Projects the point from 3D world-space to the camera viewport-space (in screen pixels for given viewport).
///
/// The input world-space location (XYZ in world).
- /// The output screen-space location (XY in screen pixels).
+ /// The output camera viewport-space location (XY in screen pixels).
/// The viewport.
- API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& screenSpaceLocation, API_PARAM(Ref) const Viewport& viewport) const;
+ API_FUNCTION() void ProjectPoint(const Vector3& worldSpaceLocation, API_PARAM(Out) Vector2& cameraViewportSpaceLocation, API_PARAM(Ref) const Viewport& viewport) const;
///
/// Converts the mouse position to 3D ray.
diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp
index 240344fc6..8810b0de6 100644
--- a/Source/Engine/Level/Actors/DirectionalLight.cpp
+++ b/Source/Engine/Level/Actors/DirectionalLight.cpp
@@ -92,6 +92,6 @@ void DirectionalLight::OnTransformChanged()
// Base
LightWithShadow::OnTransformChanged();
- _box = BoundingBox(_transform.Translation, _transform.Translation);
+ _box = BoundingBox(_transform.Translation);
_sphere = BoundingSphere(_transform.Translation, 0.0f);
}
diff --git a/Source/Engine/Level/Actors/EmptyActor.cpp b/Source/Engine/Level/Actors/EmptyActor.cpp
index 309f63652..dfb64824d 100644
--- a/Source/Engine/Level/Actors/EmptyActor.cpp
+++ b/Source/Engine/Level/Actors/EmptyActor.cpp
@@ -22,6 +22,6 @@ void EmptyActor::OnTransformChanged()
// Base
Actor::OnTransformChanged();
- _box = BoundingBox(_transform.Translation, _transform.Translation);
+ _box = BoundingBox(_transform.Translation);
_sphere = BoundingSphere(_transform.Translation, 0.0f);
}
diff --git a/Source/Engine/Level/Actors/EnvironmentProbe.cpp b/Source/Engine/Level/Actors/EnvironmentProbe.cpp
index 9a2bb08a8..d5939d722 100644
--- a/Source/Engine/Level/Actors/EnvironmentProbe.cpp
+++ b/Source/Engine/Level/Actors/EnvironmentProbe.cpp
@@ -3,6 +3,7 @@
#include "EnvironmentProbe.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Graphics/RenderView.h"
+#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Renderer/ProbesRenderer.h"
diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp
index 77f8e16e7..366ab7de4 100644
--- a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp
+++ b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp
@@ -228,6 +228,6 @@ void ExponentialHeightFog::OnTransformChanged()
// Base
Actor::OnTransformChanged();
- _box = BoundingBox(_transform.Translation, _transform.Translation);
+ _box = BoundingBox(_transform.Translation);
_sphere = BoundingSphere(_transform.Translation, 0.0f);
}
diff --git a/Source/Engine/Level/Actors/Light.cpp b/Source/Engine/Level/Actors/Light.cpp
index 56e3368b0..9f80216c5 100644
--- a/Source/Engine/Level/Actors/Light.cpp
+++ b/Source/Engine/Level/Actors/Light.cpp
@@ -2,6 +2,9 @@
#include "Light.h"
#include "../Scene/Scene.h"
+#if USE_EDITOR
+#include "Engine/Graphics/RenderView.h"
+#endif
#include "Engine/Serialization/Serialization.h"
Light::Light(const SpawnParams& params)
diff --git a/Source/Engine/Level/Actors/Sky.cpp b/Source/Engine/Level/Actors/Sky.cpp
index 8aaf5fdd3..1e35c27f0 100644
--- a/Source/Engine/Level/Actors/Sky.cpp
+++ b/Source/Engine/Level/Actors/Sky.cpp
@@ -4,14 +4,17 @@
#include "DirectionalLight.h"
#include "Engine/Core/Math/Color.h"
#include "Engine/Content/Content.h"
-#include "Engine/Graphics/RenderView.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Renderer/AtmospherePreCompute.h"
#include "Engine/Renderer/GBufferPass.h"
#include "Engine/Graphics/RenderBuffers.h"
+#include "Engine/Graphics/RenderView.h"
+#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/GPUContext.h"
-#include "Engine/Serialization/Serialization.h"
+#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Shaders/GPUConstantBuffer.h"
+#include "Engine/Graphics/Shaders/GPUShader.h"
+#include "Engine/Serialization/Serialization.h"
#include "Engine/Level/Scene/SceneRendering.h"
PACK_STRUCT(struct Data {
@@ -259,6 +262,6 @@ void Sky::OnTransformChanged()
// Base
Actor::OnTransformChanged();
- _box = BoundingBox(_transform.Translation, _transform.Translation);
+ _box = BoundingBox(_transform.Translation);
_sphere = BoundingSphere(_transform.Translation, 0.0f);
}
diff --git a/Source/Engine/Level/Actors/Sky.h b/Source/Engine/Level/Actors/Sky.h
index c5c81edc0..eafd8d9e6 100644
--- a/Source/Engine/Level/Actors/Sky.h
+++ b/Source/Engine/Level/Actors/Sky.h
@@ -9,6 +9,8 @@
#include "Engine/Renderer/Config.h"
#include "Engine/Renderer/DrawCall.h"
+class GPUPipelineState;
+
///
/// Sky actor renders atmosphere around the scene with fog and sky.
///
diff --git a/Source/Engine/Level/Actors/SkyLight.cpp b/Source/Engine/Level/Actors/SkyLight.cpp
index 03e1d3ca5..da7efbf31 100644
--- a/Source/Engine/Level/Actors/SkyLight.cpp
+++ b/Source/Engine/Level/Actors/SkyLight.cpp
@@ -3,6 +3,7 @@
#include "SkyLight.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Graphics/RenderView.h"
+#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Renderer/ProbesRenderer.h"
diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp
index e8ea08c89..4edd596b7 100644
--- a/Source/Engine/Level/Actors/Skybox.cpp
+++ b/Source/Engine/Level/Actors/Skybox.cpp
@@ -146,6 +146,6 @@ void Skybox::OnTransformChanged()
// Base
Actor::OnTransformChanged();
- _box = BoundingBox(_transform.Translation, _transform.Translation);
+ _box = BoundingBox(_transform.Translation);
_sphere = BoundingSphere(_transform.Translation, 0.0f);
}
diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp
index 76ecb9653..4c0d21cd7 100644
--- a/Source/Engine/Level/Actors/Spline.cpp
+++ b/Source/Engine/Level/Actors/Spline.cpp
@@ -3,10 +3,12 @@
#include "Spline.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Animations/CurveSerialization.h"
+#include "Engine/Core/Math/Matrix.h"
#include
Spline::Spline(const SpawnParams& params)
: Actor(params)
+ , _localBounds(Vector3::Zero, Vector3::Zero)
{
}
@@ -411,17 +413,27 @@ void Spline::SetTangentsSmooth()
void Spline::UpdateSpline()
{
+ auto& keyframes = Curve.GetKeyframes();
+ const int32 count = keyframes.Count();
+
// Always keep last point in the loop
- const int32 count = Curve.GetKeyframes().Count();
if (_loop && count > 1)
{
- auto& first = Curve[0];
- auto& last = Curve[count - 1];
+ auto& first = keyframes[0];
+ auto& last = keyframes[count - 1];
last.Value = first.Value;
last.TangentIn = first.TangentIn;
last.TangentOut = first.TangentOut;
}
+ // Update bounds
+ _localBounds = BoundingBox(count != 0 ? keyframes[0].Value.Translation : Vector3::Zero);
+ for (int32 i = 1; i < count; i++)
+ _localBounds.Merge(keyframes[i].Value.Translation);
+ Matrix world;
+ _transform.GetWorld(world);
+ BoundingBox::Transform(_localBounds, world, _box);
+
SplineUpdated();
}
@@ -485,6 +497,34 @@ void Spline::OnDebugDrawSelected()
#endif
+void Spline::OnTransformChanged()
+{
+ // Base
+ Actor::OnTransformChanged();
+
+ Matrix world;
+ _transform.GetWorld(world);
+ BoundingBox::Transform(_localBounds, world, _box);
+ BoundingSphere::FromBox(_box, _sphere);
+}
+
+void Spline::PostLoad()
+{
+ // Base
+ Actor::PostLoad();
+
+ auto& keyframes = Curve.GetKeyframes();
+ const int32 count = keyframes.Count();
+
+ // Update bounds
+ _localBounds = BoundingBox(count != 0 ? keyframes[0].Value.Translation : Vector3::Zero);
+ for (int32 i = 1; i < count; i++)
+ _localBounds.Merge(keyframes[i].Value.Translation);
+ Matrix world;
+ _transform.GetWorld(world);
+ BoundingBox::Transform(_localBounds, world, _box);
+}
+
void Spline::Serialize(SerializeStream& stream, const void* otherObj)
{
// Base
diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h
index f19f9a443..79fce685d 100644
--- a/Source/Engine/Level/Actors/Spline.h
+++ b/Source/Engine/Level/Actors/Spline.h
@@ -15,6 +15,7 @@ DECLARE_SCENE_OBJECT(Spline);
private:
bool _loop = false;
+ BoundingBox _localBounds;
public:
@@ -377,6 +378,8 @@ public:
void OnDebugDraw() override;
void OnDebugDrawSelected() override;
#endif
+ void OnTransformChanged() override;
+ void PostLoad() override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
};
diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp
index 84306716b..3e874f07f 100644
--- a/Source/Engine/Level/Actors/SplineModel.cpp
+++ b/Source/Engine/Level/Actors/SplineModel.cpp
@@ -5,9 +5,14 @@
#include "Engine/Engine/Engine.h"
#include "Engine/Core/Math/Matrix3x4.h"
#include "Engine/Serialization/Serialization.h"
+#include "Engine/Graphics/GPUBufferDescription.h"
#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Graphics/GPUBuffer.h"
+#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Profiler/ProfilerCPU.h"
+#include "Engine/Renderer/DrawCall.h"
+#include "Engine/Renderer/RenderList.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#endif
@@ -110,7 +115,7 @@ void SplineModel::OnSplineUpdated()
// Skip updates when actor is disabled or something is missing
if (!_spline || !Model || !Model->IsLoaded() || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2)
{
- _box = BoundingBox(_transform.Translation, _transform.Translation);
+ _box = BoundingBox(_transform.Translation);
BoundingSphere::FromBox(_box, _sphere);
return;
}
@@ -138,11 +143,10 @@ void SplineModel::OnSplineUpdated()
Vector3 tmp = corners[i] * _preTransform.Scale;
double rotation[4] = { (double)_preTransform.Orientation.X, (double)_preTransform.Orientation.Y, (double)_preTransform.Orientation.Z, (double)_preTransform.Orientation.W };
const double length = sqrt(rotation[0] * rotation[0] + rotation[1] * rotation[1] + rotation[2] * rotation[2] + rotation[3] * rotation[3]);
- const double inv = 1.0 / length;
- rotation[0] *= inv;
- rotation[1] *= inv;
- rotation[2] *= inv;
- rotation[3] *= inv;
+ rotation[0] /= length;
+ rotation[1] /= length;
+ rotation[2] /= length;
+ rotation[3] /= length;
double pos[3] = { (double)tmp.X, (double)tmp.Y, (double)tmp.Z };
const double x = rotation[0] + rotation[0];
const double y = rotation[1] + rotation[1];
@@ -251,7 +255,7 @@ void SplineModel::UpdateDeformationBuffer()
AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent);
for (int32 chunk = 0; chunk < chunksPerSegment; chunk++)
{
- const float alpha = (float)chunk * chunksPerSegmentInv;
+ const float alpha = (chunk == chunksPerSegment - 1)? 1.0f : ((float)chunk * chunksPerSegmentInv);
// Evaluate transformation at the curve
AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform);
diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp
index a884c9707..0005e74f9 100644
--- a/Source/Engine/Level/Actors/StaticModel.cpp
+++ b/Source/Engine/Level/Actors/StaticModel.cpp
@@ -2,7 +2,10 @@
#include "StaticModel.h"
#include "Engine/Engine/Engine.h"
+#include "Engine/Graphics/GPUBuffer.h"
+#include "Engine/Graphics/GPUBufferDescription.h"
#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Graphics/RenderTask.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Level/Prefabs/PrefabManager.h"
#include "Engine/Level/Scene/Scene.h"
@@ -187,7 +190,7 @@ void StaticModel::UpdateBounds()
}
else
{
- _box = BoundingBox(_transform.Translation, _transform.Translation);
+ _box = BoundingBox(_transform.Translation);
}
BoundingSphere::FromBox(_box, _sphere);
}
diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h
index a255ba4ab..b3276cc83 100644
--- a/Source/Engine/Level/Actors/StaticModel.h
+++ b/Source/Engine/Level/Actors/StaticModel.h
@@ -4,6 +4,7 @@
#include "ModelInstanceActor.h"
#include "Engine/Content/Assets/Model.h"
+#include "Engine/Renderer/DrawCall.h"
#include "Engine/Renderer/Lightmaps.h"
///
diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp
index 049e9ebf3..efc73dc63 100644
--- a/Source/Engine/Level/Level.cpp
+++ b/Source/Engine/Level/Level.cpp
@@ -10,6 +10,7 @@
#include "Engine/Core/Collections/CollectionPoolCache.h"
#include "Engine/Core/ObjectsRemovalService.h"
#include "Engine/Core/Config/LayersTagsSettings.h"
+#include "Engine/Core/Types/LayersMask.h"
#include "Engine/Debug/Exceptions/ArgumentException.h"
#include "Engine/Debug/Exceptions/ArgumentNullException.h"
#include "Engine/Debug/Exceptions/InvalidOperationException.h"
@@ -202,7 +203,7 @@ void LayersAndTagsSettings::Apply()
void LevelService::Update()
{
- PROFILE_CPU();
+ PROFILE_CPU_NAMED("Level::Update");
ScopeLock lock(Level::ScenesLock);
auto& scenes = Level::Scenes;
@@ -231,7 +232,7 @@ void LevelService::Update()
void LevelService::LateUpdate()
{
- PROFILE_CPU();
+ PROFILE_CPU_NAMED("Level::LateUpdate");
ScopeLock lock(Level::ScenesLock);
auto& scenes = Level::Scenes;
@@ -263,7 +264,7 @@ void LevelService::LateUpdate()
void LevelService::FixedUpdate()
{
- PROFILE_CPU();
+ PROFILE_CPU_NAMED("Level::FixedUpdate");
ScopeLock lock(Level::ScenesLock);
auto& scenes = Level::Scenes;
@@ -496,14 +497,8 @@ public:
// - load scenes (from temporary files)
// Note: we don't want to override original scene files
- // If no scene loaded just reload scripting
- if (!Level::IsAnySceneLoaded())
- {
- // Reload scripting
- LOG(Info, "No scenes loaded, performing fast scripts reload");
- Scripting::Reload(false);
- return false;
- }
+ LOG(Info, "Scripts reloading start");
+ const auto startTime = DateTime::NowUTC();
// Cache data
struct SceneData
@@ -538,8 +533,6 @@ public:
scenes[i].Init(Level::Scenes[i]);
// Fire event
- LOG(Info, "Scripts reloading start");
- const auto startTime = DateTime::NowUTC();
Level::ScriptsReloadStart();
// Save scenes (to memory)
@@ -566,9 +559,10 @@ public:
Scripting::Reload();
// Restore scenes (from memory)
- LOG(Info, "Loading temporary scenes");
for (int32 i = 0; i < scenesCount; i++)
{
+ LOG(Info, "Restoring scene {0}", scenes[i].Name);
+
// Parse json
const auto& sceneData = scenes[i].Data;
ISerializable::SerializeDocument document;
@@ -590,8 +584,9 @@ public:
scenes.Resize(0);
// Initialize scenes (will link references and create managed objects using new assembly)
- LOG(Info, "Prepare scene objects");
+ if (Level::Scenes.HasItems())
{
+ LOG(Info, "Prepare scene objects");
SceneBeginData beginData;
for (int32 i = 0; i < Level::Scenes.Count(); i++)
{
@@ -601,8 +596,7 @@ public:
}
// Fire event
- const auto endTime = DateTime::NowUTC();
- LOG(Info, "Scripts reloading end. Total time: {0}ms", static_cast((endTime - startTime).GetTotalMilliseconds()));
+ LOG(Info, "Scripts reloading end. Total time: {0}ms", static_cast((DateTime::NowUTC() - startTime).GetTotalMilliseconds()));
Level::ScriptsReloadEnd();
return false;
@@ -649,7 +643,7 @@ public:
void LevelImpl::CallSceneEvent(SceneEventType eventType, Scene* scene, Guid sceneId)
{
- PROFILE_CPU();
+ PROFILE_CPU_NAMED("Level::CallSceneEvent");
// Call event
const auto scriptsDomain = Scripting::GetScriptsDomain();
@@ -1037,9 +1031,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, bool autoI
// Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it)
// TODO: resave and force sync scenes during game cooking so this step could be skipped in game
- Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
SceneObjectsFactory::SynchronizePrefabInstances(*sceneObjects.Value, actorToRemovedObjectsData, modifier.Value);
- Scripting::ObjectsLookupIdMapping.Set(nullptr);
// Delete objects without parent
for (int32 i = 1; i < objectsCount; i++)
diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h
index 9fe9cb39b..1f7f0bb98 100644
--- a/Source/Engine/Level/Level.h
+++ b/Source/Engine/Level/Level.h
@@ -5,6 +5,7 @@
#include "Engine/Core/Delegate.h"
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Types/DataContainer.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Platform/CriticalSection.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Serialization/JsonFwd.h"
diff --git a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp
index c0029aa93..abad0c275 100644
--- a/Source/Engine/Level/Prefabs/Prefab.Apply.cpp
+++ b/Source/Engine/Level/Prefabs/Prefab.Apply.cpp
@@ -6,6 +6,7 @@
#include "Engine/Core/ObjectsRemovalService.h"
#include "Engine/Core/Cache.h"
+#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/Script.h"
#include "Engine/Serialization/Json.h"
@@ -358,27 +359,47 @@ bool PrefabInstanceData::SynchronizePrefabInstances(Array& p
{
obj->Deserialize(instance.Data[dataIndex], modifier.Value);
- // Send events because some properties may be modified during prefab changes apply
- // TODO: maybe send only valid events (need to track changes for before-after state)
- Actor* actor = dynamic_cast(obj);
- if (actor && actor->IsDuringPlay())
+ // Preserve order in parent (values from prefab are used)
+ if (i != 0)
{
- Level::callActorEvent(Level::ActorEventType::OnActorNameChanged, actor, nullptr);
- Level::callActorEvent(Level::ActorEventType::OnActorActiveChanged, actor, nullptr);
- Level::callActorEvent(Level::ActorEventType::OnActorOrderInParentChanged, actor, nullptr);
+ auto prefab = Content::Load(prefabId);
+ const auto defaultInstance = prefab ? prefab->GetDefaultInstance(obj->GetPrefabObjectID()) : nullptr;
+ if (defaultInstance)
+ {
+ obj->SetOrderInParent(defaultInstance->GetOrderInParent());
+ }
}
}
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
- // Setup objects after deserialization
+ // Setup new objects after deserialization
for (int32 i = existingObjectsCount; i < sceneObjects->Count(); i++)
{
SceneObject* obj = sceneObjects.Value->At(i);
obj->PostLoad();
}
+ // Synchronize existing objects logic with deserialized state (fire events)
+ for (int32 i = 0; i < existingObjectsCount; i++)
+ {
+ SceneObject* obj = sceneObjects->At(i);
+ Actor* actor = dynamic_cast(obj);
+ if (actor)
+ {
+ const bool shouldBeActiveInHierarchy = actor->GetIsActive() && (!actor->GetParent() || actor->GetParent()->IsActiveInHierarchy());
+ if (shouldBeActiveInHierarchy != actor->IsActiveInHierarchy())
+ {
+ actor->_isActiveInHierarchy = shouldBeActiveInHierarchy;
+ actor->OnActiveInTreeChanged();
+ Level::callActorEvent(Level::ActorEventType::OnActorActiveChanged, actor, nullptr);
+ }
+ Level::callActorEvent(Level::ActorEventType::OnActorNameChanged, actor, nullptr);
+ Level::callActorEvent(Level::ActorEventType::OnActorOrderInParentChanged, actor, nullptr);
+ }
+ }
+
// Restore order in parent
instance.TargetActor->SetOrderInParent(instance.OrderInParent);
@@ -784,6 +805,20 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
obj->Deserialize(diffDataDocument[dataIndex], modifier.Value);
sceneObjects->Add(obj);
+
+ // Synchronize order of the scene objects with the serialized data (eg. user reordered actors in prefab editor and applied changes)
+ if (i != 0)
+ {
+ for (int32 j = 0; j < targetObjects->Count(); j++)
+ {
+ SceneObject* targetObject = targetObjects->At(j);
+ if (targetObject->GetPrefabObjectID() == obj->GetID())
+ {
+ obj->SetOrderInParent(targetObject->GetOrderInParent());
+ break;
+ }
+ }
+ }
}
else
{
@@ -800,7 +835,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
sceneObjects->RemoveAtKeepOrder(i);
}
- // Deserialize new prefab objects (add new objects)
+ // Deserialize new prefab objects
int32 newPrefabInstanceIdToDataIndexCounter = 0;
int32 newPrefabInstanceIdToDataIndexStart = sceneObjects->Count();
sceneObjects->Resize(sceneObjects->Count() + newPrefabInstanceIdToDataIndex.Count());
@@ -822,9 +857,27 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
{
const int32 dataIndex = i->Value;
SceneObject* obj = sceneObjects->At(newPrefabInstanceIdToDataIndexStart + newPrefabInstanceIdToDataIndexCounter++);
- if (obj)
+ if (!obj)
+ continue;
+ SceneObjectsFactory::Deserialize(obj, diffDataDocument[dataIndex], modifier.Value);
+ }
+ for (int32 j = 0; j < targetObjects->Count(); j++)
+ {
+ auto obj = targetObjects->At(j);
+ Guid prefabObjectId;
+ if (newPrefabInstanceIdToPrefabObjectId.TryGet(obj->GetSceneObjectId(), prefabObjectId))
{
- SceneObjectsFactory::Deserialize(obj, diffDataDocument[dataIndex], modifier.Value);
+ newPrefabInstanceIdToDataIndexCounter = 0;
+ for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i)
+ {
+ SceneObject* e = sceneObjects->At(newPrefabInstanceIdToDataIndexStart + newPrefabInstanceIdToDataIndexCounter++);
+ if (e->GetID() == prefabObjectId)
+ {
+ // Synchronize order of new objects with the order in target instance
+ e->SetOrderInParent(obj->GetOrderInParent());
+ break;
+ }
+ }
}
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
@@ -861,7 +914,10 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
auto obj = sceneObjects.Value->At(i);
- obj->PostLoad();
+ if (obj)
+ {
+ obj->PostLoad();
+ }
}
// Update transformations
diff --git a/Source/Engine/Level/Prefabs/Prefab.cpp b/Source/Engine/Level/Prefabs/Prefab.cpp
index ed4ed2a2b..53bbb9d14 100644
--- a/Source/Engine/Level/Prefabs/Prefab.cpp
+++ b/Source/Engine/Level/Prefabs/Prefab.cpp
@@ -11,7 +11,7 @@
#include "Engine/Scripting/Scripting.h"
#endif
-REGISTER_JSON_ASSET(Prefab, "FlaxEngine.Prefab");
+REGISTER_JSON_ASSET(Prefab, "FlaxEngine.Prefab", false);
Prefab::Prefab(const SpawnParams& params, const AssetInfo* info)
: JsonAssetBase(params, info)
diff --git a/Source/Engine/Level/Prefabs/Prefab.h b/Source/Engine/Level/Prefabs/Prefab.h
index f5c25d273..694b50cdd 100644
--- a/Source/Engine/Level/Prefabs/Prefab.h
+++ b/Source/Engine/Level/Prefabs/Prefab.h
@@ -3,6 +3,7 @@
#pragma once
#include "Engine/Content/JsonAsset.h"
+#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/Dictionary.h"
class Actor;
@@ -15,7 +16,6 @@ class SceneObject;
API_CLASS(NoSpawn) class FLAXENGINE_API Prefab : public JsonAssetBase
{
DECLARE_ASSET_HEADER(Prefab);
-
private:
bool _isCreatingDefaultInstance;
diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp
index 512d6e9b7..77698e739 100644
--- a/Source/Engine/Level/Prefabs/PrefabManager.cpp
+++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp
@@ -16,6 +16,7 @@
#include "Engine/Core/Cache.h"
#include "Engine/Debug/Exceptions/ArgumentException.h"
#include "Engine/Engine/EngineService.h"
+#include "Engine/Scripting/Script.h"
#include "Engine/Scripting/Scripting.h"
#if USE_EDITOR
@@ -216,21 +217,57 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, DictionaryIdsMapping);
SceneObjectsFactory::SynchronizePrefabInstances(*sceneObjects.Value, actorToRemovedObjectsData, modifier.Value);
- Scripting::ObjectsLookupIdMapping.Set(nullptr);
}
- // Delete objects without parent
+ // Delete objects without parent or with invalid linkage to the prefab
for (int32 i = 1; i < sceneObjects->Count(); i++)
{
SceneObject* obj = sceneObjects->At(i);
- if (obj && obj->GetParent() == nullptr)
+ if (!obj)
+ continue;
+
+ // Check for missing parent (eg. parent object has been deleted)
+ if (obj->GetParent() == nullptr)
{
sceneObjects->At(i) = nullptr;
LOG(Warning, "Scene object {0} {1} has missing parent object after load. Removing it.", obj->GetID(), obj->ToString());
obj->DeleteObject();
+ continue;
}
+
+#if USE_EDITOR && !BUILD_RELEASE
+ // Check for not being added to the parent (eg. invalid setup events fault on registration)
+ auto actor = dynamic_cast(obj);
+ auto script = dynamic_cast