diff --git a/.github/workflows/build_android.yml b/.github/workflows/build_android.yml
index 568b4f35e..b0d4633a8 100644
--- a/.github/workflows/build_android.yml
+++ b/.github/workflows/build_android.yml
@@ -33,4 +33,4 @@ jobs:
git lfs pull
- name: Build
run: |
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -arch=ARM64 -platform=Android -configuration=Release -buildtargets=FlaxGame
+ .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=7 -arch=ARM64 -platform=Android -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/build_ios.yml b/.github/workflows/build_ios.yml
index f4b5d8147..2aec46320 100644
--- a/.github/workflows/build_ios.yml
+++ b/.github/workflows/build_ios.yml
@@ -33,4 +33,4 @@ jobs:
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Mac/CallBuildTool.sh -build -log -arch=ARM64 -platform=iOS -configuration=Release -buildtargets=FlaxGame
+ ./Development/Scripts/Mac/CallBuildTool.sh -build -log -dotnet=7 -arch=ARM64 -platform=iOS -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml
index bd1726737..56accba84 100644
--- a/.github/workflows/build_linux.yml
+++ b/.github/workflows/build_linux.yml
@@ -36,7 +36,7 @@ jobs:
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxEditor
+ ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxEditor
# Game
game-linux:
@@ -64,4 +64,4 @@ jobs:
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -arch=x64 -platform=Linux -configuration=Release -buildtargets=FlaxGame
+ ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Linux -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/build_mac.yml b/.github/workflows/build_mac.yml
index 88cb9b7a8..54bdb77b5 100644
--- a/.github/workflows/build_mac.yml
+++ b/.github/workflows/build_mac.yml
@@ -30,7 +30,7 @@ jobs:
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -arch=x64 -platform=Mac -configuration=Development -buildtargets=FlaxEditor
+ ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Mac -configuration=Development -buildtargets=FlaxEditor
# Game
game-mac:
@@ -55,4 +55,4 @@ jobs:
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -arch=x64 -platform=Mac -configuration=Release -buildtargets=FlaxGame
+ ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Mac -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml
index 513cbfd08..b6131fb53 100644
--- a/.github/workflows/build_windows.yml
+++ b/.github/workflows/build_windows.yml
@@ -30,7 +30,7 @@ jobs:
git lfs pull
- name: Build
run: |
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor
+ .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor
# Game
game-windows:
@@ -55,4 +55,4 @@ jobs:
git lfs pull
- name: Build
run: |
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -arch=x64 -platform=Windows -configuration=Release -buildtargets=FlaxGame
+ .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Windows -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index f9d18bcfc..a524bdca2 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -34,8 +34,8 @@ jobs:
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: |
- ./GenerateProjectFiles.sh -vs2022 -log -verbose -printSDKs
- ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget
+ ./GenerateProjectFiles.sh -vs2022 -log -verbose -printSDKs -dotnet=7
+ ./Development/Scripts/Linux/CallBuildTool.sh -build -log -dotnet=7 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget
dotnet msbuild Source/Tools/Flax.Build.Tests/Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo
dotnet msbuild Source/Tools/Flax.Build.Tests/Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo
- name: Test
@@ -48,7 +48,7 @@ jobs:
dotnet test -f net7.0 Binaries/Tests/FlaxEngine.CSharp.dll
- name: Test UseLargeWorlds
run: |
- ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget -UseLargeWorlds=true
+ ./Development/Scripts/Linux/CallBuildTool.sh -build -log -dotnet=7 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget -UseLargeWorlds=true
${GITHUB_WORKSPACE}/Binaries/Editor/Linux/Development/FlaxTests
# Tests on Windows
@@ -72,8 +72,8 @@ jobs:
git lfs pull
- name: Build
run: |
- .\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget
+ .\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs -dotnet=7
+ .\Development\Scripts\Windows\CallBuildTool.bat -build -log -dotnet=7 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget
dotnet msbuild Source\Tools\Flax.Build.Tests\Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo
- name: Test
run: |
diff --git a/.gitignore b/.gitignore
index 54907892f..b7e11e554 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ Source/*.csproj
/Package_*/
!Source/Engine/Debug
/Source/Platforms/Editor/Linux/Mono/etc/mono/registry
+PackageEditor_Cert.command
PackageEditor_Cert.bat
PackagePlatforms_Cert.bat
diff --git a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax
index 8e810f7d4..cc4a16fe3 100644
--- a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax
+++ b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:57066ba805fd3f21a1d48048c7d3a0ec4e4c66c5cdd1ca97553605987f43b460
-size 41028
+oid sha256:5d0dc041c6b8712c2f892ac3185c1f9e0b3608b774db5590bcaad3ad0775dc93
+size 41042
diff --git a/Content/Editor/DebugMaterials/SingleColor/Particle.flax b/Content/Editor/DebugMaterials/SingleColor/Particle.flax
index 72e956e7a..e22b6c1fa 100644
--- a/Content/Editor/DebugMaterials/SingleColor/Particle.flax
+++ b/Content/Editor/DebugMaterials/SingleColor/Particle.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e737c911cffc1e1ad5cad3d7d4e13e24cdba7d08da4b488bf7bb41f098cb1638
-size 31713
+oid sha256:a54c7442f97baa1ab835485f906911372847808e2865790109b0524614663210
+size 31722
diff --git a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax
index 2fffdd0eb..790690cd8 100644
--- a/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax
+++ b/Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7933e14f937b148d6b0c4a7fff10aa9b931f01e1bfe42ce3e5ff4575fbeeb463
-size 31873
+oid sha256:4cd7963263033f8f6e09de86da1ce14aa9baee3f1811986d56441cc62164e231
+size 31882
diff --git a/Content/Editor/Gizmo/FoliageBrushMaterial.flax b/Content/Editor/Gizmo/FoliageBrushMaterial.flax
index 8eb64e89d..f1fa6e3f5 100644
--- a/Content/Editor/Gizmo/FoliageBrushMaterial.flax
+++ b/Content/Editor/Gizmo/FoliageBrushMaterial.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:67e5aba4c1eeb15231aef4001c7b0bb25db4243b871b1493774b1d1348310b22
-size 37868
+oid sha256:f87fe1172cc96b6aaef5653b9d7c5ef336b7f5934c620ae13661fd89b636cfcc
+size 37909
diff --git a/Content/Editor/Particles/Constant Burst.flax b/Content/Editor/Particles/Constant Burst.flax
index 16ca82be3..bb90199fe 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:5f6a7d30653808828c9f3ece113eb55684a330253366ff34de953085de4f9766
-size 2671
+oid sha256:fc1e46708152005dc92532e940b3ce19ec527b595fb18365b41ebdcd338ad2c4
+size 2705
diff --git a/Content/Editor/Particles/Periodic Burst.flax b/Content/Editor/Particles/Periodic Burst.flax
index 80a4c306d..784d456c5 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:eeedc5055dc5a8218fd229a7a433ede79ce036eaf07326a9cee5f988b9b67a06
-size 3621
+oid sha256:94bf88983e3cf9fe555141a867af3c20e5bd58d13a527a9b4e02d4d1be157be9
+size 3664
diff --git a/Content/Editor/Particles/Smoke.flax b/Content/Editor/Particles/Smoke.flax
index 1335a84f4..a682db6ee 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:c3dc51e7805056006ca6cbb481ba202583a9b2287c152fc04e28e1d07747d6ce
-size 14706
+oid sha256:dc72b3152b85137a22b7dc72209ece02e8c2fff6674ddd395eaa4a4b089a51da
+size 14662
diff --git a/Content/Editor/Particles/Sparks.flax b/Content/Editor/Particles/Sparks.flax
index 7977e231b..227d9e381 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:77d902ab5f79426cc66dc5f19a3b8280136a58aa3c6fd317554d1a032357c65a
-size 15275
+oid sha256:d38b4ed6a68e0c327e7d6dda2f47c6665611c4ae9c7f8b1ba6148eb26abb205f
+size 13650
diff --git a/Content/Editor/Scripting/ScriptTemplate.cs b/Content/Editor/Scripting/ScriptTemplate.cs
index 663acf05f..30fbe9d86 100644
--- a/Content/Editor/Scripting/ScriptTemplate.cs
+++ b/Content/Editor/Scripting/ScriptTemplate.cs
@@ -33,4 +33,3 @@ public class %class% : Script
// Here you can add code that needs to be called every frame
}
}
-
diff --git a/Content/Editor/Terrain/Circle Brush Material.flax b/Content/Editor/Terrain/Circle Brush Material.flax
index b014ffb1f..9b21e6c82 100644
--- a/Content/Editor/Terrain/Circle Brush Material.flax
+++ b/Content/Editor/Terrain/Circle Brush Material.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1e73a50fe4296e58eff5942855dd655ba4c2c776ee3acc0451844645dda14247
-size 27150
+oid sha256:a20f7283220500bb17c8c5afbab5a5c1bfdc03703868b8ced7da462657806fd7
+size 27409
diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax
index 18f653f86..0e94c5c06 100644
--- a/Content/Shaders/GlobalSignDistanceField.flax
+++ b/Content/Shaders/GlobalSignDistanceField.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2c8aa181a814d69b15ffec6493a71a6f42ae816ce04f7803cff2d5073b4b3c4f
-size 11790
+oid sha256:e075583620e62407503c73f52487c204ddcad421e80fa621aebd55af1cfb08d5
+size 11798
diff --git a/Flax.flaxproj b/Flax.flaxproj
index ebcdc2830..81a63a1fc 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -3,7 +3,8 @@
"Version": {
"Major": 1,
"Minor": 7,
- "Build": 6401
+ "Revision": 1,
+ "Build": 6406
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.",
diff --git a/GenerateProjectFiles.bat b/GenerateProjectFiles.bat
index 622939c34..28970a203 100644
--- a/GenerateProjectFiles.bat
+++ b/GenerateProjectFiles.bat
@@ -15,7 +15,7 @@ if errorlevel 1 goto BuildToolFailed
:: Build bindings for all editor configurations
echo Building C# bindings...
-Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor,FlaxGame
+Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor
popd
echo Done!
diff --git a/GenerateProjectFiles.command b/GenerateProjectFiles.command
index 5ee5c0783..a42121252 100755
--- a/GenerateProjectFiles.command
+++ b/GenerateProjectFiles.command
@@ -14,4 +14,4 @@ bash ./Development/Scripts/Mac/CallBuildTool.sh --genproject "$@"
# Build bindings for all editor configurations
echo Building C# bindings...
# TODO: Detect the correct architecture here
-Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor,FlaxGame
+Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor
diff --git a/GenerateProjectFiles.sh b/GenerateProjectFiles.sh
index dceb8abe8..76d96c7ef 100755
--- a/GenerateProjectFiles.sh
+++ b/GenerateProjectFiles.sh
@@ -14,4 +14,4 @@ bash ./Development/Scripts/Linux/CallBuildTool.sh --genproject "$@"
# Build bindings for all editor configurations
echo Building C# bindings...
# TODO: Detect the correct architecture here
-Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor,FlaxGame
+Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor
diff --git a/PackageAll.bat b/PackageAll.bat
index 02ab69f4d..0325f2244 100644
--- a/PackageAll.bat
+++ b/PackageAll.bat
@@ -7,7 +7,7 @@ pushd
echo Performing the full package...
rem Run the build tool.
-call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -deployPlatforms -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
+call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -deployPlatforms -dotnet=7 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
if errorlevel 1 goto BuildToolFailed
popd
diff --git a/PackageEditor.bat b/PackageEditor.bat
index 2dc90f7f4..515b81871 100644
--- a/PackageEditor.bat
+++ b/PackageEditor.bat
@@ -7,7 +7,7 @@ pushd
echo Building and packaging Flax Editor...
rem Run the build tool.
-call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
+call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -dotnet=7 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
if errorlevel 1 goto BuildToolFailed
popd
diff --git a/PackageEditor.command b/PackageEditor.command
index d7cc909a0..eb62ae1a4 100755
--- a/PackageEditor.command
+++ b/PackageEditor.command
@@ -9,4 +9,4 @@ echo Building and packaging Flax Editor...
cd "`dirname "$0"`"
# Run Flax.Build (also pass the arguments)
-bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployEditor --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
+bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployEditor --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
diff --git a/PackageEditor.sh b/PackageEditor.sh
index 0584ab4e7..151147b6a 100755
--- a/PackageEditor.sh
+++ b/PackageEditor.sh
@@ -9,4 +9,4 @@ echo Building and packaging Flax Editor...
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" "$@"
+bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployEditor --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
diff --git a/PackagePlatforms.bat b/PackagePlatforms.bat
index d8ebd0980..eb9c42d34 100644
--- a/PackagePlatforms.bat
+++ b/PackagePlatforms.bat
@@ -7,7 +7,7 @@ pushd
echo Building and packaging platforms data...
rem Run the build tool.
-call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployPlatforms -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
+call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployPlatforms -dotnet=7 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %*
if errorlevel 1 goto BuildToolFailed
popd
diff --git a/PackagePlatforms.command b/PackagePlatforms.command
index 20847f232..e9182a627 100755
--- a/PackagePlatforms.command
+++ b/PackagePlatforms.command
@@ -9,4 +9,4 @@ echo Building and packaging platforms data...
cd "`dirname "$0"`"
# Run Flax.Build (also pass the arguments)
-bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployPlatforms --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
+bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployPlatforms --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
diff --git a/PackagePlatforms.sh b/PackagePlatforms.sh
index 3f9a9c550..f7e43cc42 100755
--- a/PackagePlatforms.sh
+++ b/PackagePlatforms.sh
@@ -9,4 +9,4 @@ echo Building and packaging platforms data...
cd "`dirname "$0"`"
# Run Flax.Build (also pass the arguments)
-bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployPlatforms --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
+bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployPlatforms --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@"
diff --git a/README.md b/README.md
index fac631a6a..d6688bd03 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
Flax Engine is a high quality modern 3D game engine written in C++ and C#.
-From stunning graphics to powerful scripts - Flax can give everything for your games. Designed for fast workflow with many ready to use features waiting for you right now. To learn more see the website ([www.flaxengine.com](https://flaxengine.com)).
+From stunning graphics to powerful scripts, it's designed for fast workflow with many ready-to-use features waiting for you right now. To learn more see the website ([www.flaxengine.com](https://flaxengine.com)).
This repository contains full source code of the Flax Engine (excluding NDA-protected platforms support). Anyone is welcome to contribute or use the modified source in Flax-based games.
diff --git a/Source/Editor/Content/AssetPickerValidator.cs b/Source/Editor/Content/AssetPickerValidator.cs
new file mode 100644
index 000000000..f43ab6a29
--- /dev/null
+++ b/Source/Editor/Content/AssetPickerValidator.cs
@@ -0,0 +1,292 @@
+using System;
+using System.IO;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.Utilities;
+
+namespace FlaxEditor.Content;
+
+///
+/// Manages and converts the selected content item to the appropriate types. Useful for drag operations.
+///
+public class AssetPickerValidator : IContentItemOwner
+{
+ private Asset _selected;
+ private ContentItem _selectedItem;
+ private ScriptType _type;
+ private string _fileExtension;
+
+ ///
+ /// Gets or sets the selected item.
+ ///
+ public ContentItem SelectedItem
+ {
+ get => _selectedItem;
+ set
+ {
+ if (_selectedItem == value)
+ return;
+ if (value == null)
+ {
+ if (_selected == null && _selectedItem is SceneItem)
+ {
+ // Deselect scene reference
+ _selectedItem.RemoveReference(this);
+ _selectedItem = null;
+ _selected = null;
+ OnSelectedItemChanged();
+ return;
+ }
+
+ // Deselect
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = null;
+ _selected = null;
+ OnSelectedItemChanged();
+ }
+ else if (value is SceneItem item)
+ {
+ if (_selectedItem == item)
+ return;
+ if (!IsValid(item))
+ item = null;
+
+ // Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue)
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = item;
+ _selected = null;
+ _selectedItem?.AddReference(this);
+ OnSelectedItemChanged();
+ }
+ else if (value is AssetItem assetItem)
+ {
+ SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID);
+ }
+ else
+ {
+ // Change value
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = value;
+ _selected = null;
+ OnSelectedItemChanged();
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the selected asset identifier.
+ ///
+ public Guid SelectedID
+ {
+ get
+ {
+ if (_selected != null)
+ return _selected.ID;
+ if (_selectedItem is AssetItem assetItem)
+ return assetItem.ID;
+ return Guid.Empty;
+ }
+ set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value);
+ }
+
+ ///
+ /// Gets or sets the selected content item path.
+ ///
+ public string SelectedPath
+ {
+ get
+ {
+ string path = _selectedItem?.Path ?? _selected?.Path;
+ if (path != null)
+ {
+ // Convert into path relative to the project (cross-platform)
+ var projectFolder = Globals.ProjectFolder;
+ if (path.StartsWith(projectFolder))
+ path = path.Substring(projectFolder.Length + 1);
+ }
+ return path;
+ }
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ SelectedItem = null;
+ }
+ else
+ {
+ var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
+ SelectedItem = Editor.Instance.ContentDatabase.Find(path);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the selected asset object.
+ ///
+ public Asset SelectedAsset
+ {
+ get => _selected;
+ set
+ {
+ // Check if value won't change
+ if (value == _selected)
+ return;
+
+ // Find item from content database and check it
+ var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null;
+ if (item != null && !IsValid(item))
+ item = null;
+
+ // Change value
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = item;
+ _selected = value;
+ _selectedItem?.AddReference(this);
+ OnSelectedItemChanged();
+ }
+ }
+
+ ///
+ /// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use for generic file picker.
+ ///
+ public ScriptType AssetType
+ {
+ get => _type;
+ set
+ {
+ if (_type != value)
+ {
+ _type = value;
+
+ // Auto deselect if the current value is invalid
+ if (_selectedItem != null && !IsValid(_selectedItem))
+ SelectedItem = null;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the content items extensions filter. Null if unused.
+ ///
+ public string FileExtension
+ {
+ get => _fileExtension;
+ set
+ {
+ if (_fileExtension != value)
+ {
+ _fileExtension = value;
+
+ // Auto deselect if the current value is invalid
+ if (_selectedItem != null && !IsValid(_selectedItem))
+ SelectedItem = null;
+ }
+ }
+ }
+
+ ///
+ /// Occurs when selected item gets changed.
+ ///
+ public event Action SelectedItemChanged;
+
+ ///
+ /// The custom callback for assets validation. Cane be used to implement a rule for assets to pick.
+ ///
+ public Func CheckValid;
+
+ ///
+ /// Returns whether item is valid.
+ ///
+ ///
+ ///
+ public bool IsValid(ContentItem item)
+ {
+ if (_fileExtension != null && !item.Path.EndsWith(_fileExtension))
+ return false;
+ if (CheckValid != null && !CheckValid(item))
+ return false;
+ if (_type == ScriptType.Null)
+ return true;
+
+ if (item is AssetItem assetItem)
+ {
+ // Faster path for binary items (in-built)
+ if (assetItem is BinaryAssetItem binaryItem)
+ return _type.IsAssignableFrom(new ScriptType(binaryItem.Type));
+
+ // Type filter
+ var type = TypeUtils.GetType(assetItem.TypeName);
+ if (_type.IsAssignableFrom(type))
+ return true;
+
+ // Json assets can contain any type of the object defined by the C# type (data oriented design)
+ if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset)))
+ return true;
+
+ // Special case for scene asset references
+ if (_type.Type == typeof(SceneReference) && assetItem is SceneItem)
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AssetPickerValidator()
+ : this(new ScriptType(typeof(Asset)))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The assets types that this picker accepts.
+ public AssetPickerValidator(ScriptType assetType)
+ {
+ _type = assetType;
+ }
+
+ ///
+ /// Called when selected item gets changed.
+ ///
+ protected virtual void OnSelectedItemChanged()
+ {
+ SelectedItemChanged?.Invoke();
+ }
+
+ ///
+ public void OnItemDeleted(ContentItem item)
+ {
+ // Deselect item
+ SelectedItem = null;
+ }
+
+ ///
+ public void OnItemRenamed(ContentItem item)
+ {
+ }
+
+ ///
+ public void OnItemReimported(ContentItem item)
+ {
+ }
+
+ ///
+ public void OnItemDispose(ContentItem item)
+ {
+ // Deselect item
+ SelectedItem = null;
+ }
+
+ ///
+ /// Call to remove reference from the selected item.
+ ///
+ public void OnDestroy()
+ {
+ _selectedItem?.RemoveReference(this);
+ _selectedItem = null;
+ _selected = null;
+ }
+}
diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs
index 2f6651b14..e2cd1e771 100644
--- a/Source/Editor/Content/Tree/ContentTreeNode.cs
+++ b/Source/Editor/Content/Tree/ContentTreeNode.cs
@@ -24,6 +24,16 @@ namespace FlaxEditor.Content
///
protected ContentFolder _folder;
+ ///
+ /// Whether this node can be deleted.
+ ///
+ public virtual bool CanDelete => true;
+
+ ///
+ /// Whether this node can be duplicated.
+ ///
+ public virtual bool CanDuplicate => true;
+
///
/// Gets the content folder item.
///
@@ -86,6 +96,7 @@ namespace FlaxEditor.Content
Folder.ParentFolder = parent.Folder;
Parent = parent;
}
+ IconColor = Style.Current.Foreground;
}
///
@@ -300,7 +311,7 @@ namespace FlaxEditor.Content
StartRenaming();
return true;
case KeyboardKeys.Delete:
- if (Folder.Exists)
+ if (Folder.Exists && CanDelete)
Editor.Instance.Windows.ContentWin.Delete(Folder);
return true;
}
@@ -309,7 +320,7 @@ namespace FlaxEditor.Content
switch (key)
{
case KeyboardKeys.D:
- if (Folder.Exists)
+ if (Folder.Exists && CanDuplicate)
Editor.Instance.Windows.ContentWin.Duplicate(Folder);
return true;
}
diff --git a/Source/Editor/Content/Tree/MainContentTreeNode.cs b/Source/Editor/Content/Tree/MainContentTreeNode.cs
index def873622..a4f24f4ab 100644
--- a/Source/Editor/Content/Tree/MainContentTreeNode.cs
+++ b/Source/Editor/Content/Tree/MainContentTreeNode.cs
@@ -12,6 +12,12 @@ namespace FlaxEditor.Content
{
private FileSystemWatcher _watcher;
+ ///
+ public override bool CanDelete => false;
+
+ ///
+ public override bool CanDuplicate => false;
+
///
/// Initializes a new instance of the class.
///
diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h
index 51a3fcd79..1924b0c56 100644
--- a/Source/Editor/Cooker/CookingData.h
+++ b/Source/Editor/Cooker/CookingData.h
@@ -12,6 +12,13 @@
class GameCooker;
class PlatformTools;
+#if OFFICIAL_BUILD
+// Use the fixed .NET SDK version in packaged builds for compatibility (FlaxGame is precompiled with it)
+#define GAME_BUILD_DOTNET_VER TEXT("-dotnet=7")
+#else
+#define GAME_BUILD_DOTNET_VER TEXT("")
+#endif
+
///
/// Game building options. Used as flags.
///
diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
index 7d05409e5..995f19fce 100644
--- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
@@ -280,17 +280,25 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
const Char* gradlew = TEXT("gradlew");
#endif
#if PLATFORM_LINUX
- Platform::RunProcess(String::Format(TEXT("chmod +x \"{0}/gradlew\""), data.OriginalOutputPath), data.OriginalOutputPath, Dictionary(), true);
+ {
+ CreateProcessSettings procSettings;
+ procSettings.FileName = String::Format(TEXT("chmod +x \"{0}/gradlew\""), data.OriginalOutputPath);
+ procSettings.WorkingDirectory = data.OriginalOutputPath;
+ procSettings.HiddenWindow = true;
+ Platform::CreateProcess(procSettings);
+ }
#endif
const bool distributionPackage = buildSettings->ForDistribution;
- CreateProcessSettings procSettings;
- procSettings.FileName = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT("assemble") : TEXT("assembleDebug"));
- procSettings.WorkingDirectory = data.OriginalOutputPath;
- const int32 result = Platform::CreateProcess(procSettings);
- if (result != 0)
{
- data.Error(String::Format(TEXT("Failed to build Gradle project into package (result code: {0}). See log for more info."), result));
- return true;
+ CreateProcessSettings procSettings;
+ procSettings.FileName = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT("assemble") : TEXT("assembleDebug"));
+ procSettings.WorkingDirectory = data.OriginalOutputPath;
+ const int32 result = Platform::CreateProcess(procSettings);
+ if (result != 0)
+ {
+ data.Error(String::Format(TEXT("Failed to build Gradle project into package (result code: {0}). See log for more info."), result));
+ return true;
+ }
}
// Copy result package
diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
index ec7b8e7e1..42ef6fcdf 100644
--- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
@@ -104,4 +104,19 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data)
return false;
}
+void LinuxPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
+{
+ // Pick the first executable file
+ Array files;
+ FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
+ for (auto& file : files)
+ {
+ if (FileSystem::GetExtension(file).IsEmpty())
+ {
+ executableFile = file;
+ break;
+ }
+ }
+}
+
#endif
diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
index 562b38962..432240d00 100644
--- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
@@ -20,6 +20,7 @@ public:
ArchitectureType GetArchitecture() const override;
bool UseSystemDotnet() const override;
bool OnDeployBinaries(CookingData& data) override;
+ void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
};
#endif
diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
index a1db61dbb..aa56a6b95 100644
--- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
@@ -249,4 +249,19 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
return false;
}
+void MacPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
+{
+ // Pick the first executable file
+ Array files;
+ FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
+ for (auto& file : files)
+ {
+ if (FileSystem::GetExtension(file).IsEmpty())
+ {
+ executableFile = file;
+ break;
+ }
+ }
+}
+
#endif
diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
index 21d9141e3..efdd0b733 100644
--- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
@@ -27,6 +27,7 @@ public:
bool IsNativeCodeFile(CookingData& data, const String& file) override;
void OnBuildStarted(CookingData& data) override;
bool OnPostProcess(CookingData& data) override;
+ void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
};
#endif
diff --git a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
index 361e5f937..befc41640 100644
--- a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
+++ b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
@@ -188,8 +188,8 @@ bool CompileScriptsStep::Perform(CookingData& data)
LOG(Info, "Starting scripts compilation for game...");
const String logFile = data.CacheDirectory / TEXT("CompileLog.txt");
auto args = String::Format(
- TEXT("-log -logfile=\"{4}\" -build -mutex -buildtargets={0} -platform={1} -arch={2} -configuration={3} -aotMode={5}"),
- target, platform, architecture, configuration, logFile, ToString(data.Tools->UseAOT()));
+ TEXT("-log -logfile=\"{4}\" -build -mutex -buildtargets={0} -platform={1} -arch={2} -configuration={3} -aotMode={5} {6}"),
+ target, platform, architecture, configuration, logFile, ToString(data.Tools->UseAOT()), GAME_BUILD_DOTNET_VER);
#if PLATFORM_WINDOWS
if (data.Platform == BuildPlatform::LinuxX64)
#elif PLATFORM_LINUX
diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
index 8306475d1..c1179c77a 100644
--- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp
+++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
@@ -87,7 +87,7 @@ bool DeployDataStep::Perform(CookingData& data)
{
// Ask Flax.Build to provide .Net SDK location for the current platform
String sdks;
- bool failed = ScriptsBuilder::RunBuildTool(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs"), data.CacheDirectory);
+ bool failed = ScriptsBuilder::RunBuildTool(String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs {}"), GAME_BUILD_DOTNET_VER), data.CacheDirectory);
failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
int32 idx = sdks.Find(TEXT("DotNetSdk, "), StringSearchCase::CaseSensitive);
if (idx != -1)
@@ -168,7 +168,7 @@ bool DeployDataStep::Perform(CookingData& data)
String sdks;
const Char *platformName, *archName;
data.GetBuildPlatformName(platformName, archName);
- String args = String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printDotNetRuntime -platform={} -arch={}"), platformName, archName);
+ String args = String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printDotNetRuntime -platform={} -arch={} {}"), platformName, archName, GAME_BUILD_DOTNET_VER);
bool failed = ScriptsBuilder::RunBuildTool(args, data.CacheDirectory);
failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
Array parts;
@@ -269,8 +269,8 @@ bool DeployDataStep::Perform(CookingData& data)
LOG(Info, "Optimizing .NET class library size to include only used assemblies");
const String logFile = data.CacheDirectory / TEXT("StripDotnetLibs.txt");
String args = String::Format(
- TEXT("-log -logfile=\"{}\" -runDotNetClassLibStripping -mutex -binaries=\"{}\""),
- logFile, data.DataOutputPath);
+ TEXT("-log -logfile=\"{}\" -runDotNetClassLibStripping -mutex -binaries=\"{}\" {}"),
+ logFile, data.DataOutputPath, GAME_BUILD_DOTNET_VER);
for (const String& define : data.CustomDefines)
{
args += TEXT(" -D");
diff --git a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
index deb8f5022..5d566efb5 100644
--- a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
+++ b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
@@ -67,8 +67,8 @@ bool PrecompileAssembliesStep::Perform(CookingData& data)
data.GetBuildPlatformName(platform, architecture);
const String logFile = data.CacheDirectory / TEXT("AOTLog.txt");
String args = String::Format(
- TEXT("-log -logfile=\"{}\" -runDotNetAOT -mutex -platform={} -arch={} -configuration={} -aotMode={} -binaries=\"{}\" -intermediate=\"{}\""),
- logFile, platform, architecture, configuration, ToString(aotMode), data.DataOutputPath, data.ManagedCodeOutputPath);
+ TEXT("-log -logfile=\"{}\" -runDotNetAOT -mutex -platform={} -arch={} -configuration={} -aotMode={} -binaries=\"{}\" -intermediate=\"{}\" {}"),
+ logFile, platform, architecture, configuration, ToString(aotMode), data.DataOutputPath, data.ManagedCodeOutputPath, GAME_BUILD_DOTNET_VER);
if (!buildSettings.SkipUnusedDotnetLibsPackaging)
args += TEXT(" -skipUnusedDotnetLibs=false"); // Run AOT on whole class library (not just used libs)
for (const String& define : data.CustomDefines)
diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs
index 03b21e12b..9de330213 100644
--- a/Source/Editor/CustomEditors/CustomEditor.cs
+++ b/Source/Editor/CustomEditors/CustomEditor.cs
@@ -157,6 +157,12 @@ namespace FlaxEditor.CustomEditors
var values = _values;
var presenter = _presenter;
var layout = _layout;
+ if (layout.Editors.Count > 1)
+ {
+ // There are more editors using the same layout so rebuild parent editor to prevent removing others editors
+ _parent?.RebuildLayout();
+ return;
+ }
var control = layout.ContainerControl;
var parent = _parent;
var parentScrollV = (_presenter?.Panel.Parent as Panel)?.VScrollBar?.Value ?? -1;
diff --git a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
index 71c5f6daf..4099e5aee 100644
--- a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
@@ -225,8 +225,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
_actor = actor;
- var showActorPicker = actor == null || ParentEditor.Values.All(x => x is not Cloth);
- if (showActorPicker)
+ if (ParentEditor.Values.Any(x => x is Cloth))
+ {
+ // Cloth always picks the parent model mesh
+ if (actor == null)
+ {
+ layout.Label("Cloth needs to be added as a child to model actor.");
+ }
+ }
+ else
{
// Actor reference picker
_actorPicker = layout.Custom();
@@ -242,7 +249,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
var model = staticModel.Model;
if (model == null || model.WaitForLoaded())
+ {
+ layout.Label("No model.");
return;
+ }
var materials = model.MaterialSlots;
var lods = model.LODs;
meshNames = new string[lods.Length][];
@@ -267,7 +277,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
var skinnedModel = animatedModel.SkinnedModel;
if (skinnedModel == null || skinnedModel.WaitForLoaded())
+ {
+ layout.Label("No model.");
return;
+ }
var materials = skinnedModel.MaterialSlots;
var lods = skinnedModel.LODs;
meshNames = new string[lods.Length][];
diff --git a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
index 8fb742b5e..7e0c6f38c 100644
--- a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
@@ -37,41 +37,32 @@ public class MissingScriptEditor : GenericEditor
Parent = _dropPanel,
Height = 64,
};
-
_replaceScriptButton = new Button
{
Text = "Replace Script",
TooltipText = "Replaces the missing script with a given script type",
AnchorPreset = AnchorPresets.TopCenter,
- Width = 240,
- Height = 24,
- X = -120,
- Y = 0,
+ Bounds = new Rectangle(-120, 0, 240, 24),
Parent = replaceScriptPanel,
};
_replaceScriptButton.Clicked += OnReplaceScriptButtonClicked;
-
var replaceAllLabel = new Label
{
Text = "Replace all matching missing scripts",
TooltipText = "Whether or not to apply this script change to all scripts missing the same type.",
AnchorPreset = AnchorPresets.BottomCenter,
- Y = -34,
+ Y = -38,
Parent = replaceScriptPanel,
};
- replaceAllLabel.X -= FlaxEngine.GUI.Style.Current.FontSmall.MeasureText(replaceAllLabel.Text).X;
-
_shouldReplaceAllCheckbox = new CheckBox
{
TooltipText = replaceAllLabel.TooltipText,
AnchorPreset = AnchorPresets.BottomCenter,
- Y = -34,
+ Y = -38,
Parent = replaceScriptPanel,
};
-
- float centerDifference = (_shouldReplaceAllCheckbox.Right - replaceAllLabel.Left) / 2;
- replaceAllLabel.X += centerDifference;
- _shouldReplaceAllCheckbox.X += centerDifference;
+ _shouldReplaceAllCheckbox.X -= _replaceScriptButton.Width * 0.5f + 0.5f;
+ replaceAllLabel.X -= 52;
base.Initialize(layout);
}
diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
index 5215e23a4..296560507 100644
--- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
@@ -695,7 +695,41 @@ namespace FlaxEditor.CustomEditors.Dedicated
private void SetType(ref ScriptType controlType, UIControl uiControl)
{
string previousName = uiControl.Control?.GetType().Name ?? nameof(UIControl);
- uiControl.Control = (Control)controlType.CreateInstance();
+
+ var oldControlType = (Control)uiControl.Control;
+ var newControlType = (Control)controlType.CreateInstance();
+
+ // copy old control data to new control
+ if (oldControlType != null)
+ {
+ newControlType.Visible = oldControlType.Visible;
+ newControlType.Enabled = oldControlType.Enabled;
+ newControlType.AutoFocus = oldControlType.AutoFocus;
+
+ newControlType.AnchorMin = oldControlType.AnchorMin;
+ newControlType.AnchorMax = oldControlType.AnchorMax;
+ newControlType.Offsets = oldControlType.Offsets;
+
+ newControlType.LocalLocation = oldControlType.LocalLocation;
+ newControlType.Scale = oldControlType.Scale;
+ newControlType.Bounds = oldControlType.Bounds;
+ newControlType.Width = oldControlType.Width;
+ newControlType.Height = oldControlType.Height;
+ newControlType.Center = oldControlType.Center;
+ newControlType.PivotRelative = oldControlType.PivotRelative;
+
+ newControlType.Pivot = oldControlType.Pivot;
+ newControlType.Shear = oldControlType.Shear;
+ newControlType.Rotation = oldControlType.Rotation;
+ }
+ if (oldControlType is ContainerControl oldContainer && newControlType is ContainerControl newContainer)
+ {
+ newContainer.CullChildren = oldContainer.CullChildren;
+ newContainer.ClipChildren = oldContainer.ClipChildren;
+ }
+
+ uiControl.Control = newControlType;
+
if (uiControl.Name.StartsWith(previousName))
{
string newName = controlType.Name + uiControl.Name.Substring(previousName.Length);
diff --git a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
index bf087feda..4829bd91a 100644
--- a/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ActorLayerEditor.cs
@@ -34,7 +34,7 @@ namespace FlaxEditor.CustomEditors.Editors
value = 0;
// If selected is single actor that has children, ask if apply layer to the sub objects as well
- if (Values.IsSingleObject && (int)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren)
+ if (Values.IsSingleObject && (int)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren && !Editor.IsPlayMode)
{
var valueText = comboBox.SelectedItem;
diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
index cfba940c2..1f3359fd5 100644
--- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
@@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
// Generic file picker
assetType = ScriptType.Null;
- Picker.FileExtension = assetReference.TypeName;
+ Picker.Validator.FileExtension = assetReference.TypeName;
}
else
{
@@ -85,7 +85,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
- Picker.AssetType = assetType;
+ Picker.Validator.AssetType = assetType;
Picker.Height = height;
Picker.SelectedItemChanged += OnSelectedItemChanged;
}
@@ -95,15 +95,15 @@ namespace FlaxEditor.CustomEditors.Editors
if (_isRefreshing)
return;
if (typeof(AssetItem).IsAssignableFrom(_valueType.Type))
- SetValue(Picker.SelectedItem);
+ SetValue(Picker.Validator.SelectedItem);
else if (_valueType.Type == typeof(Guid))
- SetValue(Picker.SelectedID);
+ SetValue(Picker.Validator.SelectedID);
else if (_valueType.Type == typeof(SceneReference))
- SetValue(new SceneReference(Picker.SelectedID));
+ SetValue(new SceneReference(Picker.Validator.SelectedID));
else if (_valueType.Type == typeof(string))
- SetValue(Picker.SelectedPath);
+ SetValue(Picker.Validator.SelectedPath);
else
- SetValue(Picker.SelectedAsset);
+ SetValue(Picker.Validator.SelectedAsset);
}
///
@@ -115,15 +115,15 @@ namespace FlaxEditor.CustomEditors.Editors
{
_isRefreshing = true;
if (Values[0] is AssetItem assetItem)
- Picker.SelectedItem = assetItem;
+ Picker.Validator.SelectedItem = assetItem;
else if (Values[0] is Guid guid)
- Picker.SelectedID = guid;
+ Picker.Validator.SelectedID = guid;
else if (Values[0] is SceneReference sceneAsset)
- Picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
+ Picker.Validator.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
else if (Values[0] is string path)
- Picker.SelectedPath = path;
+ Picker.Validator.SelectedPath = path;
else
- Picker.SelectedAsset = Values[0] as Asset;
+ Picker.Validator.SelectedAsset = Values[0] as Asset;
_isRefreshing = false;
}
}
diff --git a/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs b/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs
index 8ac6a51cb..b3c5792ac 100644
--- a/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/BehaviorKnowledgeSelectorEditor.cs
@@ -171,11 +171,13 @@ namespace FlaxEditor.CustomEditors.Editors
tree.Select(typeNode);
if (addItems)
{
- var items = GenericEditor.GetItemsForType(type, type.IsClass, true);
+ var items = GenericEditor.GetItemsForType(type, type.IsClass, true, true);
foreach (var item in items)
{
if (typed && !typed.IsAssignableFrom(item.Info.ValueType))
continue;
+ if (item.Info.DeclaringType.Type == typeof(FlaxEngine.Object))
+ continue; // Skip engine internals
var itemPath = typePath + item.Info.Name;
var node = new TreeNode
{
diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
index 8922e2d25..6f623fb23 100644
--- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
@@ -3,9 +3,12 @@
using System;
using System.Collections;
using System.Linq;
+using FlaxEditor.Content;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.GUI.Drag;
+using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -110,7 +113,7 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Initialize(LayoutElementsContainer layout)
{
// No support for different collections for now
- if (HasDifferentValues || HasDifferentTypes)
+ if (HasDifferentTypes)
return;
var size = Count;
@@ -135,14 +138,43 @@ namespace FlaxEditor.CustomEditors.Editors
spacing = collection.Spacing;
}
+ var dragArea = layout.CustomContainer();
+ dragArea.CustomControl.Editor = this;
+ dragArea.CustomControl.ElementType = ElementType;
+
+ // Check for the AssetReferenceAttribute. In JSON assets, it can be used to filter
+ // which scripts can be dragged over and dropped on this collection editor.
+ var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
+ if (assetReference != null)
+ {
+ if (string.IsNullOrEmpty(assetReference.TypeName))
+ {
+ }
+ else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
+ {
+ dragArea.CustomControl.ElementType = ScriptType.Null;
+ dragArea.CustomControl.FileExtension = assetReference.TypeName;
+ }
+ else
+ {
+ var customType = TypeUtils.GetType(assetReference.TypeName);
+ if (customType != ScriptType.Null)
+ dragArea.CustomControl.ElementType = customType;
+ else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
+ Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for drag and drop filter.", assetReference.TypeName));
+ else
+ dragArea.CustomControl.ElementType = ScriptType.Void;
+ }
+ }
+
// Size
if (_readOnly || (NotNullItems && size == 0))
{
- layout.Label("Size", size.ToString());
+ dragArea.Label("Size", size.ToString());
}
else
{
- _size = layout.IntegerValue("Size");
+ _size = dragArea.IntegerValue("Size");
_size.IntValue.MinValue = 0;
_size.IntValue.MaxValue = ushort.MaxValue;
_size.IntValue.Value = size;
@@ -152,7 +184,7 @@ namespace FlaxEditor.CustomEditors.Editors
// Elements
if (size > 0)
{
- var panel = layout.VerticalPanel();
+ var panel = dragArea.VerticalPanel();
panel.Panel.BackgroundColor = _background;
var elementType = ElementType;
@@ -212,37 +244,33 @@ namespace FlaxEditor.CustomEditors.Editors
// Add/Remove buttons
if (!_readOnly)
{
- var area = layout.Space(20);
- var addButton = new Button(area.ContainerControl.Width - (16 + 16 + 2 + 2), 2, 16, 16)
- {
- Text = "+",
- TooltipText = "Add new item",
- AnchorPreset = AnchorPresets.TopRight,
- Parent = area.ContainerControl,
- Enabled = !NotNullItems || size > 0,
- };
- addButton.Clicked += () =>
- {
- if (IsSetBlocked)
- return;
+ var panel = dragArea.HorizontalPanel();
+ panel.Panel.Size = new Float2(0, 20);
+ panel.Panel.Margin = new Margin(2);
- Resize(Count + 1);
- };
- var removeButton = new Button(addButton.Right + 2, addButton.Y, 16, 16)
- {
- Text = "-",
- TooltipText = "Remove last item",
- AnchorPreset = AnchorPresets.TopRight,
- Parent = area.ContainerControl,
- Enabled = size > 0,
- };
- removeButton.Clicked += () =>
+ var removeButton = panel.Button("-", "Remove last item");
+ removeButton.Button.Size = new Float2(16, 16);
+ removeButton.Button.Enabled = size > 0;
+ removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
+ removeButton.Button.Clicked += () =>
{
if (IsSetBlocked)
return;
Resize(Count - 1);
};
+
+ var addButton = panel.Button("+", "Add new item");
+ addButton.Button.Size = new Float2(16, 16);
+ addButton.Button.Enabled = !NotNullItems || size > 0;
+ addButton.Button.AnchorPreset = AnchorPresets.TopRight;
+ addButton.Button.Clicked += () =>
+ {
+ if (IsSetBlocked)
+ return;
+
+ Resize(Count + 1);
+ };
}
}
@@ -369,5 +397,232 @@ namespace FlaxEditor.CustomEditors.Editors
}
return base.OnDirty(editor, value, token);
}
+
+ private class DragAreaControl : VerticalPanel
+ {
+ private DragItems _dragItems;
+ private DragActors _dragActors;
+ private DragHandlers _dragHandlers;
+ private AssetPickerValidator _pickerValidator;
+
+ public ScriptType ElementType
+ {
+ get => _pickerValidator?.AssetType ?? ScriptType.Null;
+ set => _pickerValidator = new AssetPickerValidator(value);
+ }
+
+ public CollectionEditor Editor { get; set; }
+
+ public string FileExtension
+ {
+ set => _pickerValidator.FileExtension = value;
+ }
+
+ ///
+ public override void Draw()
+ {
+ if (_dragHandlers is { HasValidDrag: true })
+ {
+ var area = new Rectangle(Float2.Zero, Size);
+ Render2D.FillRectangle(area, Color.Orange * 0.5f);
+ Render2D.DrawRectangle(area, Color.Black);
+ }
+
+ base.Draw();
+ }
+
+ public override void OnDestroy()
+ {
+ _pickerValidator.OnDestroy();
+ }
+
+ private bool ValidateActors(ActorNode node)
+ {
+ return node.Actor.GetScript(ElementType.Type) || ElementType.Type.IsAssignableTo(typeof(Actor));
+ }
+
+ ///
+ public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
+ {
+ var result = base.OnDragEnter(ref location, data);
+ if (result != DragDropEffect.None)
+ return result;
+
+ if (_dragHandlers == null)
+ {
+ _dragItems = new DragItems(_pickerValidator.IsValid);
+ _dragActors = new DragActors(ValidateActors);
+ _dragHandlers = new DragHandlers
+ {
+ _dragActors,
+ _dragItems
+ };
+ }
+ return _dragHandlers.OnDragEnter(data);
+ }
+
+ ///
+ public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
+ {
+ var result = base.OnDragMove(ref location, data);
+ if (result != DragDropEffect.None)
+ return result;
+
+ return _dragHandlers.Effect;
+ }
+
+ ///
+ public override void OnDragLeave()
+ {
+ _dragHandlers.OnDragLeave();
+
+ base.OnDragLeave();
+ }
+
+ ///
+ public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
+ {
+ var result = base.OnDragDrop(ref location, data);
+ if (result != DragDropEffect.None)
+ {
+ _dragHandlers.OnDragDrop(null);
+ return result;
+ }
+
+ if (_dragHandlers.HasValidDrag)
+ {
+ if (_dragItems.HasValidDrag)
+ {
+ var list = Editor.CloneValues();
+ if (list == null)
+ {
+ if (Editor.Values.Type.IsArray)
+ {
+ list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0);
+ }
+ else
+ {
+ list = Editor.Values.Type.CreateInstance() as IList;
+ }
+ }
+ if (list.IsFixedSize)
+ {
+ var oldSize = list.Count;
+ var newSize = list.Count + _dragItems.Objects.Count;
+ var type = Editor.Values.Type.GetElementType();
+ var array = TypeUtils.CreateArrayInstance(type, newSize);
+ list.CopyTo(array, 0);
+
+ for (var i = oldSize; i < newSize; i++)
+ {
+ var validator = new AssetPickerValidator
+ {
+ FileExtension = _pickerValidator.FileExtension,
+ AssetType = _pickerValidator.AssetType,
+ SelectedItem = _dragItems.Objects[i - oldSize],
+ };
+
+ if (typeof(AssetItem).IsAssignableFrom(ElementType.Type))
+ array.SetValue(validator.SelectedItem, i);
+ else if (ElementType.Type == typeof(Guid))
+ array.SetValue(validator.SelectedID, i);
+ else if (ElementType.Type == typeof(SceneReference))
+ array.SetValue(new SceneReference(validator.SelectedID), i);
+ else if (ElementType.Type == typeof(string))
+ array.SetValue(validator.SelectedPath, i);
+ else
+ array.SetValue(validator.SelectedAsset, i);
+
+ validator.OnDestroy();
+ }
+ Editor.SetValue(array);
+ }
+ else
+ {
+ foreach (var item in _dragItems.Objects)
+ {
+ var validator = new AssetPickerValidator
+ {
+ FileExtension = _pickerValidator.FileExtension,
+ AssetType = _pickerValidator.AssetType,
+ SelectedItem = item,
+ };
+
+ if (typeof(AssetItem).IsAssignableFrom(ElementType.Type))
+ list.Add(validator.SelectedItem);
+ else if (ElementType.Type == typeof(Guid))
+ list.Add(validator.SelectedID);
+ else if (ElementType.Type == typeof(SceneReference))
+ list.Add(new SceneReference(validator.SelectedID));
+ else if (ElementType.Type == typeof(string))
+ list.Add(validator.SelectedPath);
+ else
+ list.Add(validator.SelectedAsset);
+
+ validator.OnDestroy();
+ }
+ Editor.SetValue(list);
+ }
+ }
+ else if (_dragActors.HasValidDrag)
+ {
+ var list = Editor.CloneValues();
+ if (list == null)
+ {
+ if (Editor.Values.Type.IsArray)
+ {
+ list = TypeUtils.CreateArrayInstance(Editor.Values.Type.GetElementType(), 0);
+ }
+ else
+ {
+ list = Editor.Values.Type.CreateInstance() as IList;
+ }
+ }
+
+ if (list.IsFixedSize)
+ {
+ var oldSize = list.Count;
+ var newSize = list.Count + _dragActors.Objects.Count;
+ var type = Editor.Values.Type.GetElementType();
+ var array = TypeUtils.CreateArrayInstance(type, newSize);
+ list.CopyTo(array, 0);
+
+ for (var i = oldSize; i < newSize; i++)
+ {
+ var actor = _dragActors.Objects[i - oldSize].Actor;
+ if (ElementType.Type.IsAssignableTo(typeof(Actor)))
+ {
+ array.SetValue(actor, i);
+ }
+ else
+ {
+ array.SetValue(actor.GetScript(ElementType.Type), i);
+ }
+ }
+ Editor.SetValue(array);
+ }
+ else
+ {
+ foreach (var actorNode in _dragActors.Objects)
+ {
+ if (ElementType.Type.IsAssignableTo(typeof(Actor)))
+ {
+ list.Add(actorNode.Actor);
+ }
+ else
+ {
+ list.Add(actorNode.Actor.GetScript(ElementType.Type));
+ }
+ }
+ Editor.SetValue(list);
+ }
+ }
+
+ _dragHandlers.OnDragDrop(null);
+ }
+
+ return result;
+ }
+ }
}
}
diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
index 55ac453a9..68c24a675 100644
--- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
@@ -247,8 +247,9 @@ namespace FlaxEditor.CustomEditors.Editors
/// The type.
/// True if use type properties.
/// True if use type fields.
+ /// True if use type properties that have only getter method without setter method (aka read-only).
/// The items.
- public static List GetItemsForType(ScriptType type, bool useProperties, bool useFields)
+ public static List GetItemsForType(ScriptType type, bool useProperties, bool useFields, bool usePropertiesWithoutSetter = false)
{
var items = new List();
@@ -264,7 +265,7 @@ namespace FlaxEditor.CustomEditors.Editors
var showInEditor = attributes.Any(x => x is ShowInEditorAttribute);
// Skip properties without getter or setter
- if (!p.HasGet || (!p.HasSet && !showInEditor))
+ if (!p.HasGet || (!p.HasSet && !showInEditor && !usePropertiesWithoutSetter))
continue;
// Skip hidden fields, handle special attributes
diff --git a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
index 9607680f2..f901b20d9 100644
--- a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs
@@ -28,14 +28,16 @@ namespace FlaxEditor.CustomEditors.Editors
var group = layout.Group("Entry");
_group = group;
- if (ParentEditor == null)
+ if (ParentEditor == null || HasDifferentTypes)
return;
var entry = (ModelInstanceEntry)Values[0];
var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
var materialLabel = new PropertyNameLabel("Material");
materialLabel.TooltipText = "The mesh surface material used for the rendering.";
- if (ParentEditor.ParentEditor?.Values[0] is ModelInstanceActor modelInstance)
+ var parentEditorValues = ParentEditor.ParentEditor?.Values;
+ if (parentEditorValues?[0] is ModelInstanceActor modelInstance)
{
+ // TODO: store _modelInstance and _material in array for each selected model instance actor
_entryIndex = entryIndex;
_modelInstance = modelInstance;
var slots = modelInstance.MaterialSlots;
@@ -56,6 +58,8 @@ namespace FlaxEditor.CustomEditors.Editors
// Create material picker
var materialValue = new CustomValueContainer(new ScriptType(typeof(MaterialBase)), _material, (instance, index) => _material, (instance, index, value) => _material = value as MaterialBase);
+ for (var i = 1; i < parentEditorValues.Count; i++)
+ materialValue.Add(_material);
var materialEditor = (AssetRefEditor)_group.Property(materialLabel, materialValue);
materialEditor.Values.SetDefaultValue(defaultValue);
materialEditor.RefreshDefaultValue();
@@ -72,14 +76,14 @@ namespace FlaxEditor.CustomEditors.Editors
return;
_isRefreshing = true;
var slots = _modelInstance.MaterialSlots;
- var material = _materialEditor.Picker.SelectedAsset as MaterialBase;
+ var material = _materialEditor.Picker.Validator.SelectedAsset as MaterialBase;
var defaultMaterial = GPUDevice.Instance.DefaultMaterial;
var value = (ModelInstanceEntry)Values[0];
var prevMaterial = value.Material;
if (!material)
{
// Fallback to default material
- _materialEditor.Picker.SelectedAsset = defaultMaterial;
+ _materialEditor.Picker.Validator.SelectedAsset = defaultMaterial;
value.Material = defaultMaterial;
}
else if (material == slots[_entryIndex].Material)
diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs
index 8d6b0f9e2..84f58daf1 100644
--- a/Source/Editor/GUI/AssetPicker.cs
+++ b/Source/Editor/GUI/AssetPicker.cs
@@ -5,6 +5,7 @@ using System.IO;
using FlaxEditor.Content;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting;
+using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -17,189 +18,21 @@ namespace FlaxEditor.GUI
///
///
[HideInEditor]
- public class AssetPicker : Control, IContentItemOwner
+ public class AssetPicker : Control
{
private const float DefaultIconSize = 64;
private const float ButtonsOffset = 2;
private const float ButtonsSize = 12;
- private Asset _selected;
- private ContentItem _selectedItem;
- private ScriptType _type;
- private string _fileExtension;
-
private bool _isMouseDown;
private Float2 _mouseDownPos;
private Float2 _mousePos;
private DragItems _dragOverElement;
///
- /// Gets or sets the selected item.
+ /// The asset validator. Used to ensure only appropriate items can be picked.
///
- public ContentItem SelectedItem
- {
- get => _selectedItem;
- set
- {
- if (_selectedItem == value)
- return;
- if (value == null)
- {
- if (_selected == null && _selectedItem is SceneItem)
- {
- // Deselect scene reference
- _selectedItem.RemoveReference(this);
- _selectedItem = null;
- _selected = null;
- OnSelectedItemChanged();
- return;
- }
-
- // Deselect
- _selectedItem?.RemoveReference(this);
- _selectedItem = null;
- _selected = null;
- OnSelectedItemChanged();
- }
- else if (value is SceneItem item)
- {
- if (_selectedItem == item)
- return;
- if (!IsValid(item))
- item = null;
-
- // Change value to scene reference (cannot load asset because scene can be already loaded - duplicated ID issue)
- _selectedItem?.RemoveReference(this);
- _selectedItem = item;
- _selected = null;
- _selectedItem?.AddReference(this);
- OnSelectedItemChanged();
- }
- else if (value is AssetItem assetItem)
- {
- SelectedAsset = FlaxEngine.Content.LoadAsync(assetItem.ID);
- }
- else
- {
- // Change value
- _selectedItem?.RemoveReference(this);
- _selectedItem = value;
- _selected = null;
- OnSelectedItemChanged();
- }
- }
- }
-
- ///
- /// Gets or sets the selected asset identifier.
- ///
- public Guid SelectedID
- {
- get
- {
- if (_selected != null)
- return _selected.ID;
- if (_selectedItem is AssetItem assetItem)
- return assetItem.ID;
- return Guid.Empty;
- }
- set => SelectedItem = Editor.Instance.ContentDatabase.FindAsset(value);
- }
-
- ///
- /// Gets or sets the selected content item path.
- ///
- public string SelectedPath
- {
- get
- {
- string path = _selectedItem?.Path ?? _selected?.Path;
- if (path != null)
- {
- // Convert into path relative to the project (cross-platform)
- var projectFolder = Globals.ProjectFolder;
- if (path.StartsWith(projectFolder))
- path = path.Substring(projectFolder.Length + 1);
- }
- return path;
- }
- set
- {
- if (string.IsNullOrEmpty(value))
- {
- SelectedItem = null;
- }
- else
- {
- var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
- SelectedItem = Editor.Instance.ContentDatabase.Find(path);
- }
- }
- }
-
- ///
- /// Gets or sets the selected asset object.
- ///
- public Asset SelectedAsset
- {
- get => _selected;
- set
- {
- // Check if value won't change
- if (value == _selected)
- return;
-
- // Find item from content database and check it
- var item = value ? Editor.Instance.ContentDatabase.FindAsset(value.ID) : null;
- if (item != null && !IsValid(item))
- item = null;
-
- // Change value
- _selectedItem?.RemoveReference(this);
- _selectedItem = item;
- _selected = value;
- _selectedItem?.AddReference(this);
- OnSelectedItemChanged();
- }
- }
-
- ///
- /// Gets or sets the assets types that this picker accepts (it supports types derived from the given type). Use for generic file picker.
- ///
- public ScriptType AssetType
- {
- get => _type;
- set
- {
- if (_type != value)
- {
- _type = value;
-
- // Auto deselect if the current value is invalid
- if (_selectedItem != null && !IsValid(_selectedItem))
- SelectedItem = null;
- }
- }
- }
-
- ///
- /// Gets or sets the content items extensions filter. Null if unused.
- ///
- public string FileExtension
- {
- get => _fileExtension;
- set
- {
- if (_fileExtension != value)
- {
- _fileExtension = value;
-
- // Auto deselect if the current value is invalid
- if (_selectedItem != null && !IsValid(_selectedItem))
- SelectedItem = null;
- }
- }
- }
+ public AssetPickerValidator Validator { get; }
///
/// Occurs when selected item gets changed.
@@ -216,38 +49,6 @@ namespace FlaxEditor.GUI
///
public bool CanEdit = true;
- private bool IsValid(ContentItem item)
- {
- if (_fileExtension != null && !item.Path.EndsWith(_fileExtension))
- return false;
- if (CheckValid != null && !CheckValid(item))
- return false;
- if (_type == ScriptType.Null)
- return true;
-
- if (item is AssetItem assetItem)
- {
- // Faster path for binary items (in-built)
- if (assetItem is BinaryAssetItem binaryItem)
- return _type.IsAssignableFrom(new ScriptType(binaryItem.Type));
-
- // Type filter
- var type = TypeUtils.GetType(assetItem.TypeName);
- if (_type.IsAssignableFrom(type))
- return true;
-
- // Json assets can contain any type of the object defined by the C# type (data oriented design)
- if (assetItem is JsonAssetItem && (_type.Type == typeof(JsonAsset) || _type.Type == typeof(Asset)))
- return true;
-
- // Special case for scene asset references
- if (_type.Type == typeof(SceneReference) && assetItem is SceneItem)
- return true;
- }
-
- return false;
- }
-
///
/// Initializes a new instance of the class.
///
@@ -264,7 +65,8 @@ namespace FlaxEditor.GUI
public AssetPicker(ScriptType assetType, Float2 location)
: base(location, new Float2(DefaultIconSize + ButtonsOffset + ButtonsSize, DefaultIconSize))
{
- _type = assetType;
+ Validator = new AssetPickerValidator(assetType);
+ Validator.SelectedItemChanged += OnSelectedItemChanged;
_mousePos = Float2.Minimum;
}
@@ -275,10 +77,10 @@ namespace FlaxEditor.GUI
{
// Update tooltip
string tooltip;
- if (_selectedItem is AssetItem assetItem)
+ if (Validator.SelectedItem is AssetItem assetItem)
tooltip = assetItem.NamePath;
else
- tooltip = SelectedPath;
+ tooltip = Validator.SelectedPath;
TooltipText = tooltip;
SelectedItemChanged?.Invoke();
@@ -289,37 +91,13 @@ namespace FlaxEditor.GUI
// Do the drag drop operation if has selected element
if (new Rectangle(Float2.Zero, Size).Contains(ref _mouseDownPos))
{
- if (_selected != null)
- DoDragDrop(DragAssets.GetDragData(_selected));
- else if (_selectedItem != null)
- DoDragDrop(DragItems.GetDragData(_selectedItem));
+ if (Validator.SelectedAsset != null)
+ DoDragDrop(DragAssets.GetDragData(Validator.SelectedAsset));
+ else if (Validator.SelectedItem != null)
+ DoDragDrop(DragItems.GetDragData(Validator.SelectedItem));
}
}
- ///
- public void OnItemDeleted(ContentItem item)
- {
- // Deselect item
- SelectedItem = null;
- }
-
- ///
- public void OnItemRenamed(ContentItem item)
- {
- }
-
- ///
- public void OnItemReimported(ContentItem item)
- {
- }
-
- ///
- public void OnItemDispose(ContentItem item)
- {
- // Deselect item
- SelectedItem = null;
- }
-
private Rectangle IconRect => new Rectangle(0, 0, Height, Height);
private Rectangle Button1Rect => new Rectangle(Height + ButtonsOffset, 0, ButtonsSize, ButtonsSize);
@@ -341,10 +119,10 @@ namespace FlaxEditor.GUI
if (CanEdit)
Render2D.DrawSprite(style.ArrowDown, button1Rect, button1Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
- if (_selectedItem != null)
+ if (Validator.SelectedItem != null)
{
// Draw item preview
- _selectedItem.DrawThumbnail(ref iconRect);
+ Validator.SelectedItem.DrawThumbnail(ref iconRect);
// Draw buttons
if (CanEdit)
@@ -363,7 +141,7 @@ namespace FlaxEditor.GUI
{
Render2D.DrawText(
style.FontSmall,
- _selectedItem.ShortName,
+ Validator.SelectedItem.ShortName,
new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize),
style.Foreground,
TextAlignment.Near,
@@ -371,7 +149,7 @@ namespace FlaxEditor.GUI
}
}
// Check if has no item but has an asset (eg. virtual asset)
- else if (_selected)
+ else if (Validator.SelectedAsset)
{
// Draw remove button
Render2D.DrawSprite(style.Cross, button3Rect, button3Rect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey);
@@ -380,8 +158,8 @@ namespace FlaxEditor.GUI
float sizeForTextLeft = Width - button1Rect.Right;
if (sizeForTextLeft > 30)
{
- var name = _selected.GetType().Name;
- if (_selected.IsVirtual)
+ var name = Validator.SelectedAsset.GetType().Name;
+ if (Validator.SelectedAsset.IsVirtual)
name += " (virtual)";
Render2D.DrawText(
style.FontSmall,
@@ -395,8 +173,8 @@ namespace FlaxEditor.GUI
else
{
// No element selected
- Render2D.FillRectangle(iconRect, new Color(0.2f));
- Render2D.DrawText(style.FontMedium, "No asset\nselected", iconRect, Color.Wheat, TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, Height / DefaultIconSize);
+ Render2D.FillRectangle(iconRect, style.BackgroundNormal);
+ Render2D.DrawText(style.FontMedium, "No asset\nselected", iconRect, Color.Orange, TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, Height / DefaultIconSize);
}
// Check if drag is over
@@ -407,9 +185,7 @@ namespace FlaxEditor.GUI
///
public override void OnDestroy()
{
- _selectedItem?.RemoveReference(this);
- _selectedItem = null;
- _selected = null;
+ Validator.OnDestroy();
base.OnDestroy();
}
@@ -463,57 +239,57 @@ namespace FlaxEditor.GUI
// Buttons logic
if (!CanEdit)
{
- if (Button1Rect.Contains(location) && _selectedItem != null)
+ if (Button1Rect.Contains(location) && Validator.SelectedItem != null)
{
// Select asset
- Editor.Instance.Windows.ContentWin.Select(_selectedItem);
+ Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
}
}
else if (Button1Rect.Contains(location))
{
Focus();
- if (_type != ScriptType.Null)
+ if (Validator.AssetType != ScriptType.Null)
{
// Show asset picker popup
- var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
+ var popup = AssetSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
- SelectedItem = item;
+ Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
- if (_selected != null)
+ if (Validator.SelectedAsset != null)
{
- var selectedAssetName = Path.GetFileNameWithoutExtension(_selected.Path);
+ var selectedAssetName = Path.GetFileNameWithoutExtension(Validator.SelectedAsset.Path);
popup.ScrollToAndHighlightItemByName(selectedAssetName);
}
}
else
{
// Show content item picker popup
- var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, IsValid, item =>
+ var popup = ContentSearchPopup.Show(this, Button1Rect.BottomLeft, Validator.IsValid, item =>
{
- SelectedItem = item;
+ Validator.SelectedItem = item;
RootWindow.Focus();
Focus();
});
- if (_selectedItem != null)
+ if (Validator.SelectedItem != null)
{
- popup.ScrollToAndHighlightItemByName(_selectedItem.ShortName);
+ popup.ScrollToAndHighlightItemByName(Validator.SelectedItem.ShortName);
}
}
}
- else if (_selected != null || _selectedItem != null)
+ else if (Validator.SelectedAsset != null || Validator.SelectedItem != null)
{
- if (Button2Rect.Contains(location) && _selectedItem != null)
+ if (Button2Rect.Contains(location) && Validator.SelectedItem != null)
{
// Select asset
- Editor.Instance.Windows.ContentWin.Select(_selectedItem);
+ Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
}
else if (Button3Rect.Contains(location))
{
// Deselect asset
Focus();
- SelectedItem = null;
+ Validator.SelectedItem = null;
}
}
}
@@ -540,10 +316,10 @@ namespace FlaxEditor.GUI
{
Focus();
- if (_selectedItem != null && IconRect.Contains(location))
+ if (Validator.SelectedItem != null && IconRect.Contains(location))
{
// Open it
- Editor.Instance.ContentEditing.Open(_selectedItem);
+ Editor.Instance.ContentEditing.Open(Validator.SelectedItem);
}
// Handled
@@ -557,7 +333,7 @@ namespace FlaxEditor.GUI
// Check if drop asset
if (_dragOverElement == null)
- _dragOverElement = new DragItems(IsValid);
+ _dragOverElement = new DragItems(Validator.IsValid);
if (CanEdit && _dragOverElement.OnDragEnter(data))
{
}
@@ -590,7 +366,7 @@ namespace FlaxEditor.GUI
if (CanEdit && _dragOverElement.HasValidDrag)
{
// Select element
- SelectedItem = _dragOverElement.Objects[0];
+ Validator.SelectedItem = _dragOverElement.Objects[0];
}
// Clear cache
diff --git a/Source/Editor/GUI/Dialogs/Dialog.cs b/Source/Editor/GUI/Dialogs/Dialog.cs
index 5054aee98..86e24e3b3 100644
--- a/Source/Editor/GUI/Dialogs/Dialog.cs
+++ b/Source/Editor/GUI/Dialogs/Dialog.cs
@@ -39,6 +39,11 @@ namespace FlaxEditor.GUI.Dialogs
///
public DialogResult Result => _result;
+ ///
+ /// Returns the size of the dialog.
+ ///
+ public Float2 DialogSize => _dialogSize;
+
///
/// Initializes a new instance of the class.
///
diff --git a/Source/Editor/GUI/Docking/DockHintWindow.cs b/Source/Editor/GUI/Docking/DockHintWindow.cs
index 52c5dcd3c..6e2353441 100644
--- a/Source/Editor/GUI/Docking/DockHintWindow.cs
+++ b/Source/Editor/GUI/Docking/DockHintWindow.cs
@@ -44,11 +44,11 @@ namespace FlaxEditor.GUI.Docking
var mousePos = window.MousePosition;
var previousSize = window.Size;
window.Restore();
- window.Position = FlaxEngine.Input.MouseScreenPosition - mousePos * window.Size / previousSize;
+ window.Position = Platform.MousePosition - mousePos * window.Size / previousSize;
}
// Calculate dragging offset and move window to the destination position
- var mouseScreenPosition = FlaxEngine.Input.MouseScreenPosition;
+ var mouseScreenPosition = Platform.MousePosition;
// If the _toMove window was not focused when initializing this window, the result vector only contains zeros
// and to prevent a failure, we need to perform an update for the drag offset at later time which will be done in the OnMouseMove event handler.
@@ -83,6 +83,7 @@ namespace FlaxEditor.GUI.Docking
// Enable hit window presentation
Proxy.Window.RenderingEnabled = true;
Proxy.Window.Show();
+ Proxy.Window.Focus();
}
///
@@ -113,7 +114,7 @@ namespace FlaxEditor.GUI.Docking
var window = _toMove.Window?.Window;
if (window == null)
return;
- var mouse = FlaxEngine.Input.MouseScreenPosition;
+ var mouse = Platform.MousePosition;
// Move base window
window.Position = mouse - _dragOffset;
@@ -193,7 +194,7 @@ namespace FlaxEditor.GUI.Docking
// Move window to the mouse position (with some offset for caption bar)
var window = (WindowRootControl)toMove.Root;
- var mouse = FlaxEngine.Input.MouseScreenPosition;
+ var mouse = Platform.MousePosition;
window.Window.Position = mouse - new Float2(8, 8);
// Get floating panel
@@ -244,7 +245,7 @@ namespace FlaxEditor.GUI.Docking
private void UpdateRects()
{
// Cache mouse position
- _mouse = FlaxEngine.Input.MouseScreenPosition;
+ _mouse = Platform.MousePosition;
// Check intersection with any dock panel
var uiMouse = _mouse;
@@ -270,15 +271,16 @@ namespace FlaxEditor.GUI.Docking
// Cache dock rectangles
var size = _rectDock.Size;
var offset = _rectDock.Location;
- float BorderMargin = 4.0f;
- float ProxyHintWindowsSize2 = Proxy.HintWindowsSize * 0.5f;
- float centerX = size.X * 0.5f;
- float centerY = size.Y * 0.5f;
- _rUpper = new Rectangle(centerX - ProxyHintWindowsSize2, BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rBottom = new Rectangle(centerX - ProxyHintWindowsSize2, size.Y - Proxy.HintWindowsSize - BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rLeft = new Rectangle(BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rRight = new Rectangle(size.X - Proxy.HintWindowsSize - BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rCenter = new Rectangle(centerX - ProxyHintWindowsSize2, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
+ var borderMargin = 4.0f;
+ var hintWindowsSize = Proxy.HintWindowsSize * Platform.DpiScale;
+ var hintWindowsSize2 = hintWindowsSize * 0.5f;
+ var centerX = size.X * 0.5f;
+ var centerY = size.Y * 0.5f;
+ _rUpper = new Rectangle(centerX - hintWindowsSize2, borderMargin, hintWindowsSize, hintWindowsSize) + offset;
+ _rBottom = new Rectangle(centerX - hintWindowsSize2, size.Y - hintWindowsSize - borderMargin, hintWindowsSize, hintWindowsSize) + offset;
+ _rLeft = new Rectangle(borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
+ _rRight = new Rectangle(size.X - hintWindowsSize - borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
+ _rCenter = new Rectangle(centerX - hintWindowsSize2, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
// Hit test
DockState toSet = DockState.Float;
@@ -428,7 +430,6 @@ namespace FlaxEditor.GUI.Docking
{
if (Window == null)
{
- // Create proxy window
var settings = CreateWindowSettings.Default;
settings.Title = "DockHint.Window";
settings.Size = initSize;
@@ -440,12 +441,10 @@ namespace FlaxEditor.GUI.Docking
settings.IsRegularWindow = false;
settings.SupportsTransparency = true;
settings.ShowInTaskbar = false;
- settings.ShowAfterFirstPaint = true;
+ settings.ShowAfterFirstPaint = false;
settings.IsTopmost = true;
Window = Platform.CreateWindow(ref settings);
-
- // Set opacity and background color
Window.Opacity = 0.6f;
Window.GUI.BackgroundColor = Style.Current.DragWindow;
}
@@ -465,7 +464,7 @@ namespace FlaxEditor.GUI.Docking
var settings = CreateWindowSettings.Default;
settings.Title = name;
- settings.Size = new Float2(HintWindowsSize);
+ settings.Size = new Float2(HintWindowsSize * Platform.DpiScale);
settings.AllowInput = false;
settings.AllowMaximize = false;
settings.AllowMinimize = false;
@@ -479,7 +478,6 @@ namespace FlaxEditor.GUI.Docking
settings.ShowAfterFirstPaint = false;
win = Platform.CreateWindow(ref settings);
-
win.Opacity = 0.6f;
win.GUI.BackgroundColor = Style.Current.DragWindow;
}
diff --git a/Source/Editor/GUI/Docking/DockPanel.cs b/Source/Editor/GUI/Docking/DockPanel.cs
index e544285a5..b04aad08c 100644
--- a/Source/Editor/GUI/Docking/DockPanel.cs
+++ b/Source/Editor/GUI/Docking/DockPanel.cs
@@ -465,36 +465,47 @@ namespace FlaxEditor.GUI.Docking
{
if (Parent.Parent is SplitPanel splitter)
{
- // Check if has any child panels
- var childPanel = new List(_childPanels);
- for (int i = 0; i < childPanel.Count; i++)
+ // Check if there is another nested dock panel inside this dock panel and extract it here
+ var childPanels = _childPanels.ToArray();
+ if (childPanels.Length != 0)
{
- // Undock all tabs
- var panel = childPanel[i];
- int count = panel.TabsCount;
- while (count-- > 0)
+ // Move tabs from child panels into this one
+ DockWindow selectedTab = null;
+ foreach (var childPanel in childPanels)
{
- panel.GetTab(0).Close();
+ var childPanelTabs = childPanel.Tabs.ToArray();
+ for (var i = 0; i < childPanelTabs.Length; i++)
+ {
+ var childPanelTab = childPanelTabs[i];
+ if (selectedTab == null && childPanelTab.IsSelected)
+ selectedTab = childPanelTab;
+ childPanel.UndockWindow(childPanelTab);
+ AddTab(childPanelTab, false);
+ }
}
+ if (selectedTab != null)
+ SelectTab(selectedTab);
}
-
- // Unlink splitter
- var splitterParent = splitter.Parent;
- Assert.IsNotNull(splitterParent);
- splitter.Parent = null;
-
- // Move controls from second split panel to the split panel parent
- var scrPanel = Parent == splitter.Panel2 ? splitter.Panel1 : splitter.Panel2;
- var srcPanelChildrenCount = scrPanel.ChildrenCount;
- for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
+ else
{
- scrPanel.GetChild(i).Parent = splitterParent;
- }
- Assert.IsTrue(scrPanel.ChildrenCount == 0);
- Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
+ // Unlink splitter
+ var splitterParent = splitter.Parent;
+ Assert.IsNotNull(splitterParent);
+ splitter.Parent = null;
- // Delete
- splitter.Dispose();
+ // Move controls from second split panel to the split panel parent
+ var scrPanel = Parent == splitter.Panel2 ? splitter.Panel1 : splitter.Panel2;
+ var srcPanelChildrenCount = scrPanel.ChildrenCount;
+ for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
+ {
+ scrPanel.GetChild(i).Parent = splitterParent;
+ }
+ Assert.IsTrue(scrPanel.ChildrenCount == 0);
+ Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
+
+ // Delete
+ splitter.Dispose();
+ }
}
else if (!IsMaster)
{
@@ -582,19 +593,17 @@ namespace FlaxEditor.GUI.Docking
/// Adds the tab.
///
/// The window to insert as a tab.
- protected virtual void AddTab(DockWindow window)
+ /// True if auto-select newly added tab.
+ protected virtual void AddTab(DockWindow window, bool autoSelect = true)
{
- // Dock
_tabs.Add(window);
window.ParentDockPanel = this;
-
- // Select tab
- SelectTab(window);
+ if (autoSelect)
+ SelectTab(window);
}
private void CreateTabsProxy()
{
- // Check if has no tabs proxy created
if (_tabsProxy == null)
{
// Create proxy and make set simple full dock
diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs
index 15ff2cad0..e6e57de8e 100644
--- a/Source/Editor/GUI/Docking/DockPanelProxy.cs
+++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs
@@ -13,6 +13,7 @@ namespace FlaxEditor.GUI.Docking
public class DockPanelProxy : ContainerControl
{
private DockPanel _panel;
+ private double _dragEnterTime = -1;
///
/// The is mouse down flag (left button).
@@ -256,8 +257,8 @@ namespace FlaxEditor.GUI.Docking
else
{
tabColor = style.BackgroundHighlighted;
- Render2D.DrawLine(tabRect.BottomLeft - new Float2(0 , 1), tabRect.UpperLeft, tabColor);
- Render2D.DrawLine(tabRect.BottomRight - new Float2(0 , 1), tabRect.UpperRight, tabColor);
+ Render2D.DrawLine(tabRect.BottomLeft - new Float2(0, 1), tabRect.UpperLeft, tabColor);
+ Render2D.DrawLine(tabRect.BottomRight - new Float2(0, 1), tabRect.UpperRight, tabColor);
}
if (tab.Icon.IsValid)
@@ -477,11 +478,7 @@ namespace FlaxEditor.GUI.Docking
var result = base.OnDragEnter(ref location, data);
if (result != DragDropEffect.None)
return result;
-
- if (TrySelectTabUnderLocation(ref location))
- return DragDropEffect.Move;
-
- return DragDropEffect.None;
+ return TrySelectTabUnderLocation(ref location);
}
///
@@ -490,11 +487,15 @@ namespace FlaxEditor.GUI.Docking
var result = base.OnDragMove(ref location, data);
if (result != DragDropEffect.None)
return result;
+ return TrySelectTabUnderLocation(ref location);
+ }
- if (TrySelectTabUnderLocation(ref location))
- return DragDropEffect.Move;
+ ///
+ public override void OnDragLeave()
+ {
+ _dragEnterTime = -1;
- return DragDropEffect.None;
+ base.OnDragLeave();
}
///
@@ -503,17 +504,25 @@ namespace FlaxEditor.GUI.Docking
rect = new Rectangle(0, DockPanel.DefaultHeaderHeight, Width, Height - DockPanel.DefaultHeaderHeight);
}
- private bool TrySelectTabUnderLocation(ref Float2 location)
+ private DragDropEffect TrySelectTabUnderLocation(ref Float2 location)
{
var tab = GetTabAtPos(location, out _);
if (tab != null)
{
+ // Auto-select tab only if drag takes some time
+ var time = Platform.TimeSeconds;
+ if (_dragEnterTime < 0)
+ _dragEnterTime = time;
+ if (time - _dragEnterTime < 0.3f)
+ return DragDropEffect.Link;
+ _dragEnterTime = -1;
+
_panel.SelectTab(tab);
Update(0); // Fake update
- return true;
+ return DragDropEffect.Move;
}
-
- return false;
+ _dragEnterTime = -1;
+ return DragDropEffect.None;
}
private void ShowContextMenu(DockWindow tab, ref Float2 location)
diff --git a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
index f8ffe62ad..7bb85751a 100644
--- a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
+++ b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
@@ -72,7 +72,7 @@ namespace FlaxEditor.GUI.Docking
settings.Size = size;
settings.Position = location;
settings.MinimumSize = new Float2(1);
- settings.MaximumSize = new Float2(4096);
+ settings.MaximumSize = Float2.Zero; // Unlimited size
settings.Fullscreen = false;
settings.HasBorder = true;
settings.SupportsTransparency = false;
diff --git a/Source/Editor/GUI/Input/ColorValueBox.cs b/Source/Editor/GUI/Input/ColorValueBox.cs
index 167cc65bb..fdd7d073a 100644
--- a/Source/Editor/GUI/Input/ColorValueBox.cs
+++ b/Source/Editor/GUI/Input/ColorValueBox.cs
@@ -14,6 +14,8 @@ namespace FlaxEditor.GUI.Input
[HideInEditor]
public class ColorValueBox : Control
{
+ private bool _isMouseDown;
+
///
/// Delegate function used for the color picker events handling.
///
@@ -134,11 +136,22 @@ namespace FlaxEditor.GUI.Input
Render2D.DrawRectangle(r, IsMouseOver || IsNavFocused ? style.BackgroundSelected : Color.Black);
}
+ ///
+ public override bool OnMouseDown(Float2 location, MouseButton button)
+ {
+ _isMouseDown = true;
+ return base.OnMouseDown(location, button);
+ }
+
///
public override bool OnMouseUp(Float2 location, MouseButton button)
{
- Focus();
- OnSubmit();
+ if (_isMouseDown)
+ {
+ _isMouseDown = false;
+ Focus();
+ OnSubmit();
+ }
return true;
}
diff --git a/Source/Editor/GUI/PlatformSelector.cs b/Source/Editor/GUI/PlatformSelector.cs
index 1e03a9677..a29f62805 100644
--- a/Source/Editor/GUI/PlatformSelector.cs
+++ b/Source/Editor/GUI/PlatformSelector.cs
@@ -100,9 +100,10 @@ namespace FlaxEditor.GUI
AutoResize = true;
Offsets = new Margin(0, 0, 0, IconSize);
- _mouseOverColor = style.Foreground;
- _selectedColor = style.Foreground;
- _defaultColor = style.ForegroundGrey;
+ // Ignoring style on purpose (style would make sense if the icons were white, but they are colored)
+ _mouseOverColor = new Color(0.8f, 0.8f, 0.8f, 1f);
+ _selectedColor = Color.White;
+ _defaultColor = new Color(0.7f, 0.7f, 0.7f, 0.5f);
for (int i = 0; i < platforms.Length; i++)
{
diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs
index cb9cb09b2..f6bd5b02a 100644
--- a/Source/Editor/GUI/Row.cs
+++ b/Source/Editor/GUI/Row.cs
@@ -98,7 +98,7 @@ namespace FlaxEditor.GUI
rect.Width -= leftDepthMargin;
Render2D.PushClip(rect);
- Render2D.DrawText(style.FontMedium, text, rect, Color.White, column.CellAlignment, TextAlignment.Center);
+ Render2D.DrawText(style.FontMedium, text, rect, style.Foreground, column.CellAlignment, TextAlignment.Center);
Render2D.PopClip();
x += width;
diff --git a/Source/Editor/GUI/Tabs/Tab.cs b/Source/Editor/GUI/Tabs/Tab.cs
index 3968cc17d..85f805af0 100644
--- a/Source/Editor/GUI/Tabs/Tab.cs
+++ b/Source/Editor/GUI/Tabs/Tab.cs
@@ -13,6 +13,8 @@ namespace FlaxEditor.GUI.Tabs
[HideInEditor]
public class Tab : ContainerControl
{
+ internal Tabs _selectedInTabs;
+
///
/// Gets or sets the text.
///
@@ -86,5 +88,25 @@ namespace FlaxEditor.GUI.Tabs
{
return new Tabs.TabHeader((Tabs)Parent, this);
}
+
+ ///
+ protected override void OnParentChangedInternal()
+ {
+ if (_selectedInTabs != null)
+ _selectedInTabs.SelectedTab = null;
+
+ base.OnParentChangedInternal();
+ }
+
+ ///
+ public override void OnDestroy()
+ {
+ if (IsDisposing)
+ return;
+ if (_selectedInTabs != null)
+ _selectedInTabs.SelectedTab = null;
+
+ base.OnDestroy();
+ }
}
}
diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs
index d830b9e0f..3c70363e7 100644
--- a/Source/Editor/GUI/Tabs/Tabs.cs
+++ b/Source/Editor/GUI/Tabs/Tabs.cs
@@ -239,7 +239,7 @@ namespace FlaxEditor.GUI.Tabs
///
public Tab SelectedTab
{
- get => _selectedIndex == -1 && Children.Count > _selectedIndex + 1 ? null : Children[_selectedIndex + 1] as Tab;
+ get => _selectedIndex < 0 || Children.Count <= _selectedIndex ? null : Children[_selectedIndex + 1] as Tab;
set => SelectedTabIndex = value != null ? Children.IndexOf(value) - 1 : -1;
}
@@ -263,7 +263,12 @@ namespace FlaxEditor.GUI.Tabs
// Check if index will change
if (_selectedIndex != index)
{
- SelectedTab?.OnDeselected();
+ var prev = SelectedTab;
+ if (prev != null)
+ {
+ prev._selectedInTabs = null;
+ prev.OnDeselected();
+ }
_selectedIndex = index;
PerformLayout();
OnSelectedTabChanged();
@@ -342,8 +347,13 @@ namespace FlaxEditor.GUI.Tabs
///
protected virtual void OnSelectedTabChanged()
{
+ var selectedTab = SelectedTab;
SelectedTabChanged?.Invoke(this);
- SelectedTab?.OnSelected();
+ if (selectedTab != null)
+ {
+ selectedTab._selectedInTabs = this;
+ selectedTab.OnSelected();
+ }
}
///
diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs
index a32b35692..6b4d7bf4c 100644
--- a/Source/Editor/GUI/Timeline/Timeline.cs
+++ b/Source/Editor/GUI/Timeline/Timeline.cs
@@ -627,10 +627,11 @@ namespace FlaxEditor.GUI.Timeline
Parent = this
};
+ var style = Style.Current;
var headerTopArea = new ContainerControl
{
AutoFocus = false,
- BackgroundColor = Style.Current.LightBackground,
+ BackgroundColor = style.LightBackground,
AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = new Margin(0, 0, 0, HeaderTopAreaHeight),
Parent = _splitter.Panel1
@@ -683,7 +684,7 @@ namespace FlaxEditor.GUI.Timeline
{
AutoFocus = false,
ClipChildren = false,
- BackgroundColor = Style.Current.LightBackground,
+ BackgroundColor = style.LightBackground,
AnchorPreset = AnchorPresets.HorizontalStretchBottom,
Offsets = new Margin(0, 0, -playbackButtonsSize, playbackButtonsSize),
Parent = _splitter.Panel1
@@ -845,7 +846,7 @@ namespace FlaxEditor.GUI.Timeline
_timeIntervalsHeader = new TimeIntervalsHeader(this)
{
AutoFocus = false,
- BackgroundColor = Style.Current.Background.RGBMultiplied(0.9f),
+ BackgroundColor = style.Background.RGBMultiplied(0.9f),
AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = new Margin(0, 0, 0, HeaderTopAreaHeight),
Parent = _splitter.Panel2
@@ -854,7 +855,7 @@ namespace FlaxEditor.GUI.Timeline
{
AutoFocus = false,
ClipChildren = false,
- BackgroundColor = Style.Current.Background.RGBMultiplied(0.7f),
+ BackgroundColor = style.Background.RGBMultiplied(0.7f),
AnchorPreset = AnchorPresets.StretchAll,
Offsets = new Margin(0, 0, HeaderTopAreaHeight, 0),
Parent = _splitter.Panel2
diff --git a/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs b/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs
index b7c87cb01..3bbca15ef 100644
--- a/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/SingleMediaAssetTrack.cs
@@ -39,7 +39,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
if (AssetID == value?.ID)
return;
AssetID = value?.ID ?? Guid.Empty;
- _picker.SelectedAsset = value;
+ _picker.Validator.SelectedAsset = value;
OnAssetChanged();
Timeline?.MarkAsEdited();
}
@@ -63,10 +63,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void OnPickerSelectedItemChanged()
{
- if (Asset == (TAsset)_picker.SelectedAsset)
+ if (Asset == (TAsset)_picker.Validator.SelectedAsset)
return;
using (new TrackUndoBlock(this))
- Asset = (TAsset)_picker.SelectedAsset;
+ Asset = (TAsset)_picker.Validator.SelectedAsset;
}
///
diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs
index 7e3af05bf..703079469 100644
--- a/Source/Editor/GUI/Tree/TreeNode.cs
+++ b/Source/Editor/GUI/Tree/TreeNode.cs
@@ -776,11 +776,20 @@ namespace FlaxEditor.GUI.Tree
// Check if mouse hits arrow
if (_mouseOverArrow && HasAnyVisibleChild)
{
- // Toggle open state
- if (_opened)
- Collapse();
+ if (ParentTree.Root.GetKey(KeyboardKeys.Alt))
+ {
+ if (_opened)
+ CollapseAll();
+ else
+ ExpandAll();
+ }
else
- Expand();
+ {
+ if (_opened)
+ Collapse();
+ else
+ Expand();
+ }
}
// Check if mouse hits bar
diff --git a/Source/Editor/Gizmo/IGizmoOwner.cs b/Source/Editor/Gizmo/IGizmoOwner.cs
index ff653196e..3ab0a917b 100644
--- a/Source/Editor/Gizmo/IGizmoOwner.cs
+++ b/Source/Editor/Gizmo/IGizmoOwner.cs
@@ -106,5 +106,11 @@ namespace FlaxEditor.Gizmo
///
/// The nodes to select
void Select(List nodes);
+
+ ///
+ /// Spawns the actor in the viewport hierarchy.
+ ///
+ /// The new actor to spawn.
+ void Spawn(Actor actor);
}
}
diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs
index ef7558b8d..4a3fa39ba 100644
--- a/Source/Editor/Gizmo/TransformGizmo.cs
+++ b/Source/Editor/Gizmo/TransformGizmo.cs
@@ -111,7 +111,8 @@ namespace FlaxEditor.Gizmo
if (isSelected)
{
GetSelectedObjectsBounds(out var selectionBounds, out _);
- ray.Position = ray.GetPoint(selectionBounds.Size.Y * 0.5f);
+ var offset = Mathf.Max(selectionBounds.Size.Y * 0.5f, 1.0f);
+ ray.Position = ray.GetPoint(offset);
continue;
}
diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp
index e01fd04bb..969608676 100644
--- a/Source/Editor/Managed/ManagedEditor.Internal.cpp
+++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp
@@ -513,7 +513,9 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa
WindowsManager::WindowsLocker.Unlock();
}
WindowsManager::WindowsLocker.Lock();
- for (auto& win : WindowsManager::Windows)
+ Array> windows;
+ windows.Add(WindowsManager::Windows);
+ for (Window* win : windows)
{
if (win->IsVisible())
win->OnUpdate(deltaTime);
diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp
index fd9219d35..bed742885 100644
--- a/Source/Editor/Managed/ManagedEditor.cpp
+++ b/Source/Editor/Managed/ManagedEditor.cpp
@@ -330,14 +330,15 @@ bool ManagedEditor::CanReloadScripts()
bool ManagedEditor::CanAutoBuildCSG()
{
+ if (!ManagedEditorOptions.AutoRebuildCSG)
+ return false;
+
// Skip calls from non-managed thread (eg. physics worker)
if (!MCore::Thread::IsAttached())
return false;
if (!HasManagedInstance())
return false;
- if (!ManagedEditorOptions.AutoRebuildCSG)
- return false;
if (Internal_CanAutoBuildCSG == nullptr)
{
Internal_CanAutoBuildCSG = GetClass()->GetMethod("Internal_CanAutoBuildCSG");
@@ -348,14 +349,15 @@ bool ManagedEditor::CanAutoBuildCSG()
bool ManagedEditor::CanAutoBuildNavMesh()
{
+ if (!ManagedEditorOptions.AutoRebuildNavMesh)
+ return false;
+
// Skip calls from non-managed thread (eg. physics worker)
if (!MCore::Thread::IsAttached())
return false;
if (!HasManagedInstance())
return false;
- if (!ManagedEditorOptions.AutoRebuildNavMesh)
- return false;
if (Internal_CanAutoBuildNavMesh == nullptr)
{
Internal_CanAutoBuildNavMesh = GetClass()->GetMethod("Internal_CanAutoBuildNavMesh");
diff --git a/Source/Editor/Modules/PrefabsModule.cs b/Source/Editor/Modules/PrefabsModule.cs
index adb5f7685..6abf8e45c 100644
--- a/Source/Editor/Modules/PrefabsModule.cs
+++ b/Source/Editor/Modules/PrefabsModule.cs
@@ -124,6 +124,7 @@ namespace FlaxEditor.Modules
if (!Editor.StateMachine.CurrentState.CanEditScene)
return;
undo = Editor.Undo;
+ Editor.Scene.MarkSceneEdited(actor.Scene);
}
// Record undo for prefab creating (backend links the target instance with the prefab)
diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs
index 7789eb7c4..954e3bd50 100644
--- a/Source/Editor/Modules/SceneModule.cs
+++ b/Source/Editor/Modules/SceneModule.cs
@@ -242,7 +242,6 @@ namespace FlaxEditor.Modules
/// True if don't close opened scenes and just add new scene to them, otherwise will release current scenes and load single one.
public void OpenScene(Guid sceneId, bool additive = false)
{
- // Check if cannot change scene now
if (!Editor.StateMachine.CurrentState.CanChangeScene)
return;
@@ -266,13 +265,35 @@ namespace FlaxEditor.Modules
Editor.StateMachine.ChangingScenesState.LoadScene(sceneId, additive);
}
+ ///
+ /// Reload all loaded scenes.
+ ///
+ public void ReloadScenes()
+ {
+ if (!Editor.StateMachine.CurrentState.CanChangeScene)
+ return;
+
+ if (!Editor.IsPlayMode)
+ {
+ if (CheckSaveBeforeClose())
+ return;
+ }
+
+ // Reload scenes
+ foreach (var scene in Level.Scenes)
+ {
+ var sceneId = scene.ID;
+ Level.UnloadScene(scene);
+ Level.LoadScene(sceneId);
+ }
+ }
+
///
/// Closes scene (async).
///
/// The scene.
public void CloseScene(Scene scene)
{
- // Check if cannot change scene now
if (!Editor.StateMachine.CurrentState.CanChangeScene)
return;
@@ -296,7 +317,6 @@ namespace FlaxEditor.Modules
///
public void CloseAllScenes()
{
- // Check if cannot change scene now
if (!Editor.StateMachine.CurrentState.CanChangeScene)
return;
@@ -321,7 +341,6 @@ namespace FlaxEditor.Modules
/// The scene to not close.
public void CloseAllScenesExcept(Scene scene)
{
- // Check if cannot change scene now
if (!Editor.StateMachine.CurrentState.CanChangeScene)
return;
diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
index 225bad082..082439402 100644
--- a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
@@ -147,6 +147,17 @@ namespace FlaxEditor.Modules.SourceCodeEditing
}
if (key != null)
xml.TryGetValue(key, out text);
+
+ // Customize tooltips for properties to be more human-readable in UI
+ if (text != null && memberType.HasFlag(MemberTypes.Property) && text.StartsWith("Gets or sets ", StringComparison.Ordinal))
+ {
+ text = text.Substring(13);
+ unsafe
+ {
+ fixed (char* e = text)
+ e[0] = char.ToUpper(e[0]);
+ }
+ }
}
}
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index 1369ecd1e..3386d411c 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -39,6 +39,7 @@ namespace FlaxEditor.Modules
ContextMenuSingleSelectGroup _numberOfClientsGroup = new ContextMenuSingleSelectGroup();
private ContextMenuButton _menuFileSaveScenes;
+ private ContextMenuButton _menuFileReloadScenes;
private ContextMenuButton _menuFileCloseScenes;
private ContextMenuButton _menuFileOpenScriptsProject;
private ContextMenuButton _menuFileGenerateScriptsProjectFiles;
@@ -470,13 +471,13 @@ namespace FlaxEditor.Modules
// Place dialog nearby the target control
var targetControlDesktopCenter = targetControl.PointToScreen(targetControl.Size * 0.5f);
var desktopSize = Platform.GetMonitorBounds(targetControlDesktopCenter);
- var pos = targetControlDesktopCenter + new Float2(10.0f, -dialog.Height * 0.5f);
- var dialogEnd = pos + dialog.Size;
+ var pos = targetControlDesktopCenter + new Float2(10.0f, -dialog.DialogSize.Y * 0.5f);
+ var dialogEnd = pos + dialog.DialogSize;
var desktopEnd = desktopSize.BottomRight - new Float2(10.0f);
if (dialogEnd.X >= desktopEnd.X || dialogEnd.Y >= desktopEnd.Y)
- pos = targetControl.PointToScreen(Float2.Zero) - new Float2(10.0f + dialog.Width, dialog.Height);
+ pos = targetControl.PointToScreen(Float2.Zero) - new Float2(10.0f + dialog.DialogSize.X, dialog.DialogSize.Y);
var desktopBounds = Platform.VirtualDesktopBounds;
- pos = Float2.Clamp(pos, desktopBounds.UpperLeft, desktopBounds.BottomRight - dialog.Size);
+ pos = Float2.Clamp(pos, desktopBounds.UpperLeft, desktopBounds.BottomRight - dialog.DialogSize);
dialog.RootWindow.Window.Position = pos;
// Register for context menu (prevent auto-closing context menu when selecting color)
@@ -527,6 +528,7 @@ namespace FlaxEditor.Modules
_menuFileSaveAll = cm.AddButton("Save All", inputOptions.Save, Editor.SaveAll);
_menuFileSaveScenes = cm.AddButton("Save scenes", inputOptions.SaveScenes, Editor.Scene.SaveScenes);
_menuFileCloseScenes = cm.AddButton("Close scenes", inputOptions.CloseScenes, Editor.Scene.CloseAllScenes);
+ _menuFileReloadScenes = cm.AddButton("Reload scenes", Editor.Scene.ReloadScenes);
cm.AddSeparator();
_menuFileOpenScriptsProject = cm.AddButton("Open scripts project", inputOptions.OpenScriptsProject, Editor.CodeEditing.OpenSolution);
_menuFileGenerateScriptsProjectFiles = cm.AddButton("Generate scripts project files", inputOptions.GenerateScriptsProject, Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync);
@@ -830,6 +832,7 @@ namespace FlaxEditor.Modules
_menuFileSaveScenes.Enabled = hasOpenedScene;
_menuFileCloseScenes.Enabled = hasOpenedScene;
+ _menuFileReloadScenes.Enabled = hasOpenedScene;
_menuFileGenerateScriptsProjectFiles.Enabled = !Editor.ProgressReporting.GenerateScriptsProjectFiles.IsActive;
c.PerformLayout();
diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs
index 1a4f5ce9e..cda83b56e 100644
--- a/Source/Editor/Modules/WindowsModule.cs
+++ b/Source/Editor/Modules/WindowsModule.cs
@@ -171,9 +171,13 @@ namespace FlaxEditor.Modules
var mainWindow = MainWindow;
if (mainWindow)
{
- var projectPath = Globals.ProjectFolder.Replace('/', '\\');
- var platformBit = Platform.Is64BitApp ? "64" : "32";
- var title = string.Format("Flax Editor - \'{0}\' ({1}-bit)", projectPath, platformBit);
+ var projectPath = Globals.ProjectFolder;
+#if PLATFORM_WINDOWS
+ projectPath = projectPath.Replace('/', '\\');
+#endif
+ var engineVersion = Editor.EngineProject.Version;
+ var engineVersionText = engineVersion.Revision > 0 ? $"{engineVersion.Major}.{engineVersion.Minor}.{engineVersion.Revision}" : $"{engineVersion.Major}.{engineVersion.Minor}";
+ var title = $"Flax Editor {engineVersionText} - \'{projectPath}\'";
mainWindow.Title = title;
}
}
@@ -237,7 +241,11 @@ namespace FlaxEditor.Modules
///
public void LoadDefaultLayout()
{
- LoadLayout(StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/LayoutDefault.xml"));
+ var path = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/LayoutDefault.xml");
+ if (File.Exists(path))
+ {
+ LoadLayout(path);
+ }
}
///
@@ -731,7 +739,6 @@ namespace FlaxEditor.Modules
settings.Size = Platform.DesktopSize * 0.75f;
settings.StartPosition = WindowStartPosition.CenterScreen;
settings.ShowAfterFirstPaint = true;
-
#if PLATFORM_WINDOWS
if (!Editor.Instance.Options.Options.Interface.UseNativeWindowSystem)
{
@@ -743,12 +750,9 @@ namespace FlaxEditor.Modules
#elif PLATFORM_LINUX
settings.HasBorder = false;
#endif
-
MainWindow = Platform.CreateWindow(ref settings);
-
if (MainWindow == null)
{
- // Error
Editor.LogError("Failed to create editor main window!");
return;
}
diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs
index eb61c0f68..95c3c1d6f 100644
--- a/Source/Editor/Options/InputBinding.cs
+++ b/Source/Editor/Options/InputBinding.cs
@@ -259,10 +259,7 @@ namespace FlaxEditor.Options
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
- {
return true;
- }
-
return base.CanConvertFrom(context, sourceType);
}
@@ -270,9 +267,7 @@ namespace FlaxEditor.Options
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
- {
return false;
- }
return base.CanConvertTo(context, destinationType);
}
@@ -284,7 +279,6 @@ namespace FlaxEditor.Options
InputBinding.TryParse(str, out var result);
return result;
}
-
return base.ConvertFrom(context, culture, value);
}
@@ -295,7 +289,6 @@ namespace FlaxEditor.Options
{
return ((InputBinding)value).ToString();
}
-
return base.ConvertTo(context, culture, value, destinationType);
}
}
diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs
index 90d098bb6..e2e7d0e71 100644
--- a/Source/Editor/Options/InputOptions.cs
+++ b/Source/Editor/Options/InputOptions.cs
@@ -76,6 +76,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Common"), EditorOrder(230)]
public InputBinding RotateSelection = new InputBinding(KeyboardKeys.R);
+ [DefaultValue(typeof(InputBinding), "F11")]
+ [EditorDisplay("Common"), EditorOrder(240)]
+ public InputBinding ToggleFullscreen = new InputBinding(KeyboardKeys.F11);
+
#endregion
#region File
@@ -208,16 +212,20 @@ namespace FlaxEditor.Options
[EditorDisplay("Debugger", "Continue"), EditorOrder(810)]
public InputBinding DebuggerContinue = new InputBinding(KeyboardKeys.F5);
+ [DefaultValue(typeof(InputBinding), "Shift+F11")]
+ [EditorDisplay("Debugger", "Unlock mouse in Play Mode"), EditorOrder(820)]
+ public InputBinding DebuggerUnlockMouse = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift);
+
[DefaultValue(typeof(InputBinding), "F10")]
- [EditorDisplay("Debugger", "Step Over"), EditorOrder(820)]
+ [EditorDisplay("Debugger", "Step Over"), EditorOrder(830)]
public InputBinding DebuggerStepOver = new InputBinding(KeyboardKeys.F10);
[DefaultValue(typeof(InputBinding), "F11")]
- [EditorDisplay("Debugger", "Step Into"), EditorOrder(830)]
+ [EditorDisplay("Debugger", "Step Into"), EditorOrder(840)]
public InputBinding DebuggerStepInto = new InputBinding(KeyboardKeys.F11);
[DefaultValue(typeof(InputBinding), "Shift+F11")]
- [EditorDisplay("Debugger", "Step Out"), EditorOrder(840)]
+ [EditorDisplay("Debugger", "Step Out"), EditorOrder(850)]
public InputBinding DebuggerStepOut = new InputBinding(KeyboardKeys.F11, KeyboardKeys.Shift);
#endregion
diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs
index 07e899c5e..1137e4c37 100644
--- a/Source/Editor/Options/OptionsModule.cs
+++ b/Source/Editor/Options/OptionsModule.cs
@@ -208,13 +208,20 @@ namespace FlaxEditor.Options
// If a non-default style was chosen, switch to that style
string styleName = themeOptions.SelectedStyle;
- if (styleName != "Default" && themeOptions.Styles.TryGetValue(styleName, out var style) && style != null)
+ if (styleName != ThemeOptions.DefaultName && styleName != ThemeOptions.LightDefault && themeOptions.Styles.TryGetValue(styleName, out var style) && style != null)
{
Style.Current = style;
}
else
{
- Style.Current = CreateDefaultStyle();
+ if (styleName == ThemeOptions.LightDefault)
+ {
+ Style.Current = CreateLightStyle();
+ }
+ else
+ {
+ Style.Current = CreateDefaultStyle();
+ }
}
}
@@ -224,7 +231,6 @@ namespace FlaxEditor.Options
/// The style object.
public Style CreateDefaultStyle()
{
- // Metro Style colors
var options = Options;
var style = new Style
{
@@ -233,6 +239,7 @@ namespace FlaxEditor.Options
Foreground = Color.FromBgra(0xFFFFFFFF),
ForegroundGrey = Color.FromBgra(0xFFA9A9B3),
ForegroundDisabled = Color.FromBgra(0xFF787883),
+ ForegroundViewport = Color.FromBgra(0xFFFFFFFF),
BackgroundHighlighted = Color.FromBgra(0xFF54545C),
BorderHighlighted = Color.FromBgra(0xFF6A6A75),
BackgroundSelected = Color.FromBgra(0xFF007ACC),
@@ -274,7 +281,58 @@ namespace FlaxEditor.Options
SharedTooltip = new Tooltip(),
};
style.DragWindow = style.BackgroundSelected * 0.7f;
+ return style;
+ }
+ ///
+ /// Creates the light style (2nd default).
+ ///
+ /// The style object.
+ public Style CreateLightStyle()
+ {
+ var options = Options;
+ var style = new Style
+ {
+ Background = new Color(0.92f, 0.92f, 0.92f, 1f),
+ LightBackground = new Color(0.84f, 0.84f, 0.88f, 1f),
+ DragWindow = new Color(0.0f, 0.26f, 0.43f, 0.70f),
+ Foreground = new Color(0.0f, 0.0f, 0.0f, 1f),
+ ForegroundGrey = new Color(0.30f, 0.30f, 0.31f, 1f),
+ ForegroundDisabled = new Color(0.45f, 0.45f, 0.49f, 1f),
+ ForegroundViewport = new Color(1.0f, 1.0f, 1.0f, 1f),
+ BackgroundHighlighted = new Color(0.59f, 0.59f, 0.64f, 1f),
+ BorderHighlighted = new Color(0.50f, 0.50f, 0.55f, 1f),
+ BackgroundSelected = new Color(0.00f, 0.46f, 0.78f, 0.78f),
+ BorderSelected = new Color(0.11f, 0.57f, 0.88f, 0.65f),
+ BackgroundNormal = new Color(0.67f, 0.67f, 0.75f, 1f),
+ BorderNormal = new Color(0.59f, 0.59f, 0.64f, 1f),
+ TextBoxBackground = new Color(0.75f, 0.75f, 0.81f, 1f),
+ TextBoxBackgroundSelected = new Color(0.73f, 0.73f, 0.80f, 1f),
+ CollectionBackgroundColor = new Color(0.85f, 0.85f, 0.88f, 1f),
+ ProgressNormal = new Color(0.03f, 0.65f, 0.12f, 1f),
+
+ // Fonts
+ FontTitle = options.Interface.TitleFont.GetFont(),
+ FontLarge = options.Interface.LargeFont.GetFont(),
+ FontMedium = options.Interface.MediumFont.GetFont(),
+ FontSmall = options.Interface.SmallFont.GetFont(),
+
+ // Icons
+ ArrowDown = Editor.Icons.ArrowDown12,
+ ArrowRight = Editor.Icons.ArrowRight12,
+ Search = Editor.Icons.Search12,
+ Settings = Editor.Icons.Settings12,
+ Cross = Editor.Icons.Cross12,
+ CheckBoxIntermediate = Editor.Icons.CheckBoxIntermediate12,
+ CheckBoxTick = Editor.Icons.CheckBoxTick12,
+ StatusBarSizeGrip = Editor.Icons.WindowDrag12,
+ Translate = Editor.Icons.Translate32,
+ Rotate = Editor.Icons.Rotate32,
+ Scale = Editor.Icons.Scale32,
+ Scalar = Editor.Icons.Scalar32,
+
+ SharedTooltip = new Tooltip(),
+ };
return style;
}
diff --git a/Source/Editor/Options/ThemeOptions.cs b/Source/Editor/Options/ThemeOptions.cs
index 243918939..674e281af 100644
--- a/Source/Editor/Options/ThemeOptions.cs
+++ b/Source/Editor/Options/ThemeOptions.cs
@@ -15,6 +15,9 @@ namespace FlaxEditor.Options
[CustomEditor(typeof(ThemeOptionsEditor))]
public sealed class ThemeOptions
{
+ internal const string DefaultName = "Default";
+ internal const string LightDefault = "LightDefault";
+
internal class ThemeOptionsEditor : Editor
{
private LabelElement _infoLabel;
@@ -63,13 +66,14 @@ namespace FlaxEditor.Options
private void ReloadOptions(ComboBox obj)
{
var themeOptions = (ThemeOptions)ParentEditor.Values[0];
- var options = new string[themeOptions.Styles.Count + 1];
- options[0] = "Default";
+ var options = new string[themeOptions.Styles.Count + 2];
+ options[0] = DefaultName;
+ options[1] = LightDefault;
int i = 0;
foreach (var styleName in themeOptions.Styles.Keys)
{
- options[i + 1] = styleName;
+ options[i + 2] = styleName;
i++;
}
_combobox.ComboBox.SetItems(options);
diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs
index cee63a562..0fd018cbc 100644
--- a/Source/Editor/Options/ViewportOptions.cs
+++ b/Source/Editor/Options/ViewportOptions.cs
@@ -26,45 +26,108 @@ namespace FlaxEditor.Options
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).
+ /// Gets or sets the total amount of steps the camera needs to go from minimum to maximum speed.
///
- [DefaultValue(1.0f), Limit(0.01f, 100.0f)]
- [EditorDisplay("Defaults"), EditorOrder(110), Tooltip("The default movement speed for the viewport camera (must match the dropdown menu values in the viewport).")]
- public float DefaultMovementSpeed { get; set; } = 1.0f;
+ [DefaultValue(64), Limit(1, 128)]
+ [EditorDisplay("Camera"), EditorOrder(110), Tooltip("The total amount of steps the camera needs to go from minimum to maximum speed.")]
+ public int TotalCameraSpeedSteps { get; set; } = 64;
+
+ ///
+ /// Gets or sets the degree to which the camera will be eased when using camera flight in the editor window.
+ ///
+ [DefaultValue(3.0f), Limit(1.0f, 8.0f)]
+ [EditorDisplay("Camera"), EditorOrder(111), Tooltip("The degree to which the camera will be eased when using camera flight in the editor window (ignored if camera easing degree is enabled).")]
+ public float CameraEasingDegree { get; set; } = 3.0f;
+
+ ///
+ /// Gets or sets the default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values).
+ ///
+ [DefaultValue(1.0f), Limit(0.05f, 32.0f)]
+ [EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values).")]
+ public float MovementSpeed { get; set; } = 1.0f;
+
+ ///
+ /// Gets or sets the default minimum camera movement speed.
+ ///
+ [DefaultValue(0.05f), Limit(0.05f, 32.0f)]
+ [EditorDisplay("Defaults"), EditorOrder(121), Tooltip("The default minimum movement speed for the viewport camera.")]
+ public float MinMovementSpeed { get; set; } = 0.05f;
+
+ ///
+ /// Gets or sets the default maximum camera movement speed.
+ ///
+ [DefaultValue(32.0f), Limit(16.0f, 1000.0f)]
+ [EditorDisplay("Defaults"), EditorOrder(122), Tooltip("The default maximum movement speed for the viewport camera.")]
+ public float MaxMovementSpeed { get; set; } = 32f;
+
+ ///
+ /// Gets or sets the default camera easing mode.
+ ///
+ [DefaultValue(true)]
+ [EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default camera easing mode.")]
+ public bool UseCameraEasing { get; set; } = true;
///
/// Gets or sets the default near clipping plane distance for the viewport camera.
///
[DefaultValue(10.0f), Limit(0.001f, 1000.0f)]
- [EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default near clipping plane distance for the viewport camera.")]
- public float DefaultNearPlane { get; set; } = 10.0f;
+ [EditorDisplay("Defaults"), EditorOrder(140), Tooltip("The default near clipping plane distance for the viewport camera.")]
+ public float NearPlane { get; set; } = 10.0f;
///
/// Gets or sets the default far clipping plane distance for the viewport camera.
///
[DefaultValue(40000.0f), Limit(10.0f)]
- [EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default far clipping plane distance for the viewport camera.")]
- public float DefaultFarPlane { get; set; } = 40000.0f;
+ [EditorDisplay("Defaults"), EditorOrder(150), Tooltip("The default far clipping plane distance for the viewport camera.")]
+ public float FarPlane { get; set; } = 40000.0f;
///
/// Gets or sets the default field of view angle (in degrees) for the viewport camera.
///
[DefaultValue(60.0f), Limit(35.0f, 160.0f, 0.1f)]
- [EditorDisplay("Defaults", "Default Field Of View"), EditorOrder(140), Tooltip("The default field of view angle (in degrees) for the viewport camera.")]
- public float DefaultFieldOfView { get; set; } = 60.0f;
+ [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("The default field of view angle (in degrees) for the viewport camera.")]
+ public float FieldOfView { get; set; } = 60.0f;
///
- /// Gets or sets if the panning direction is inverted for the viewport camera.
+ /// Gets or sets the default camera orthographic mode.
///
[DefaultValue(false)]
- [EditorDisplay("Defaults"), EditorOrder(150), Tooltip("Invert the panning direction for the viewport camera.")]
- public bool DefaultInvertPanning { get; set; } = false;
+ [EditorDisplay("Defaults"), EditorOrder(170), Tooltip("The default camera orthographic mode.")]
+ public bool UseOrthographicProjection { get; set; } = false;
///
- /// Scales editor viewport grid.
+ /// Gets or sets the default camera orthographic scale (if camera uses orthographic mode).
+ ///
+ [DefaultValue(5.0f), Limit(0.001f, 100000.0f, 0.1f)]
+ [EditorDisplay("Defaults"), EditorOrder(180), Tooltip("The default camera orthographic scale (if camera uses orthographic mode).")]
+ public float OrthographicScale { get; set; } = 5.0f;
+
+ ///
+ /// Gets or sets the default panning direction for the viewport camera.
+ ///
+ [DefaultValue(false)]
+ [EditorDisplay("Defaults"), EditorOrder(190), Tooltip("The default panning direction for the viewport camera.")]
+ public bool InvertPanning { get; set; } = false;
+
+ ///
+ /// Gets or sets the default relative panning mode.
+ ///
+ [DefaultValue(true)]
+ [EditorDisplay("Defaults"), EditorOrder(200), Tooltip("The default relative panning mode. Uses distance between camera and target to determine panning speed.")]
+ public bool UseRelativePanning { get; set; } = true;
+
+ ///
+ /// Gets or sets the default panning speed (ignored if relative panning is speed enabled).
+ ///
+ [DefaultValue(0.8f), Limit(0.01f, 128.0f, 0.1f)]
+ [EditorDisplay("Defaults"), EditorOrder(210), Tooltip("The default camera panning speed (ignored if relative panning is enabled).")]
+ public float PanningSpeed { get; set; } = 0.8f;
+
+ ///
+ /// Gets or sets the default editor viewport grid scale.
///
[DefaultValue(50.0f), Limit(25.0f, 500.0f, 5.0f)]
- [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("Scales editor viewport grid.")]
+ [EditorDisplay("Defaults"), EditorOrder(220), Tooltip("The default editor viewport grid scale.")]
public float ViewportGridScale { get; set; } = 50.0f;
}
}
diff --git a/Source/Editor/Progress/Handlers/CompileScriptsProgress.cs b/Source/Editor/Progress/Handlers/CompileScriptsProgress.cs
index 310612a73..cbb383b4c 100644
--- a/Source/Editor/Progress/Handlers/CompileScriptsProgress.cs
+++ b/Source/Editor/Progress/Handlers/CompileScriptsProgress.cs
@@ -25,7 +25,6 @@ namespace FlaxEditor.Progress.Handlers
ScriptsBuilder.ScriptsReloadCalled += () => OnUpdate(0.8f, "Reloading scripts...");
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd;
- ScriptsBuilder.ScriptsReload += OnScriptsReload;
}
private void OnScriptsReloadBegin()
@@ -38,14 +37,6 @@ namespace FlaxEditor.Progress.Handlers
Editor.Instance.Scene.ClearRefsToSceneObjects(true);
}
- private void OnScriptsReload()
- {
-#if !USE_NETCORE
- // Clear types cache
- Newtonsoft.Json.JsonSerializer.ClearCache();
-#endif
- }
-
private void OnCompilationFailed()
{
OnFail("Scripts compilation failed");
diff --git a/Source/Editor/ProjectInfo.cpp b/Source/Editor/ProjectInfo.cpp
index 4e7ee4483..30c558b5d 100644
--- a/Source/Editor/ProjectInfo.cpp
+++ b/Source/Editor/ProjectInfo.cpp
@@ -154,7 +154,8 @@ bool ProjectInfo::LoadProject(const String& projectPath)
Version = ::Version(
JsonTools::GetInt(version, "Major", 0),
JsonTools::GetInt(version, "Minor", 0),
- JsonTools::GetInt(version, "Build", 0));
+ JsonTools::GetInt(version, "Build", -1),
+ JsonTools::GetInt(version, "Revision", -1));
}
}
if (Version.Revision() == 0)
diff --git a/Source/Editor/ProjectInfo.cs b/Source/Editor/ProjectInfo.cs
index b00c4e042..083665f0f 100644
--- a/Source/Editor/ProjectInfo.cs
+++ b/Source/Editor/ProjectInfo.cs
@@ -23,17 +23,11 @@ namespace FlaxEditor
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
- {
writer.WriteNull();
- }
else if (value is Version)
- {
writer.WriteValue(value.ToString());
- }
else
- {
throw new JsonSerializationException("Expected Version object value");
- }
}
///
@@ -47,65 +41,60 @@ namespace FlaxEditor
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
- {
return null;
- }
- else
+
+ if (reader.TokenType == JsonToken.StartObject)
{
- if (reader.TokenType == JsonToken.StartObject)
+ try
{
- try
+ reader.Read();
+ var values = new Dictionary();
+ while (reader.TokenType == JsonToken.PropertyName)
{
+ var key = reader.Value as string;
reader.Read();
- Dictionary values = new Dictionary();
- while (reader.TokenType == JsonToken.PropertyName)
- {
- var key = reader.Value as string;
- reader.Read();
- var val = (long)reader.Value;
- reader.Read();
- values.Add(key, (int)val);
- }
+ var val = (long)reader.Value;
+ reader.Read();
+ values.Add(key, (int)val);
+ }
- int major = 0, minor = 0, build = 0;
- values.TryGetValue("Major", out major);
- values.TryGetValue("Minor", out minor);
- values.TryGetValue("Build", out build);
+ values.TryGetValue("Major", out var major);
+ values.TryGetValue("Minor", out var minor);
+ if (!values.TryGetValue("Build", out var build))
+ build = -1;
+ if (!values.TryGetValue("Revision", out var revision))
+ revision = -1;
- Version v = new Version(major, minor, build);
- return v;
- }
- catch (Exception ex)
- {
- throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
- }
+ if (build <= 0)
+ return new Version(major, minor);
+ if (revision <= 0)
+ return new Version(major, minor, build);
+ return new Version(major, minor, build, revision);
}
- else if (reader.TokenType == JsonToken.String)
+ catch (Exception ex)
{
- try
- {
- Version v = new Version((string)reader.Value!);
- return v;
- }
- catch (Exception ex)
- {
- throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
- }
- }
- else
- {
- throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.Value));
+ throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
}
}
+ if (reader.TokenType == JsonToken.String)
+ {
+ try
+ {
+ return new Version((string)reader.Value!);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
+ }
+ }
+ throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.Value));
}
///
/// Determines whether this instance can convert the specified object type.
///
/// Type of the object.
- ///
- /// true if this instance can convert the specified object type; otherwise, false.
- ///
+ /// true if this instance can convert the specified object type; otherwise, false.
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Version);
diff --git a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs
index c09e1a246..ed6fc08d6 100644
--- a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs
+++ b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs
@@ -7,6 +7,7 @@ using Real = System.Single;
#endif
using FlaxEngine;
+using FlaxEngine.GUI;
namespace FlaxEditor.SceneGraph.Actors
{
@@ -30,6 +31,13 @@ namespace FlaxEditor.SceneGraph.Actors
// Rotate to match the space (GUI uses upper left corner as a root)
Actor.LocalOrientation = Quaternion.Euler(0, -180, -180);
+ var uiControl = new UIControl
+ {
+ Name = "Canvas Scalar",
+ Transform = Actor.Transform,
+ Control = new CanvasScaler()
+ };
+ Root.Spawn(uiControl, Actor);
}
///
diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
index d392e8309..f64e46385 100644
--- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
+++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
@@ -66,7 +66,8 @@ namespace FlaxEditor.SceneGraph.GUI
_orderInParent = actor.OrderInParent;
Visible = (actor.HideFlags & HideFlags.HideInHierarchy) == 0;
- var id = actor.ID;
+ // Pick the correct id when inside a prefab window.
+ var id = actor.HasPrefabLink && actor.Scene == null ? actor.PrefabObjectID : actor.ID;
if (Editor.Instance.ProjectCache.IsExpandedActor(ref id))
{
Expand(true);
@@ -171,7 +172,8 @@ namespace FlaxEditor.SceneGraph.GUI
// Restore cached state on query filter clear
if (noFilter && actor != null)
{
- var id = actor.ID;
+ // Pick the correct id when inside a prefab window.
+ var id = actor.HasPrefabLink && actor.Scene.Scene == null ? actor.PrefabObjectID : actor.ID;
isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id);
}
@@ -264,7 +266,7 @@ namespace FlaxEditor.SceneGraph.GUI
///
/// Starts the actor renaming action.
///
- public void StartRenaming(EditorWindow window)
+ public void StartRenaming(EditorWindow window, Panel treePanel = null)
{
// Block renaming during scripts reload
if (Editor.Instance.ProgressReporting.CompileScripts.IsActive)
@@ -279,7 +281,13 @@ namespace FlaxEditor.SceneGraph.GUI
(window as PrefabWindow).ScrollingOnTreeView(false);
// Start renaming the actor
- var dialog = RenamePopup.Show(this, TextRect, _actorNode.Name, false);
+ var rect = TextRect;
+ if (treePanel != null)
+ {
+ treePanel.ScrollViewTo(this, true);
+ rect.Size = new Float2(treePanel.Width - TextRect.Location.X, TextRect.Height);
+ }
+ var dialog = RenamePopup.Show(this, rect, _actorNode.Name, false);
dialog.Renamed += OnRenamed;
dialog.Closed += popup =>
{
@@ -301,10 +309,12 @@ namespace FlaxEditor.SceneGraph.GUI
protected override void OnExpandedChanged()
{
base.OnExpandedChanged();
+ var actor = Actor;
- if (!IsLayoutLocked && Actor)
+ if (!IsLayoutLocked && actor)
{
- var id = Actor.ID;
+ // Pick the correct id when inside a prefab window.
+ var id = actor.HasPrefabLink && actor.Scene == null ? actor.PrefabObjectID : actor.ID;
Editor.Instance.ProjectCache.SetExpandedActor(ref id, IsExpanded);
}
}
diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp
index 7f5ca6f17..de0a30e40 100644
--- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp
+++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp
@@ -207,9 +207,9 @@ void RiderCodeEditor::FindEditors(Array* output)
FileSystem::GetChildDirectories(subDirectories, TEXT("/opt/"));
// Versions installed via JetBrains Toolbox
- SearchDirectory(&installations, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/rider/"));
- FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/Rider/ch-0"));
- FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT(".local/share/JetBrains/Toolbox/apps/Rider/ch-1")); // Beta versions
+ SearchDirectory(&installations, localAppDataPath / TEXT("JetBrains/Toolbox/apps/rider/"));
+ FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains/Toolbox/apps/Rider/ch-0"));
+ FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains/Toolbox/apps/Rider/ch-1")); // Beta versions
// Detect Flatpak installations
SearchDirectory(&installations,
diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
index adccbba54..93fe7b300 100644
--- a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
+++ b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
@@ -78,7 +78,10 @@ void VisualStudioCodeEditor::FindEditors(Array* output)
// Detect Flatpak installations
{
- if (Platform::RunProcess(TEXT("/bin/bash -c \"flatpak list --app --columns=application | grep com.visualstudio.code -c\""), String::Empty) == 0)
+ CreateProcessSettings procSettings;
+ procSettings.FileName = TEXT("/bin/bash -c \"flatpak list --app --columns=application | grep com.visualstudio.code -c\"");
+ procSettings.HiddenWindow = true;
+ if (Platform::CreateProcess(procSettings) == 0)
{
const String runPath(TEXT("flatpak run com.visualstudio.code"));
output->Add(New(runPath, false));
diff --git a/Source/Editor/States/LoadingState.cs b/Source/Editor/States/LoadingState.cs
index 698dc192f..d48918e1b 100644
--- a/Source/Editor/States/LoadingState.cs
+++ b/Source/Editor/States/LoadingState.cs
@@ -56,12 +56,14 @@ namespace FlaxEditor.States
else if (Editor.Options.Options.General.ForceScriptCompilationOnStartup && !skipCompile)
{
// Generate project files when Cache is missing or was cleared previously
- if (!Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Intermediate")) ||
- !Directory.Exists(Path.Combine(Editor.GameProject?.ProjectFolderPath, "Cache", "Projects")))
+ var projectFolderPath = Editor.GameProject?.ProjectFolderPath;
+ if (!Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Intermediate")) ||
+ !Directory.Exists(Path.Combine(projectFolderPath, "Cache", "Projects")))
{
- var customArgs = Editor.Instance.CodeEditing.SelectedEditor.GenerateProjectCustomArgs;
+ var customArgs = Editor.CodeEditing.SelectedEditor?.GenerateProjectCustomArgs;
ScriptsBuilder.GenerateProject(customArgs);
}
+
// Compile scripts before loading any scenes, then we load them and can open scenes
ScriptsBuilder.Compile();
}
diff --git a/Source/Editor/States/ReloadingScriptsState.cs b/Source/Editor/States/ReloadingScriptsState.cs
index 4b8866202..d9024d5c8 100644
--- a/Source/Editor/States/ReloadingScriptsState.cs
+++ b/Source/Editor/States/ReloadingScriptsState.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEngine;
-using FlaxEditor.Utilities;
using FlaxEngine.Utilities;
namespace FlaxEditor.States
diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
index 9773e9695..7c3aa4f13 100644
--- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
+++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs
@@ -465,7 +465,7 @@ namespace FlaxEditor.Surface.Archetypes
if (selectedIndex != -1)
{
var index = 5 + selectedIndex * 2;
- SetValue(index, _animationPicker.SelectedID);
+ SetValue(index, _animationPicker.Validator.SelectedID);
}
}
@@ -495,7 +495,7 @@ namespace FlaxEditor.Surface.Archetypes
{
if (isValid)
{
- _animationPicker.SelectedID = data1;
+ _animationPicker.Validator.SelectedID = data1;
_animationSpeed.Value = data0.W;
var path = string.Empty;
@@ -505,7 +505,7 @@ namespace FlaxEditor.Surface.Archetypes
}
else
{
- _animationPicker.SelectedID = Guid.Empty;
+ _animationPicker.Validator.SelectedID = Guid.Empty;
_animationSpeed.Value = 1.0f;
}
_animationPicker.Enabled = isValid;
diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs
index bfc5bfad8..f8cd7bb5a 100644
--- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs
+++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs
@@ -288,6 +288,9 @@ namespace FlaxEditor.Surface.Archetypes
}
}
SetValue(2, ids);
+
+ // Force refresh UI
+ ResizeAuto();
}
}
diff --git a/Source/Editor/Surface/Archetypes/Bitwise.cs b/Source/Editor/Surface/Archetypes/Bitwise.cs
index 06f719adc..43dcb91b5 100644
--- a/Source/Editor/Surface/Archetypes/Bitwise.cs
+++ b/Source/Editor/Surface/Archetypes/Bitwise.cs
@@ -60,7 +60,7 @@ namespace FlaxEditor.Surface.Archetypes
Op1(1, "Bitwise NOT", "Negates the value using bitwise operation", new[] { "!", "~" }),
Op2(2, "Bitwise AND", "Performs a bitwise conjunction on two values", new[] { "&" }),
Op2(3, "Bitwise OR", "", new[] { "|" }),
- Op2(4, "Bitwise XOR", ""),
+ Op2(4, "Bitwise XOR", "", new[] { "^" }),
};
}
}
diff --git a/Source/Editor/Surface/Archetypes/Boolean.cs b/Source/Editor/Surface/Archetypes/Boolean.cs
index 153b2fead..ed97a9642 100644
--- a/Source/Editor/Surface/Archetypes/Boolean.cs
+++ b/Source/Editor/Surface/Archetypes/Boolean.cs
@@ -60,7 +60,7 @@ namespace FlaxEditor.Surface.Archetypes
Op1(1, "Boolean NOT", "Negates the boolean value", new[] { "!", "~" }),
Op2(2, "Boolean AND", "Performs a logical conjunction on two values", new[] { "&&" }),
Op2(3, "Boolean OR", "Returns true if either (or both) of its operands is true", new[] { "||" }),
- Op2(4, "Boolean XOR", ""),
+ Op2(4, "Boolean XOR", "", new [] { "^" } ),
Op2(5, "Boolean NOR", ""),
Op2(6, "Boolean NAND", ""),
};
diff --git a/Source/Editor/Surface/Archetypes/Comparisons.cs b/Source/Editor/Surface/Archetypes/Comparisons.cs
index a0efbd92f..b72111c46 100644
--- a/Source/Editor/Surface/Archetypes/Comparisons.cs
+++ b/Source/Editor/Surface/Archetypes/Comparisons.cs
@@ -14,7 +14,7 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor]
public static class Comparisons
{
- private static NodeArchetype Op(ushort id, string title, string desc)
+ private static NodeArchetype Op(ushort id, string title, string desc, string[] altTitles = null)
{
return new NodeArchetype
{
@@ -22,6 +22,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = title,
Description = desc,
Flags = NodeFlags.AllGraphs,
+ AlternativeTitles = altTitles,
ConnectionsHints = ConnectionsHint.Value,
Size = new Float2(100, 40),
IndependentBoxes = new[]
@@ -170,12 +171,12 @@ namespace FlaxEditor.Surface.Archetypes
///
public static NodeArchetype[] Nodes =
{
- Op(1, "==", "Determines whether two values are equal"),
- Op(2, "!=", "Determines whether two values are not equal"),
- Op(3, ">", "Determines whether the first value is greater than the other"),
- Op(4, "<", "Determines whether the first value is less than the other"),
- Op(5, "<=", "Determines whether the first value is less or equal to the other"),
- Op(6, ">=", "Determines whether the first value is greater or equal to the other"),
+ Op(1, "==", "Determines whether two values are equal", new[] { "equals" }),
+ Op(2, "!=", "Determines whether two values are not equal", new[] { "not equals" }),
+ Op(3, ">", "Determines whether the first value is greater than the other", new[] { "greater than", "larger than", "bigger than" }),
+ Op(4, "<", "Determines whether the first value is less than the other", new[] { "less than", "smaller than" }),
+ Op(5, "<=", "Determines whether the first value is less or equal to the other", new[] { "less equals than", "smaller equals than" }),
+ Op(6, ">=", "Determines whether the first value is greater or equal to the other", new[] { "greater equals than", "larger equals than", "bigger equals than" }),
new NodeArchetype
{
TypeID = 7,
diff --git a/Source/Editor/Surface/Archetypes/Constants.cs b/Source/Editor/Surface/Archetypes/Constants.cs
index cbe7822e3..e58d917d1 100644
--- a/Source/Editor/Surface/Archetypes/Constants.cs
+++ b/Source/Editor/Surface/Archetypes/Constants.cs
@@ -7,11 +7,12 @@ using Real = System.Single;
#endif
using System;
-using System.Reflection;
+using System.Linq;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
+using FlaxEditor.Surface.Undo;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -24,6 +25,109 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor]
public static class Constants
{
+ ///
+ /// A special type of node that adds the functionality to convert nodes to parameters.
+ ///
+ internal class ConvertToParameterNode : SurfaceNode
+ {
+ private readonly ScriptType _type;
+ private readonly Func
public object Tag;
+ ///
+ /// Custom score value to use when sorting node archetypes in Editor. If positive (eg. 1, 2) can be used to add more importance for a specific node type.
+ ///
+ public float SortScore;
+
///
/// Default node values. This array supports types: bool, int, float, Vector2, Vector3, Vector4, Color, Rectangle, Guid, string, Matrix and byte[].
///
@@ -204,14 +209,17 @@ namespace FlaxEditor.Surface
Size = Size,
Flags = Flags,
Title = Title,
- Description = Title,
+ SubTitle = SubTitle,
+ Description = Description,
AlternativeTitles = (string[])AlternativeTitles?.Clone(),
Tag = Tag,
+ SortScore = SortScore,
DefaultValues = (object[])DefaultValues?.Clone(),
DefaultType = DefaultType,
ConnectionsHints = ConnectionsHints,
IndependentBoxes = (int[])IndependentBoxes?.Clone(),
DependentBoxes = (int[])DependentBoxes?.Clone(),
+ DependentBoxFilter = DependentBoxFilter,
Elements = (NodeElementArchetype[])Elements?.Clone(),
TryParseText = TryParseText,
};
diff --git a/Source/Editor/Surface/ParticleEmitterSurface.cs b/Source/Editor/Surface/ParticleEmitterSurface.cs
index 76f96f06c..b7cf83c62 100644
--- a/Source/Editor/Surface/ParticleEmitterSurface.cs
+++ b/Source/Editor/Surface/ParticleEmitterSurface.cs
@@ -93,7 +93,7 @@ namespace FlaxEditor.Surface
}
///
- protected override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
+ protected internal override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
{
groupId = 6;
return Archetypes.Parameters.Nodes[1];
diff --git a/Source/Editor/Surface/SurfaceComment.cs b/Source/Editor/Surface/SurfaceComment.cs
index ff38b1aa8..aad45190e 100644
--- a/Source/Editor/Surface/SurfaceComment.cs
+++ b/Source/Editor/Surface/SurfaceComment.cs
@@ -2,6 +2,7 @@
using System;
using FlaxEditor.GUI;
+using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -52,6 +53,12 @@ namespace FlaxEditor.Surface
set => SetValue(2, value, false);
}
+ private int OrderValue
+ {
+ get => (int)Values[3];
+ set => SetValue(3, value, false);
+ }
+
///
public SurfaceComment(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
@@ -67,6 +74,19 @@ namespace FlaxEditor.Surface
Title = TitleValue;
Color = ColorValue;
Size = SizeValue;
+
+ // Order
+ // Backwards compatibility - When opening with an older version send the old comments to the back
+ if (Values.Length < 4)
+ {
+ if (IndexInParent > 0)
+ IndexInParent = 0;
+ OrderValue = IndexInParent;
+ }
+ else if(OrderValue != -1)
+ {
+ IndexInParent = OrderValue;
+ }
}
///
@@ -76,6 +96,10 @@ namespace FlaxEditor.Surface
// Randomize color
Color = ColorValue = Color.FromHSV(new Random().NextFloat(0, 360), 0.7f, 0.25f, 0.8f);
+
+ if(OrderValue == -1)
+ OrderValue = Context.CommentCount - 1;
+ IndexInParent = OrderValue;
}
///
@@ -314,5 +338,38 @@ namespace FlaxEditor.Surface
Color = ColorValue = color;
Surface.MarkAsEdited(false);
}
+
+ ///
+ public override void OnShowSecondaryContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu menu, Float2 location)
+ {
+ base.OnShowSecondaryContextMenu(menu, location);
+
+ menu.AddSeparator();
+ ContextMenuChildMenu cmOrder = menu.AddChildMenu("Order");
+ {
+ cmOrder.ContextMenu.AddButton("Bring Forward", () =>
+ {
+ if(IndexInParent < Context.CommentCount-1)
+ IndexInParent++;
+ OrderValue = IndexInParent;
+ });
+ cmOrder.ContextMenu.AddButton("Bring to Front", () =>
+ {
+ IndexInParent = Context.CommentCount-1;
+ OrderValue = IndexInParent;
+ });
+ cmOrder.ContextMenu.AddButton("Send Backward", () =>
+ {
+ if(IndexInParent > 0)
+ IndexInParent--;
+ OrderValue = IndexInParent;
+ });
+ cmOrder.ContextMenu.AddButton("Send to Back", () =>
+ {
+ IndexInParent = 0;
+ OrderValue = IndexInParent;
+ });
+ }
+ }
}
}
diff --git a/Source/Editor/Surface/SurfaceParameter.cs b/Source/Editor/Surface/SurfaceParameter.cs
index 6eafdc2aa..dae82e111 100644
--- a/Source/Editor/Surface/SurfaceParameter.cs
+++ b/Source/Editor/Surface/SurfaceParameter.cs
@@ -3,7 +3,6 @@
using System;
using FlaxEditor.Scripting;
using FlaxEngine;
-using FlaxEngine.Utilities;
namespace FlaxEditor.Surface
{
@@ -27,7 +26,7 @@ namespace FlaxEditor.Surface
///
/// Parameter unique ID
///
- public Guid ID;
+ public Guid ID = Guid.Empty;
///
/// Parameter name
@@ -49,23 +48,5 @@ namespace FlaxEditor.Surface
///
[NoSerialize, HideInEditor]
public readonly SurfaceMeta Meta = new SurfaceMeta();
-
- ///
- /// Creates the new parameter of the given type.
- ///
- /// The type.
- /// The name.
- /// The created parameter.
- public static SurfaceParameter Create(ScriptType type, string name)
- {
- return new SurfaceParameter
- {
- ID = Guid.NewGuid(),
- IsPublic = true,
- Name = name,
- Type = type,
- Value = TypeUtils.GetDefaultValue(type),
- };
- }
}
}
diff --git a/Source/Editor/Surface/VisjectSurface.DragDrop.cs b/Source/Editor/Surface/VisjectSurface.DragDrop.cs
index 1728c282f..2ff82c269 100644
--- a/Source/Editor/Surface/VisjectSurface.DragDrop.cs
+++ b/Source/Editor/Surface/VisjectSurface.DragDrop.cs
@@ -151,7 +151,7 @@ namespace FlaxEditor.Surface
///
/// The group ID.
/// The node archetype.
- protected virtual NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
+ protected internal virtual NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
{
groupId = 6;
return Archetypes.Parameters.Nodes[0];
diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs
index 3fe722091..3ac702549 100644
--- a/Source/Editor/Surface/VisjectSurface.cs
+++ b/Source/Editor/Surface/VisjectSurface.cs
@@ -534,6 +534,11 @@ namespace FlaxEditor.Surface
///
public virtual bool CanSetParameters => false;
+ ///
+ /// Gets a value indicating whether surface supports/allows live previewing graph modifications due to value sliders and color pickers. True by default but disabled for shader surfaces that generate and compile shader source at flight.
+ ///
+ public virtual bool CanLivePreviewValueChanges => true;
+
///
/// Determines whether the specified node archetype can be used in the surface.
///
@@ -716,7 +721,18 @@ namespace FlaxEditor.Surface
return null;
Rectangle surfaceArea = GetNodesBounds(selection).MakeExpanded(80.0f);
- return _context.CreateComment(ref surfaceArea, string.IsNullOrEmpty(text) ? "Comment" : text, new Color(1.0f, 1.0f, 1.0f, 0.2f));
+ // Order below other selected comments
+ bool hasCommentsSelected = false;
+ int lowestCommentOrder = int.MaxValue;
+ for (int i = 0; i < selection.Count; i++)
+ {
+ if (selection[i] is not SurfaceComment || selection[i].IndexInParent >= lowestCommentOrder)
+ continue;
+ hasCommentsSelected = true;
+ lowestCommentOrder = selection[i].IndexInParent;
+ }
+
+ return _context.CreateComment(ref surfaceArea, string.IsNullOrEmpty(text) ? "Comment" : text, new Color(1.0f, 1.0f, 1.0f, 0.2f), hasCommentsSelected ? lowestCommentOrder : -1);
}
private static Rectangle GetNodesBounds(List nodes)
diff --git a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs
index 7d75e006b..12f01d4f1 100644
--- a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs
+++ b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs
@@ -920,12 +920,6 @@ namespace FlaxEditor.Surface
// Link control
control.OnLoaded(action);
control.Parent = RootControl;
-
- if (control is SurfaceComment)
- {
- // Move comments to the background
- control.IndexInParent = 0;
- }
}
///
diff --git a/Source/Editor/Surface/VisjectSurfaceContext.cs b/Source/Editor/Surface/VisjectSurfaceContext.cs
index 36e811c48..0886996b6 100644
--- a/Source/Editor/Surface/VisjectSurfaceContext.cs
+++ b/Source/Editor/Surface/VisjectSurfaceContext.cs
@@ -85,6 +85,27 @@ namespace FlaxEditor.Surface
}
}
+ ///
+ /// Gets the amount of surface comments
+ ///
+ ///
+ /// This is used as an alternative to , if only the amount of comments is important.
+ /// Is faster and doesn't allocate as much memory
+ ///
+ public int CommentCount
+ {
+ get
+ {
+ int count = 0;
+ for (int i = 0; i < RootControl.Children.Count; i++)
+ {
+ if (RootControl.Children[i] is SurfaceComment)
+ count++;
+ }
+ return count;
+ }
+ }
+
///
/// Gets a value indicating whether this context is modified (needs saving and flushing with surface data context source).
///
@@ -285,14 +306,16 @@ namespace FlaxEditor.Surface
/// The surface area to create comment.
/// The comment title.
/// The comment color.
+ /// The comment order or -1 to use default.
/// The comment object
- public virtual SurfaceComment SpawnComment(ref Rectangle surfaceArea, string title, Color color)
+ public virtual SurfaceComment SpawnComment(ref Rectangle surfaceArea, string title, Color color, int customOrder = -1)
{
var values = new object[]
{
title, // Title
color, // Color
surfaceArea.Size, // Size
+ customOrder, // Order
};
return (SurfaceComment)SpawnNode(7, 11, surfaceArea.Location, values);
}
@@ -303,11 +326,12 @@ namespace FlaxEditor.Surface
/// The surface area to create comment.
/// The comment title.
/// The comment color.
+ /// The comment order or -1 to use default.
/// The comment object
- public SurfaceComment CreateComment(ref Rectangle surfaceArea, string title, Color color)
+ public SurfaceComment CreateComment(ref Rectangle surfaceArea, string title, Color color, int customOrder = -1)
{
// Create comment
- var comment = SpawnComment(ref surfaceArea, title, color);
+ var comment = SpawnComment(ref surfaceArea, title, color, customOrder);
if (comment == null)
{
Editor.LogWarning("Failed to create comment.");
diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs
index 305121e11..cce86d5c0 100644
--- a/Source/Editor/Surface/VisjectSurfaceWindow.cs
+++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs
@@ -17,6 +17,7 @@ using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Surface
{
@@ -258,6 +259,11 @@ namespace FlaxEditor.Surface
///
public IVisjectSurfaceWindow Window;
+ ///
+ /// The identifier of the parameter. Empty to auto generate it.
+ ///
+ public Guid Id = Guid.NewGuid();
+
///
/// True if adding, false if removing parameter.
///
@@ -278,6 +284,11 @@ namespace FlaxEditor.Surface
///
public ScriptType Type;
+ ///
+ /// The value to initialize the parameter with. Can be null to use default one for the parameter type.
+ ///
+ public object InitValue;
+
///
public string ActionString => IsAdd ? "Add parameter" : "Remove parameter";
@@ -304,7 +315,14 @@ namespace FlaxEditor.Surface
var type = Type;
if (IsAdd && type.Type == typeof(NormalMap))
type = new ScriptType(typeof(Texture));
- var param = SurfaceParameter.Create(type, Name);
+ var param = new SurfaceParameter
+ {
+ ID = Id,
+ IsPublic = true,
+ Name = Name,
+ Type = type,
+ Value = InitValue ?? TypeUtils.GetDefaultValue(type),
+ };
if (IsAdd && Type.Type == typeof(NormalMap))
param.Value = FlaxEngine.Content.LoadAsyncInternal("Engine/Textures/NormalTexture");
Window.VisjectSurface.Parameters.Insert(Index, param);
@@ -725,6 +743,8 @@ namespace FlaxEditor.Surface
protected VisjectSurfaceWindow(Editor editor, AssetItem item, bool useTabs = false)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new FlaxEditor.Undo();
_undo.UndoDone += OnUndoRedo;
@@ -775,10 +795,10 @@ namespace FlaxEditor.Surface
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _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)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)");
+ _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
_toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph");
// Setup input actions
@@ -1058,7 +1078,6 @@ namespace FlaxEditor.Surface
public virtual void OnParamRemoveUndo()
{
_refreshPropertiesOnLoad = true;
- //_propertiesEditor.BuildLayoutOnUpdate();
_propertiesEditor.BuildLayout();
}
diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs
index 902582311..2f8d4cda8 100644
--- a/Source/Editor/Surface/VisualScriptSurface.cs
+++ b/Source/Editor/Surface/VisualScriptSurface.cs
@@ -144,7 +144,7 @@ namespace FlaxEditor.Surface
}
///
- protected override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
+ protected internal override NodeArchetype GetParameterGetterNodeArchetype(out ushort groupId)
{
groupId = 6;
return Archetypes.Parameters.Nodes[2];
diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs
index 226c7b49c..28536ea83 100644
--- a/Source/Editor/Tools/ClothPainting.cs
+++ b/Source/Editor/Tools/ClothPainting.cs
@@ -18,21 +18,25 @@ namespace FlaxEngine.Tools
///
/// Brush radius (world-space).
///
+ [Limit(0)]
public float BrushSize = 50.0f;
///
/// Brush paint intensity.
///
+ [Limit(0)]
public float BrushStrength = 2.0f;
///
/// Brush paint falloff. Hardens or softens painting.
///
+ [Limit(0)]
public float BrushFalloff = 1.5f;
///
/// Value to paint with. Hold Ctrl hey to paint with inverse value (1 - value).
///
+ [Limit(0, 1, 0.01f)]
public float PaintValue = 0.0f;
///
@@ -225,6 +229,7 @@ namespace FlaxEngine.Tools
var cloth = _cloth;
if (cloth == null)
return;
+ var hasPaintInput = Owner.IsLeftMouseButtonDown && !Owner.IsAltKeyDown;
// Perform detailed tracing to find cursor location for the brush
var ray = Owner.MouseRay;
@@ -240,7 +245,7 @@ namespace FlaxEngine.Tools
// Cursor hit other object or nothing
PaintEnd();
- if (Owner.IsLeftMouseButtonDown)
+ if (hasPaintInput)
{
// Select something else
var view = new Ray(Owner.ViewPosition, Owner.ViewDirection);
@@ -253,7 +258,7 @@ namespace FlaxEngine.Tools
}
// Handle painting
- if (Owner.IsLeftMouseButtonDown)
+ if (hasPaintInput)
PaintStart();
else
PaintEnd();
diff --git a/Source/Editor/Tools/Terrain/EditTab.cs b/Source/Editor/Tools/Terrain/EditTab.cs
index 008be64f4..d52c1ae1d 100644
--- a/Source/Editor/Tools/Terrain/EditTab.cs
+++ b/Source/Editor/Tools/Terrain/EditTab.cs
@@ -290,7 +290,7 @@ namespace FlaxEditor.Tools.Terrain
var patchCoord = Gizmo.SelectedPatchCoord;
var chunkCoord = Gizmo.SelectedChunkCoord;
- var action = new EditChunkMaterialAction(CarveTab.SelectedTerrain, ref patchCoord, ref chunkCoord, _chunkOverrideMaterial.SelectedAsset as MaterialBase);
+ var action = new EditChunkMaterialAction(CarveTab.SelectedTerrain, ref patchCoord, ref chunkCoord, _chunkOverrideMaterial.Validator.SelectedAsset as MaterialBase);
action.Do();
CarveTab.Editor.Undo.AddAction(action);
}
@@ -336,12 +336,12 @@ namespace FlaxEditor.Tools.Terrain
_isUpdatingUI = true;
if (terrain.HasPatch(ref patchCoord))
{
- _chunkOverrideMaterial.SelectedAsset = terrain.GetChunkOverrideMaterial(ref patchCoord, ref chunkCoord);
+ _chunkOverrideMaterial.Validator.SelectedAsset = terrain.GetChunkOverrideMaterial(ref patchCoord, ref chunkCoord);
_chunkOverrideMaterial.Enabled = true;
}
else
{
- _chunkOverrideMaterial.SelectedAsset = null;
+ _chunkOverrideMaterial.Validator.SelectedAsset = null;
_chunkOverrideMaterial.Enabled = false;
}
_isUpdatingUI = false;
diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs
index e578933cf..bf2e840ea 100644
--- a/Source/Editor/Viewport/Cameras/FPSCamera.cs
+++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs
@@ -259,7 +259,10 @@ namespace FlaxEditor.Viewport.Cameras
// Pan
if (input.IsPanning)
{
- var panningSpeed = 0.8f;
+ var panningSpeed = (Viewport.RelativePanning)
+ ? Mathf.Abs((position - TargetPoint).Length) * 0.005f
+ : Viewport.PanningSpeed;
+
if (Viewport.InvertPanning)
{
position += up * (mouseDelta.Y * panningSpeed);
diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs
index a24dd2f38..fe579e7e5 100644
--- a/Source/Editor/Viewport/EditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/EditorGizmoViewport.cs
@@ -86,6 +86,9 @@ namespace FlaxEditor.Viewport
///
public abstract void Select(List nodes);
+ ///
+ public abstract void Spawn(Actor actor);
+
///
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index 9a444f2da..c49392d01 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -128,12 +128,26 @@ namespace FlaxEditor.Viewport
public const int FpsCameraFilteringFrames = 3;
///
- /// The speed widget button.
+ /// The camera settings widget.
///
- protected ViewportWidgetButton _speedWidget;
+ protected ViewportWidgetsContainer _cameraWidget;
+
+ ///
+ /// The camera settings widget button.
+ ///
+ protected ViewportWidgetButton _cameraButton;
+
+ ///
+ /// The orthographic mode widget button.
+ ///
+ protected ViewportWidgetButton _orthographicModeButton;
+
+ private readonly Editor _editor;
private float _mouseSensitivity;
private float _movementSpeed;
+ private float _minMovementSpeed;
+ private float _maxMovementSpeed;
private float _mouseAccelerationScale;
private bool _useMouseFiltering;
private bool _useMouseAcceleration;
@@ -174,11 +188,17 @@ namespace FlaxEditor.Viewport
private float _fieldOfView;
private float _nearPlane;
private float _farPlane;
- private float _orthoSize = 1.0f;
- private bool _isOrtho = false;
- private float _wheelMovementChangeDeltaSum = 0;
+ private float _orthoSize;
+ private bool _isOrtho;
+ private bool _useCameraEasing;
+ private float _cameraEasingDegree;
+ private float _panningSpeed;
+ private bool _relativePanning;
private bool _invertPanning;
+ private int _speedStep;
+ private int _maxSpeedSteps;
+
///
/// Speed of the mouse.
///
@@ -189,6 +209,25 @@ namespace FlaxEditor.Viewport
///
public float MouseWheelZoomSpeedFactor = 1;
+ ///
+ /// Format of the text for the camera move speed.
+ ///
+ private string MovementSpeedTextFormat
+ {
+ get
+ {
+ if (Mathf.Abs(_movementSpeed - _maxMovementSpeed) < Mathf.Epsilon || Mathf.Abs(_movementSpeed - _minMovementSpeed) < Mathf.Epsilon)
+ return "{0:0.##}";
+
+ if (_movementSpeed < 10.0f)
+ return "{0:0.00}";
+ else if (_movementSpeed < 100.0f)
+ return "{0:0.0}";
+ else
+ return "{0:#}";
+ }
+ }
+
///
/// Gets or sets the camera movement speed.
///
@@ -197,19 +236,40 @@ namespace FlaxEditor.Viewport
get => _movementSpeed;
set
{
- for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
- {
- if (Math.Abs(value - EditorViewportCameraSpeedValues[i]) < 0.001f)
- {
- _movementSpeed = EditorViewportCameraSpeedValues[i];
- if (_speedWidget != null)
- _speedWidget.Text = _movementSpeed.ToString();
- break;
- }
- }
+ _movementSpeed = value;
+
+ if (_cameraButton != null)
+ _cameraButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed);
}
}
+ ///
+ /// Gets or sets the minimum camera movement speed.
+ ///
+ public float MinMovementSpeed
+ {
+ get => _minMovementSpeed;
+ set => _minMovementSpeed = value;
+ }
+
+ ///
+ /// Gets or sets the maximum camera movement speed.
+ ///
+ public float MaxMovementSpeed
+ {
+ get => _maxMovementSpeed;
+ set => _maxMovementSpeed = value;
+ }
+
+ ///
+ /// Gets or sets the camera easing mode.
+ ///
+ public bool UseCameraEasing
+ {
+ get => _useCameraEasing;
+ set => _useCameraEasing = value;
+ }
+
///
/// Gets the mouse movement position delta (user press and move).
///
@@ -396,6 +456,15 @@ namespace FlaxEditor.Viewport
set => _isOrtho = value;
}
+ ///
+ /// Gets or sets if the panning speed should be relative to the camera target.
+ ///
+ public bool RelativePanning
+ {
+ get => _relativePanning;
+ set => _relativePanning = value;
+ }
+
///
/// Gets or sets if the panning direction is inverted.
///
@@ -405,6 +474,15 @@ namespace FlaxEditor.Viewport
set => _invertPanning = value;
}
+ ///
+ /// Gets or sets the camera panning speed.
+ ///
+ public float PanningSpeed
+ {
+ get => _panningSpeed;
+ set => _panningSpeed = value;
+ }
+
///
/// The input actions collection to processed during user input.
///
@@ -419,6 +497,8 @@ namespace FlaxEditor.Viewport
public EditorViewport(SceneRenderTask task, ViewportCamera camera, bool useWidgets)
: base(task)
{
+ _editor = Editor.Instance;
+
_mouseAccelerationScale = 0.1f;
_useMouseFiltering = false;
_useMouseAcceleration = false;
@@ -431,43 +511,299 @@ namespace FlaxEditor.Viewport
// Setup options
{
- var options = Editor.Instance.Options.Options;
- _movementSpeed = options.Viewport.DefaultMovementSpeed;
- _nearPlane = options.Viewport.DefaultNearPlane;
- _farPlane = options.Viewport.DefaultFarPlane;
- _fieldOfView = options.Viewport.DefaultFieldOfView;
- _invertPanning = options.Viewport.DefaultInvertPanning;
-
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
- OnEditorOptionsChanged(options);
+ SetupViewportOptions();
}
+ // Initialize camera values from cache
+ if (_editor.ProjectCache.TryGetCustomData("CameraMovementSpeedValue", out var cachedState))
+ MovementSpeed = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraMinMovementSpeedValue", out cachedState))
+ _minMovementSpeed = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraMaxMovementSpeedValue", out cachedState))
+ _maxMovementSpeed = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("UseCameraEasingState", out cachedState))
+ _useCameraEasing = bool.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraPanningSpeedValue", out cachedState))
+ _panningSpeed = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraInvertPanningState", out cachedState))
+ _invertPanning = bool.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraRelativePanningState", out cachedState))
+ _relativePanning = bool.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraOrthographicState", out cachedState))
+ _isOrtho = bool.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraOrthographicSizeValue", out cachedState))
+ _orthoSize = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraFieldOfViewValue", out cachedState))
+ _fieldOfView = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraNearPlaneValue", out cachedState))
+ _nearPlane = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraFarPlaneValue", out cachedState))
+ _farPlane = float.Parse(cachedState);
+
+ OnCameraMovementProgressChanged();
+
if (useWidgets)
{
- var largestText = "Invert Panning";
+ #region Camera settings widget
+
+ var largestText = "Relative Panning";
var textSize = Style.Current.FontMedium.MeasureText(largestText);
var xLocationForExtras = textSize.X + 5;
- // Camera speed widget
- var camSpeed = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
- var camSpeedCM = new ContextMenu();
- var camSpeedButton = new ViewportWidgetButton(_movementSpeed.ToString(), Editor.Instance.Icons.CamSpeed32, camSpeedCM)
+ var cameraSpeedTextWidth = Style.Current.FontMedium.MeasureText("0.00").X;
+
+ // Camera Settings Widget
+ _cameraWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
+
+ // Camera Settings Menu
+ var cameraCM = new ContextMenu();
+ _cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), Editor.Instance.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth)
{
Tag = this,
- TooltipText = "Camera speed scale"
+ TooltipText = "Camera Settings",
+ Parent = _cameraWidget
};
- _speedWidget = camSpeedButton;
- for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
- {
- var v = EditorViewportCameraSpeedValues[i];
- var button = camSpeedCM.AddButton(v.ToString());
- button.Tag = v;
- }
- camSpeedCM.ButtonClicked += button => MovementSpeed = (float)button.Tag;
- camSpeedCM.VisibleChanged += WidgetCamSpeedShowHide;
- camSpeedButton.Parent = camSpeed;
- camSpeed.Parent = this;
+ _cameraWidget.Parent = this;
+
+ // Orthographic/Perspective Mode Widget
+ _orthographicModeButton = new ViewportWidgetButton(string.Empty, Editor.Instance.Icons.CamSpeed32, null, true)
+ {
+ Checked = !_isOrtho,
+ TooltipText = "Toggle Orthographic/Perspective Mode",
+ Parent = _cameraWidget
+ };
+ _orthographicModeButton.Toggled += OnOrthographicModeToggled;
+
+ // Camera Speed
+ var camSpeedButton = cameraCM.AddButton("Camera Speed");
+ camSpeedButton.CloseMenuOnClick = false;
+ var camSpeedValue = new FloatValueBox(_movementSpeed, xLocationForExtras, 2, 70.0f, _minMovementSpeed, _maxMovementSpeed, 0.5f)
+ {
+ Parent = camSpeedButton
+ };
+
+ camSpeedValue.ValueChanged += () => OnMovementSpeedChanged(camSpeedValue);
+ cameraCM.VisibleChanged += control => camSpeedValue.Value = _movementSpeed;
+
+ // Minimum & Maximum Camera Speed
+ var minCamSpeedButton = cameraCM.AddButton("Min Cam Speed");
+ minCamSpeedButton.CloseMenuOnClick = false;
+ var minCamSpeedValue = new FloatValueBox(_minMovementSpeed, xLocationForExtras, 2, 70.0f, 0.05f, _maxMovementSpeed, 0.5f)
+ {
+ Parent = minCamSpeedButton
+ };
+ var maxCamSpeedButton = cameraCM.AddButton("Max Cam Speed");
+ maxCamSpeedButton.CloseMenuOnClick = false;
+ var maxCamSpeedValue = new FloatValueBox(_maxMovementSpeed, xLocationForExtras, 2, 70.0f, _minMovementSpeed, 1000.0f, 0.5f)
+ {
+ Parent = maxCamSpeedButton
+ };
+
+ minCamSpeedValue.ValueChanged += () =>
+ {
+ OnMinMovementSpeedChanged(minCamSpeedValue);
+
+ maxCamSpeedValue.MinValue = minCamSpeedValue.Value;
+
+ if (Math.Abs(camSpeedValue.MinValue - minCamSpeedValue.Value) > Mathf.Epsilon)
+ camSpeedValue.MinValue = minCamSpeedValue.Value;
+ };
+ cameraCM.VisibleChanged += control => minCamSpeedValue.Value = _minMovementSpeed;
+ maxCamSpeedValue.ValueChanged += () =>
+ {
+ OnMaxMovementSpeedChanged(maxCamSpeedValue);
+
+ minCamSpeedValue.MaxValue = maxCamSpeedValue.Value;
+
+ if (Math.Abs(camSpeedValue.MaxValue - maxCamSpeedValue.Value) > Mathf.Epsilon)
+ camSpeedValue.MaxValue = maxCamSpeedValue.Value;
+ };
+ cameraCM.VisibleChanged += control => maxCamSpeedValue.Value = _maxMovementSpeed;
+
+ // Camera Easing
+ {
+ var useCameraEasing = cameraCM.AddButton("Camera Easing");
+ useCameraEasing.CloseMenuOnClick = false;
+ var useCameraEasingValue = new CheckBox(xLocationForExtras, 2, _useCameraEasing)
+ {
+ Parent = useCameraEasing
+ };
+
+ useCameraEasingValue.StateChanged += OnCameraEasingToggled;
+ cameraCM.VisibleChanged += control => useCameraEasingValue.Checked = _useCameraEasing;
+ }
+
+ // Panning Speed
+ {
+ var panningSpeed = cameraCM.AddButton("Panning Speed");
+ panningSpeed.CloseMenuOnClick = false;
+ var panningSpeedValue = new FloatValueBox(_panningSpeed, xLocationForExtras, 2, 70.0f, 0.01f, 128.0f, 0.1f)
+ {
+ Parent = panningSpeed
+ };
+
+ panningSpeedValue.ValueChanged += () => OnPanningSpeedChanged(panningSpeedValue);
+ cameraCM.VisibleChanged += control =>
+ {
+ panningSpeed.Visible = !_relativePanning;
+ panningSpeedValue.Value = _panningSpeed;
+ };
+ }
+
+ // Relative Panning
+ {
+ var relativePanning = cameraCM.AddButton("Relative Panning");
+ relativePanning.CloseMenuOnClick = false;
+ var relativePanningValue = new CheckBox(xLocationForExtras, 2, _relativePanning)
+ {
+ Parent = relativePanning
+ };
+
+ relativePanningValue.StateChanged += checkBox =>
+ {
+ if (checkBox.Checked != _relativePanning)
+ {
+ OnRelativePanningToggled(checkBox);
+ cameraCM.Hide();
+ }
+ };
+ cameraCM.VisibleChanged += control => relativePanningValue.Checked = _relativePanning;
+ }
+
+ // Invert Panning
+ {
+ var invertPanning = cameraCM.AddButton("Invert Panning");
+ invertPanning.CloseMenuOnClick = false;
+ var invertPanningValue = new CheckBox(xLocationForExtras, 2, _invertPanning)
+ {
+ Parent = invertPanning
+ };
+
+ invertPanningValue.StateChanged += OnInvertPanningToggled;
+ cameraCM.VisibleChanged += control => invertPanningValue.Checked = _invertPanning;
+ }
+
+ cameraCM.AddSeparator();
+
+ // Camera Viewpoints
+ {
+ var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu;
+ for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++)
+ {
+ var co = EditorViewportCameraViewpointValues[i];
+ var button = cameraView.AddButton(co.Name);
+ button.Tag = co.Orientation;
+ }
+
+ cameraView.ButtonClicked += OnViewpointChanged;
+ }
+
+ // Orthographic Mode
+ {
+ var ortho = cameraCM.AddButton("Orthographic");
+ ortho.CloseMenuOnClick = false;
+ var orthoValue = new CheckBox(xLocationForExtras, 2, _isOrtho)
+ {
+ Parent = ortho
+ };
+
+ orthoValue.StateChanged += checkBox =>
+ {
+ if (checkBox.Checked != _isOrtho)
+ {
+ OnOrthographicModeToggled(checkBox);
+ cameraCM.Hide();
+ }
+ };
+ cameraCM.VisibleChanged += control => orthoValue.Checked = _isOrtho;
+ }
+
+ // Field of View
+ {
+ var fov = cameraCM.AddButton("Field Of View");
+ fov.CloseMenuOnClick = false;
+ var fovValue = new FloatValueBox(_fieldOfView, xLocationForExtras, 2, 70.0f, 35.0f, 160.0f, 0.1f)
+ {
+ Parent = fov
+ };
+
+ fovValue.ValueChanged += () => OnFieldOfViewChanged(fovValue);
+ cameraCM.VisibleChanged += control =>
+ {
+ fov.Visible = !_isOrtho;
+ fovValue.Value = _fieldOfView;
+ };
+ }
+
+ // Orthographic Scale
+ {
+ var orthoSize = cameraCM.AddButton("Ortho Scale");
+ orthoSize.CloseMenuOnClick = false;
+ var orthoSizeValue = new FloatValueBox(_orthoSize, xLocationForExtras, 2, 70.0f, 0.001f, 100000.0f, 0.01f)
+ {
+ Parent = orthoSize
+ };
+
+ orthoSizeValue.ValueChanged += () => OnOrthographicSizeChanged(orthoSizeValue);
+ cameraCM.VisibleChanged += control =>
+ {
+ orthoSize.Visible = _isOrtho;
+ orthoSizeValue.Value = _orthoSize;
+ };
+ }
+
+ // Near Plane
+ {
+ var nearPlane = cameraCM.AddButton("Near Plane");
+ nearPlane.CloseMenuOnClick = false;
+ var nearPlaneValue = new FloatValueBox(_nearPlane, xLocationForExtras, 2, 70.0f, 0.001f, 1000.0f)
+ {
+ Parent = nearPlane
+ };
+
+ nearPlaneValue.ValueChanged += () => OnNearPlaneChanged(nearPlaneValue);
+ cameraCM.VisibleChanged += control => nearPlaneValue.Value = _nearPlane;
+ }
+
+ // Far Plane
+ {
+ var farPlane = cameraCM.AddButton("Far Plane");
+ farPlane.CloseMenuOnClick = false;
+ var farPlaneValue = new FloatValueBox(_farPlane, xLocationForExtras, 2, 70.0f, 10.0f)
+ {
+ Parent = farPlane
+ };
+
+ farPlaneValue.ValueChanged += () => OnFarPlaneChanged(farPlaneValue);
+ cameraCM.VisibleChanged += control => farPlaneValue.Value = _farPlane;
+ }
+
+ cameraCM.AddSeparator();
+
+ // Reset Button
+ {
+ var reset = cameraCM.AddButton("Reset to default");
+ reset.ButtonClicked += button =>
+ {
+ SetupViewportOptions();
+
+ // if the context menu is opened without triggering the value changes beforehand,
+ // the movement speed will not be correctly reset to its default value in certain cases
+ // therefore, a UI update needs to be triggered here
+ minCamSpeedValue.Value = _minMovementSpeed;
+ camSpeedValue.Value = _movementSpeed;
+ maxCamSpeedValue.Value = _maxMovementSpeed;
+ };
+ }
+
+ #endregion Camera settings widget
+
+ #region View mode widget
+
+ largestText = "Brightness";
+ textSize = Style.Current.FontMedium.MeasureText(largestText);
+ xLocationForExtras = textSize.X + 5;
- // View mode widget
var viewMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
ViewWidgetButtonMenu = new ContextMenu();
var viewModeButton = new ViewportWidgetButton("View", SpriteHandle.Invalid, ViewWidgetButtonMenu)
@@ -484,8 +820,8 @@ namespace FlaxEditor.Viewport
// Show FPS
{
InitFpsCounter();
- _showFpsButon = ViewWidgetShowMenu.AddButton("FPS Counter", () => ShowFpsCounter = !ShowFpsCounter);
- _showFpsButon.CloseMenuOnClick = false;
+ _showFpsButton = ViewWidgetShowMenu.AddButton("FPS Counter", () => ShowFpsCounter = !ShowFpsCounter);
+ _showFpsButton.CloseMenuOnClick = false;
}
}
@@ -610,104 +946,6 @@ namespace FlaxEditor.Viewport
ViewWidgetButtonMenu.AddSeparator();
- // Orthographic
- {
- var ortho = ViewWidgetButtonMenu.AddButton("Orthographic");
- ortho.CloseMenuOnClick = false;
- var orthoValue = new CheckBox(xLocationForExtras, 2, _isOrtho)
- {
- Parent = ortho
- };
- orthoValue.StateChanged += checkBox =>
- {
- if (checkBox.Checked != _isOrtho)
- {
- _isOrtho = checkBox.Checked;
- ViewWidgetButtonMenu.Hide();
- if (_isOrtho)
- {
- var orient = ViewOrientation;
- OrientViewport(ref orient);
- }
- }
- };
- ViewWidgetButtonMenu.VisibleChanged += control => orthoValue.Checked = _isOrtho;
- }
-
- // Camera Viewpoints
- {
- var cameraView = ViewWidgetButtonMenu.AddChildMenu("Viewpoints").ContextMenu;
- for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++)
- {
- var co = EditorViewportCameraViewpointValues[i];
- var button = cameraView.AddButton(co.Name);
- button.Tag = co.Orientation;
- }
- cameraView.ButtonClicked += button =>
- {
- var orient = Quaternion.Euler((Float3)button.Tag);
- OrientViewport(ref orient);
- };
- }
-
- // Field of View
- {
- var fov = ViewWidgetButtonMenu.AddButton("Field Of View");
- fov.CloseMenuOnClick = false;
- var fovValue = new FloatValueBox(1, xLocationForExtras, 2, 70.0f, 35.0f, 160.0f, 0.1f)
- {
- Parent = fov
- };
-
- fovValue.ValueChanged += () => _fieldOfView = fovValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += control =>
- {
- fov.Visible = !_isOrtho;
- fovValue.Value = _fieldOfView;
- };
- }
-
- // Ortho Scale
- {
- var orthoSize = ViewWidgetButtonMenu.AddButton("Ortho Scale");
- orthoSize.CloseMenuOnClick = false;
- var orthoSizeValue = new FloatValueBox(_orthoSize, xLocationForExtras, 2, 70.0f, 0.001f, 100000.0f, 0.01f)
- {
- Parent = orthoSize
- };
-
- orthoSizeValue.ValueChanged += () => _orthoSize = orthoSizeValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += control =>
- {
- orthoSize.Visible = _isOrtho;
- orthoSizeValue.Value = _orthoSize;
- };
- }
-
- // Near Plane
- {
- var nearPlane = ViewWidgetButtonMenu.AddButton("Near Plane");
- nearPlane.CloseMenuOnClick = false;
- var nearPlaneValue = new FloatValueBox(2.0f, xLocationForExtras, 2, 70.0f, 0.001f, 1000.0f)
- {
- Parent = nearPlane
- };
- nearPlaneValue.ValueChanged += () => _nearPlane = nearPlaneValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += control => nearPlaneValue.Value = _nearPlane;
- }
-
- // Far Plane
- {
- var farPlane = ViewWidgetButtonMenu.AddButton("Far Plane");
- farPlane.CloseMenuOnClick = false;
- var farPlaneValue = new FloatValueBox(1000, xLocationForExtras, 2, 70.0f, 10.0f)
- {
- Parent = farPlane
- };
- farPlaneValue.ValueChanged += () => _farPlane = farPlaneValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += control => farPlaneValue.Value = _farPlane;
- }
-
// Brightness
{
var brightness = ViewWidgetButtonMenu.AddButton("Brightness");
@@ -732,24 +970,7 @@ namespace FlaxEditor.Viewport
ViewWidgetButtonMenu.VisibleChanged += control => resolutionValue.Value = ResolutionScale;
}
- // Invert Panning
- {
- var invert = ViewWidgetButtonMenu.AddButton("Invert Panning");
- invert.CloseMenuOnClick = false;
- var invertValue = new CheckBox(xLocationForExtras, 2, _invertPanning)
- {
- Parent = invert
- };
-
- invertValue.StateChanged += checkBox =>
- {
- if (checkBox.Checked != _invertPanning)
- {
- _invertPanning = checkBox.Checked;
- }
- };
- ViewWidgetButtonMenu.VisibleChanged += control => invertValue.Checked = _invertPanning;
- }
+ #endregion View mode widget
}
InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Top").Orientation)));
@@ -766,6 +987,135 @@ namespace FlaxEditor.Viewport
task.Begin += OnRenderBegin;
}
+ ///
+ /// Sets the viewport options to the default values.
+ ///
+ private void SetupViewportOptions()
+ {
+ var options = Editor.Instance.Options.Options;
+ _minMovementSpeed = options.Viewport.MinMovementSpeed;
+ MovementSpeed = options.Viewport.MovementSpeed;
+ _maxMovementSpeed = options.Viewport.MaxMovementSpeed;
+ _useCameraEasing = options.Viewport.UseCameraEasing;
+ _panningSpeed = options.Viewport.PanningSpeed;
+ _invertPanning = options.Viewport.InvertPanning;
+ _relativePanning = options.Viewport.UseRelativePanning;
+
+ _isOrtho = options.Viewport.UseOrthographicProjection;
+ _orthoSize = options.Viewport.OrthographicScale;
+ _fieldOfView = options.Viewport.FieldOfView;
+ _nearPlane = options.Viewport.NearPlane;
+ _farPlane = options.Viewport.FarPlane;
+
+ OnEditorOptionsChanged(options);
+ }
+
+ private void OnMovementSpeedChanged(FloatValueBox control)
+ {
+ var value = Mathf.Clamp(control.Value, _minMovementSpeed, _maxMovementSpeed);
+ MovementSpeed = value;
+
+ OnCameraMovementProgressChanged();
+ _editor.ProjectCache.SetCustomData("CameraMovementSpeedValue", _movementSpeed.ToString());
+ }
+
+ private void OnMinMovementSpeedChanged(FloatValueBox control)
+ {
+ var value = Mathf.Clamp(control.Value, 0.05f, _maxMovementSpeed);
+ _minMovementSpeed = value;
+
+ if (_movementSpeed < value)
+ MovementSpeed = value;
+
+ OnCameraMovementProgressChanged();
+ _editor.ProjectCache.SetCustomData("CameraMinMovementSpeedValue", _minMovementSpeed.ToString());
+ }
+
+ private void OnMaxMovementSpeedChanged(FloatValueBox control)
+ {
+ var value = Mathf.Clamp(control.Value, _minMovementSpeed, 1000.0f);
+ _maxMovementSpeed = value;
+
+ if (_movementSpeed > value)
+ MovementSpeed = value;
+
+ OnCameraMovementProgressChanged();
+ _editor.ProjectCache.SetCustomData("CameraMaxMovementSpeedValue", _maxMovementSpeed.ToString());
+ }
+
+ private void OnCameraEasingToggled(Control control)
+ {
+ _useCameraEasing = !_useCameraEasing;
+
+ OnCameraMovementProgressChanged();
+ _editor.ProjectCache.SetCustomData("UseCameraEasingState", _useCameraEasing.ToString());
+ }
+
+ private void OnPanningSpeedChanged(FloatValueBox control)
+ {
+ _panningSpeed = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraPanningSpeedValue", _panningSpeed.ToString());
+ }
+
+ private void OnRelativePanningToggled(Control control)
+ {
+ _relativePanning = !_relativePanning;
+ _editor.ProjectCache.SetCustomData("CameraRelativePanningState", _relativePanning.ToString());
+ }
+
+ private void OnInvertPanningToggled(Control control)
+ {
+ _invertPanning = !_invertPanning;
+ _editor.ProjectCache.SetCustomData("CameraInvertPanningState", _invertPanning.ToString());
+ }
+
+
+ private void OnViewpointChanged(ContextMenuButton button)
+ {
+ var orient = Quaternion.Euler((Float3)button.Tag);
+ OrientViewport(ref orient);
+ }
+
+ private void OnFieldOfViewChanged(FloatValueBox control)
+ {
+ _fieldOfView = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraFieldOfViewValue", _fieldOfView.ToString());
+ }
+
+ private void OnOrthographicModeToggled(Control control)
+ {
+ _isOrtho = !_isOrtho;
+
+ if (_orthographicModeButton != null)
+ _orthographicModeButton.Checked = !_isOrtho;
+
+ if (_isOrtho)
+ {
+ var orient = ViewOrientation;
+ OrientViewport(ref orient);
+ }
+
+ _editor.ProjectCache.SetCustomData("CameraOrthographicState", _isOrtho.ToString());
+ }
+
+ private void OnOrthographicSizeChanged(FloatValueBox control)
+ {
+ _orthoSize = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraOrthographicSizeValue", _orthoSize.ToString());
+ }
+
+ private void OnNearPlaneChanged(FloatValueBox control)
+ {
+ _nearPlane = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraNearPlaneValue", _nearPlane.ToString());
+ }
+
+ private void OnFarPlaneChanged(FloatValueBox control)
+ {
+ _farPlane = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraNearPlaneValue", _farPlane.ToString());
+ }
+
///
/// Gets a value indicating whether this viewport is using mouse currently (eg. user moving objects).
///
@@ -798,33 +1148,59 @@ namespace FlaxEditor.Viewport
}
}
+ private void OnCameraMovementProgressChanged()
+ {
+ // prevent NaN
+ if (Math.Abs(_minMovementSpeed - _maxMovementSpeed) < Mathf.Epsilon)
+ {
+ _speedStep = 0;
+ return;
+ }
+
+ if (Math.Abs(_movementSpeed - _maxMovementSpeed) < Mathf.Epsilon)
+ {
+ _speedStep = _maxSpeedSteps;
+ return;
+ }
+ else if (Math.Abs(_movementSpeed - _minMovementSpeed) < Mathf.Epsilon)
+ {
+ _speedStep = 0;
+ return;
+ }
+
+ // calculate current linear/eased progress
+ var progress = Mathf.Remap(_movementSpeed, _minMovementSpeed, _maxMovementSpeed, 0.0f, 1.0f);
+
+ if (_useCameraEasing)
+ progress = Mathf.Pow(progress, 1.0f / _cameraEasingDegree);
+
+ _speedStep = Mathf.RoundToInt(progress * _maxSpeedSteps);
+ }
+
///
/// Increases or decreases the camera movement speed.
///
/// The stepping direction for speed adjustment.
protected void AdjustCameraMoveSpeed(int step)
{
- int camValueIndex = -1;
- for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
- {
- if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed))
- {
- camValueIndex = i;
- break;
- }
- }
- if (camValueIndex == -1)
- return;
+ _speedStep = Mathf.Clamp(_speedStep + step, 0, _maxSpeedSteps);
- if (step > 0)
- MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)];
- else if (step < 0)
- MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)];
+ // calculate new linear/eased progress
+ var progress = _useCameraEasing
+ ? Mathf.Pow((float)_speedStep / _maxSpeedSteps, _cameraEasingDegree)
+ : (float)_speedStep / _maxSpeedSteps;
+
+ var speed = Mathf.Lerp(_minMovementSpeed, _maxMovementSpeed, progress);
+ MovementSpeed = (float)Math.Round(speed, 3);
+ _editor.ProjectCache.SetCustomData("CameraMovementSpeedValue", _movementSpeed.ToString());
}
private void OnEditorOptionsChanged(EditorOptions options)
{
_mouseSensitivity = options.Viewport.MouseSensitivity;
+ _maxSpeedSteps = options.Viewport.TotalCameraSpeedSteps;
+ _cameraEasingDegree = options.Viewport.CameraEasingDegree;
+ OnCameraMovementProgressChanged();
}
private void OnRenderBegin(RenderTask task, GPUContext context)
@@ -863,7 +1239,7 @@ namespace FlaxEditor.Viewport
}
private FpsCounter _fpsCounter;
- private ContextMenuButton _showFpsButon;
+ private ContextMenuButton _showFpsButton;
///
/// Gets or sets a value indicating whether show or hide FPS counter.
@@ -875,7 +1251,7 @@ namespace FlaxEditor.Viewport
{
_fpsCounter.Visible = value;
_fpsCounter.Enabled = value;
- _showFpsButon.Icon = value ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
+ _showFpsButton.Icon = value ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
@@ -1016,8 +1392,6 @@ namespace FlaxEditor.Viewport
/// The parent window.
protected virtual void OnControlMouseBegin(Window win)
{
- _wheelMovementChangeDeltaSum = 0;
-
// Hide cursor and start tracking mouse movement
win.StartTrackingMouse(false);
win.Cursor = CursorType.Hidden;
@@ -1113,8 +1487,8 @@ namespace FlaxEditor.Viewport
_camera.Update(deltaTime);
useMovementSpeed = _camera.UseMovementSpeed;
- if (_speedWidget != null)
- _speedWidget.Parent.Visible = useMovementSpeed;
+ if (_cameraButton != null)
+ _cameraButton.Parent.Visible = useMovementSpeed;
}
// Get parent window
@@ -1217,18 +1591,8 @@ namespace FlaxEditor.Viewport
rmbWheel = useMovementSpeed && (_input.IsMouseRightDown || _isVirtualMouseRightDown) && wheelInUse;
if (rmbWheel)
{
- const float step = 4.0f;
- _wheelMovementChangeDeltaSum += _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity;
- if (_wheelMovementChangeDeltaSum >= step)
- {
- _wheelMovementChangeDeltaSum -= step;
- AdjustCameraMoveSpeed(1);
- }
- else if (_wheelMovementChangeDeltaSum <= -step)
- {
- _wheelMovementChangeDeltaSum += step;
- AdjustCameraMoveSpeed(-1);
- }
+ var step = _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity;
+ AdjustCameraMoveSpeed(step > 0.0f ? 1 : -1);
}
}
@@ -1497,22 +1861,6 @@ namespace FlaxEditor.Viewport
new CameraViewpoint("Bottom", new Float3(-90, 0, 0))
};
- private readonly float[] EditorViewportCameraSpeedValues =
- {
- 0.05f,
- 0.1f,
- 0.25f,
- 0.5f,
- 1.0f,
- 2.0f,
- 4.0f,
- 6.0f,
- 8.0f,
- 16.0f,
- 32.0f,
- 64.0f,
- };
-
private struct ViewModeOptions
{
public readonly string Name;
@@ -1568,24 +1916,6 @@ namespace FlaxEditor.Viewport
new ViewModeOptions(ViewMode.GlobalIllumination, "Global Illumination"),
};
- private void WidgetCamSpeedShowHide(Control cm)
- {
- if (cm.Visible == false)
- return;
-
- var ccm = (ContextMenu)cm;
- foreach (var e in ccm.Items)
- {
- if (e is ContextMenuButton b)
- {
- var v = (float)b.Tag;
- b.Icon = Mathf.Abs(MovementSpeed - v) < 0.001f
- ? Style.Current.CheckBoxTick
- : SpriteHandle.Invalid;
- }
- }
- }
-
private void WidgetViewModeShowHideClicked(ContextMenuButton button)
{
if (button.Tag is ViewMode v)
diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
index e85b6c2b3..24c8f1c4c 100644
--- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
@@ -5,9 +5,7 @@ using System.Collections.Generic;
using FlaxEditor.Content;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
-using FlaxEditor.GUI.Drag;
using FlaxEditor.SceneGraph;
-using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.Scripting;
using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Modes;
@@ -37,28 +35,8 @@ namespace FlaxEditor.Viewport
private readonly ViewportWidgetButton _rotateSnapping;
private readonly ViewportWidgetButton _scaleSnapping;
- private readonly DragAssets _dragAssets;
- private readonly DragActorType _dragActorType = new DragActorType(ValidateDragActorType);
-
private SelectionOutline _customSelectionOutline;
- ///
- /// The custom drag drop event arguments.
- ///
- ///
- public class DragDropEventArgs : DragEventArgs
- {
- ///
- /// The hit.
- ///
- public SceneGraphNode Hit;
-
- ///
- /// The hit location.
- ///
- public Vector3 HitLocation;
- }
-
///
/// The editor sprites rendering effect.
///
@@ -137,15 +115,12 @@ namespace FlaxEditor.Viewport
private bool _lockedFocus;
private double _lockedFocusOffset;
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
- private StaticModel _previewStaticModel;
- private int _previewModelEntryIndex;
- private BrushSurface _previewBrushSurface;
private EditorSpritesRenderer _editorSpritesRenderer;
///
/// Drag and drop handlers
///
- public readonly DragHandlers DragHandlers = new DragHandlers();
+ public readonly ViewportDragHandlers DragHandlers;
///
/// The transform gizmo.
@@ -219,7 +194,7 @@ namespace FlaxEditor.Viewport
: base(Object.New(), editor.Undo, editor.Scene.Root)
{
_editor = editor;
- _dragAssets = new DragAssets(ValidateDragItem);
+ DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType);
var inputOptions = editor.Options.Options.Input;
// Prepare rendering task
@@ -408,9 +383,6 @@ namespace FlaxEditor.Viewport
ViewWidgetButtonMenu.AddSeparator();
ViewWidgetButtonMenu.AddButton("Create camera here", CreateCameraAtView);
- DragHandlers.Add(_dragActorType);
- DragHandlers.Add(_dragAssets);
-
// Init gizmo modes
{
// Add default modes used by the editor
@@ -430,7 +402,11 @@ namespace FlaxEditor.Viewport
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
- InputActions.Add(options => options.ToggleTransformSpace, () => { OnTransformSpaceToggle(transformSpaceToggle); transformSpaceToggle.Checked = !transformSpaceToggle.Checked; });
+ InputActions.Add(options => options.ToggleTransformSpace, () =>
+ {
+ OnTransformSpaceToggle(transformSpaceToggle);
+ transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
+ });
InputActions.Add(options => options.LockFocusSelection, LockFocusSelection);
InputActions.Add(options => options.FocusSelection, FocusSelection);
InputActions.Add(options => options.RotateSelection, RotateSelection);
@@ -530,20 +506,9 @@ namespace FlaxEditor.Viewport
private void OnCollectDrawCalls(ref RenderContext renderContext)
{
- if (_previewStaticModel)
- {
- _debugDrawData.HighlightModel(_previewStaticModel, _previewModelEntryIndex);
- }
- if (_previewBrushSurface.Brush != null)
- {
- _debugDrawData.HighlightBrushSurface(_previewBrushSurface);
- }
-
+ DragHandlers.CollectDrawCalls(_debugDrawData, ref renderContext);
if (ShowNavigation)
- {
Editor.Internal_DrawNavMesh();
- }
-
_debugDrawData.OnDraw(ref renderContext);
}
@@ -942,78 +907,14 @@ namespace FlaxEditor.Viewport
base.OnLeftMouseButtonUp();
}
- private void GetHitLocation(ref Float2 location, out SceneGraphNode hit, out Vector3 hitLocation, out Vector3 hitNormal)
- {
- // Get mouse ray and try to hit any object
- var ray = ConvertMouseToRay(ref location);
- var view = new Ray(ViewPosition, ViewDirection);
- var gridPlane = new Plane(Vector3.Zero, Vector3.Up);
- var flags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives;
- hit = Editor.Instance.Scene.Root.RayCast(ref ray, ref view, out var closest, out var normal, flags);
- if (hit != null)
- {
- // Use hit location
- hitLocation = ray.Position + ray.Direction * closest;
- hitNormal = normal;
- }
- else if (Grid.Enabled && CollisionsHelper.RayIntersectsPlane(ref ray, ref gridPlane, out closest) && closest < 4000.0f)
- {
- // Use grid location
- hitLocation = ray.Position + ray.Direction * closest;
- hitNormal = Vector3.Up;
- }
- else
- {
- // Use area in front of the viewport
- hitLocation = ViewPosition + ViewDirection * 100;
- hitNormal = Vector3.Up;
- }
- }
-
- private void SetDragEffects(ref Float2 location)
- {
- if (_dragAssets.HasValidDrag && _dragAssets.Objects[0].IsOfType())
- {
- GetHitLocation(ref location, out var hit, out _, out _);
- ClearDragEffects();
- var material = FlaxEngine.Content.LoadAsync(_dragAssets.Objects[0].ID);
- if (material.IsDecal)
- return;
-
- if (hit is StaticModelNode staticModelNode)
- {
- _previewStaticModel = (StaticModel)staticModelNode.Actor;
- var ray = ConvertMouseToRay(ref location);
- _previewStaticModel.IntersectsEntry(ref ray, out _, out _, out _previewModelEntryIndex);
- }
- else if (hit is BoxBrushNode.SideLinkNode brushSurfaceNode)
- {
- _previewBrushSurface = brushSurfaceNode.Surface;
- }
- }
- }
-
- private void ClearDragEffects()
- {
- _previewStaticModel = null;
- _previewModelEntryIndex = -1;
- _previewBrushSurface = new BrushSurface();
- }
-
///
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
- ClearDragEffects();
-
+ DragHandlers.ClearDragEffects();
var result = base.OnDragEnter(ref location, data);
if (result != DragDropEffect.None)
return result;
-
- result = DragHandlers.OnDragEnter(data);
-
- SetDragEffects(ref location);
-
- return result;
+ return DragHandlers.DragEnter(ref location, data);
}
private bool ValidateDragItem(ContentItem contentItem)
@@ -1042,167 +943,29 @@ namespace FlaxEditor.Viewport
///
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
- ClearDragEffects();
-
+ DragHandlers.ClearDragEffects();
var result = base.OnDragMove(ref location, data);
if (result != DragDropEffect.None)
return result;
-
- SetDragEffects(ref location);
-
- return DragHandlers.Effect;
+ return DragHandlers.DragEnter(ref location, data);
}
///
public override void OnDragLeave()
{
- ClearDragEffects();
-
+ DragHandlers.ClearDragEffects();
DragHandlers.OnDragLeave();
-
base.OnDragLeave();
}
- private Vector3 PostProcessSpawnedActorLocation(Actor actor, ref Vector3 hitLocation)
- {
- // Refresh actor position to ensure that cached bounds are valid
- actor.Position = Vector3.One;
- actor.Position = Vector3.Zero;
-
- // Place the object
- //var location = hitLocation - (box.Size.Length * 0.5f) * ViewDirection;
- var editorBounds = actor.EditorBoxChildren;
- var bottomToCenter = actor.Position.Y - editorBounds.Minimum.Y;
- var location = hitLocation + new Vector3(0, bottomToCenter, 0);
-
- // Apply grid snapping if enabled
- if (UseSnapping || TransformGizmo.TranslationSnapEnable)
- {
- float snapValue = TransformGizmo.TranslationSnapValue;
- location = new Vector3(
- (int)(location.X / snapValue) * snapValue,
- (int)(location.Y / snapValue) * snapValue,
- (int)(location.Z / snapValue) * snapValue);
- }
-
- return location;
- }
-
- private void Spawn(Actor actor, ref Vector3 hitLocation, ref Vector3 hitNormal)
- {
- actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation);
- var parent = actor.Parent ?? Level.GetScene(0);
- actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null);
- Editor.Instance.SceneEditing.Spawn(actor);
- Focus();
- }
-
- private void Spawn(AssetItem item, SceneGraphNode hit, ref Float2 location, ref Vector3 hitLocation, ref Vector3 hitNormal)
- {
- if (item.IsOfType())
- {
- var material = FlaxEngine.Content.LoadAsync(item.ID);
- if (material && !material.WaitForLoaded(100) && material.IsDecal)
- {
- var actor = new Decal
- {
- Material = material,
- LocalOrientation = RootNode.RaycastNormalRotation(ref hitNormal),
- Name = item.ShortName
- };
- DebugDraw.DrawWireArrow(PostProcessSpawnedActorLocation(actor, ref hitNormal), actor.LocalOrientation, 1.0f, Color.Red, 1000000);
- Spawn(actor, ref hitLocation, ref hitNormal);
- }
- else if (hit is StaticModelNode staticModelNode)
- {
- var staticModel = (StaticModel)staticModelNode.Actor;
- var ray = ConvertMouseToRay(ref location);
- if (staticModel.IntersectsEntry(ref ray, out _, out _, out var entryIndex))
- {
- using (new UndoBlock(Undo, staticModel, "Change material"))
- staticModel.SetMaterial(entryIndex, material);
- }
- }
- else if (hit is BoxBrushNode.SideLinkNode brushSurfaceNode)
- {
- using (new UndoBlock(Undo, brushSurfaceNode.Brush, "Change material"))
- {
- var surface = brushSurfaceNode.Surface;
- surface.Material = material;
- brushSurfaceNode.Surface = surface;
- }
- }
- return;
- }
- if (item.IsOfType())
- {
- Editor.Instance.Scene.OpenScene(item.ID, true);
- return;
- }
- {
- var actor = item.OnEditorDrop(this);
- actor.Name = item.ShortName;
- Spawn(actor, ref hitLocation, ref hitNormal);
- }
- }
-
- private void Spawn(ScriptType item, SceneGraphNode hit, ref Float2 location, ref Vector3 hitLocation, ref Vector3 hitNormal)
- {
- var actor = item.CreateInstance() as Actor;
- if (actor == null)
- {
- Editor.LogWarning("Failed to spawn actor of type " + item.TypeName);
- return;
- }
- actor.Name = item.Name;
- Spawn(actor, ref hitLocation, ref hitNormal);
- }
-
///
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
- ClearDragEffects();
-
+ DragHandlers.ClearDragEffects();
var result = base.OnDragDrop(ref location, data);
if (result != DragDropEffect.None)
return result;
-
- // Check if drag sth
- Vector3 hitLocation = ViewPosition, hitNormal = -ViewDirection;
- SceneGraphNode hit = null;
- if (DragHandlers.HasValidDrag)
- {
- GetHitLocation(ref location, out hit, out hitLocation, out hitNormal);
- }
-
- // Drag assets
- if (_dragAssets.HasValidDrag)
- {
- result = _dragAssets.Effect;
-
- // Process items
- for (int i = 0; i < _dragAssets.Objects.Count; i++)
- {
- var item = _dragAssets.Objects[i];
- Spawn(item, hit, ref location, ref hitLocation, ref hitNormal);
- }
- }
- // Drag actor type
- else if (_dragActorType.HasValidDrag)
- {
- result = _dragActorType.Effect;
-
- // Process items
- for (int i = 0; i < _dragActorType.Objects.Count; i++)
- {
- var item = _dragActorType.Objects[i];
- Spawn(item, hit, ref location, ref hitLocation, ref hitNormal);
- }
- }
-
- DragHandlers.OnDragDrop(new DragDropEventArgs { Hit = hit, HitLocation = hitLocation });
-
- return result;
+ return DragHandlers.DragDrop(ref location, data);
}
///
@@ -1211,6 +974,14 @@ namespace FlaxEditor.Viewport
_editor.SceneEditing.Select(nodes);
}
+ ///
+ public override void Spawn(Actor actor)
+ {
+ var parent = actor.Parent ?? Level.GetScene(0);
+ actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null);
+ Editor.Instance.SceneEditing.Spawn(actor);
+ }
+
///
public override void OnDestroy()
{
diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs
index eb462deb1..6e111f588 100644
--- a/Source/Editor/Viewport/PrefabWindowViewport.cs
+++ b/Source/Editor/Viewport/PrefabWindowViewport.cs
@@ -5,9 +5,7 @@ using System.Collections.Generic;
using FlaxEditor.Content;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
-using FlaxEditor.GUI.Drag;
using FlaxEditor.SceneGraph;
-using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.Scripting;
using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Previews;
@@ -56,9 +54,11 @@ namespace FlaxEditor.Viewport
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
private PrefabSpritesRenderer _spritesRenderer;
- private readonly DragAssets _dragAssets;
- private readonly DragActorType _dragActorType = new DragActorType(ValidateDragActorType);
- private readonly DragHandlers _dragHandlers = new DragHandlers();
+
+ ///
+ /// Drag and drop handlers
+ ///
+ public readonly ViewportDragHandlers DragHandlers;
///
/// The transform gizmo.
@@ -81,7 +81,7 @@ namespace FlaxEditor.Viewport
_window.SelectionChanged += OnSelectionChanged;
Undo = window.Undo;
ViewportCamera = new FPSCamera();
- _dragAssets = new DragAssets(ValidateDragItem);
+ DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType);
ShowDebugDraw = true;
ShowEditorPrimitives = true;
Gizmos = new GizmosCollection(this);
@@ -228,14 +228,15 @@ namespace FlaxEditor.Viewport
_gizmoModeScale.Toggled += OnGizmoModeToggle;
gizmoMode.Parent = this;
- _dragHandlers.Add(_dragActorType);
- _dragHandlers.Add(_dragAssets);
-
// Setup input actions
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
- InputActions.Add(options => options.ToggleTransformSpace, () => { OnTransformSpaceToggle(transformSpaceToggle); transformSpaceToggle.Checked = !transformSpaceToggle.Checked; });
+ InputActions.Add(options => options.ToggleTransformSpace, () =>
+ {
+ OnTransformSpaceToggle(transformSpaceToggle);
+ transformSpaceToggle.Checked = !transformSpaceToggle.Checked;
+ });
InputActions.Add(options => options.FocusSelection, ShowSelectedActors);
SetUpdate(ref _update, OnUpdate);
@@ -267,6 +268,7 @@ namespace FlaxEditor.Viewport
private void OnCollectDrawCalls(ref RenderContext renderContext)
{
+ DragHandlers.CollectDrawCalls(_debugDrawData, ref renderContext);
_debugDrawData.OnDraw(ref renderContext);
}
@@ -306,6 +308,7 @@ namespace FlaxEditor.Viewport
var orient = ViewOrientation;
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient);
}
+
///
public EditorViewport Viewport => this;
@@ -354,6 +357,12 @@ namespace FlaxEditor.Viewport
_window.Select(nodes);
}
+ ///
+ public void Spawn(Actor actor)
+ {
+ _window.Spawn(actor);
+ }
+
///
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
@@ -669,11 +678,11 @@ namespace FlaxEditor.Viewport
///
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
+ DragHandlers.ClearDragEffects();
var result = base.OnDragEnter(ref location, data);
if (result != DragDropEffect.None)
return result;
-
- return _dragHandlers.OnDragEnter(data);
+ return DragHandlers.DragEnter(ref location, data);
}
private bool ValidateDragItem(ContentItem contentItem)
@@ -685,7 +694,6 @@ namespace FlaxEditor.Viewport
if (assetItem.IsOfType())
return true;
}
-
return false;
}
@@ -697,86 +705,21 @@ namespace FlaxEditor.Viewport
///
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
+ DragHandlers.ClearDragEffects();
var result = base.OnDragMove(ref location, data);
if (result != DragDropEffect.None)
return result;
-
- return _dragHandlers.Effect;
+ return DragHandlers.DragEnter(ref location, data);
}
///
public override void OnDragLeave()
{
- _dragHandlers.OnDragLeave();
-
+ DragHandlers.ClearDragEffects();
+ DragHandlers.OnDragLeave();
base.OnDragLeave();
}
- private Vector3 PostProcessSpawnedActorLocation(Actor actor, ref Vector3 hitLocation)
- {
- // Place the object
- //var location = hitLocation - (box.Size.Length * 0.5f) * ViewDirection;
- var location = hitLocation;
-
- // Apply grid snapping if enabled
- if (UseSnapping || TransformGizmo.TranslationSnapEnable)
- {
- float snapValue = TransformGizmo.TranslationSnapValue;
- location = new Vector3(
- (int)(location.X / snapValue) * snapValue,
- (int)(location.Y / snapValue) * snapValue,
- (int)(location.Z / snapValue) * snapValue);
- }
-
- return location;
- }
-
- private void Spawn(AssetItem item, SceneGraphNode hit, ref Float2 location, ref Vector3 hitLocation)
- {
- if (item is BinaryAssetItem binaryAssetItem)
- {
- if (typeof(MaterialBase).IsAssignableFrom(binaryAssetItem.Type))
- {
- if (hit is StaticModelNode staticModelNode)
- {
- var staticModel = (StaticModel)staticModelNode.Actor;
- var ray = ConvertMouseToRay(ref location);
- if (staticModel.IntersectsEntry(ref ray, out _, out _, out var entryIndex))
- {
- var material = FlaxEngine.Content.LoadAsync(item.ID);
- using (new UndoBlock(Undo, staticModel, "Change material"))
- staticModel.SetMaterial(entryIndex, material);
- }
- }
- return;
- }
- }
- {
- var actor = item.OnEditorDrop(this);
- actor.Name = item.ShortName;
- Spawn(actor, ref hitLocation);
- }
- }
-
- private void Spawn(Actor actor, ref Vector3 hitLocation)
- {
- actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation);
- _window.Spawn(actor);
- Focus();
- }
-
- private void Spawn(ScriptType item, SceneGraphNode hit, ref Vector3 hitLocation)
- {
- var actor = item.CreateInstance() as Actor;
- if (actor == null)
- {
- Editor.LogWarning("Failed to spawn actor of type " + item.TypeName);
- return;
- }
- actor.Name = item.Name;
- Spawn(actor, ref hitLocation);
- }
-
///
/// Focuses the viewport on the current selection of the gizmo.
///
@@ -814,57 +757,11 @@ namespace FlaxEditor.Viewport
///
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
+ DragHandlers.ClearDragEffects();
var result = base.OnDragDrop(ref location, data);
if (result != DragDropEffect.None)
return result;
-
- // Check if drag sth
- Vector3 hitLocation = ViewPosition;
- SceneGraphNode hit = null;
- if (_dragHandlers.HasValidDrag)
- {
- // Get mouse ray and try to hit any object
- var ray = ConvertMouseToRay(ref location);
- var view = new Ray(ViewPosition, ViewDirection);
- hit = _window.Graph.Root.RayCast(ref ray, ref view, out var closest, SceneGraphNode.RayCastData.FlagTypes.SkipColliders);
- if (hit != null)
- {
- // Use hit location
- hitLocation = ray.Position + ray.Direction * closest;
- }
- else
- {
- // Use area in front of the viewport
- hitLocation = ViewPosition + ViewDirection * 100;
- }
- }
-
- // Drag assets
- if (_dragAssets.HasValidDrag)
- {
- result = _dragAssets.Effect;
-
- // Process items
- for (int i = 0; i < _dragAssets.Objects.Count; i++)
- {
- var item = _dragAssets.Objects[i];
- Spawn(item, hit, ref location, ref hitLocation);
- }
- }
- // Drag actor type
- else if (_dragActorType.HasValidDrag)
- {
- result = _dragActorType.Effect;
-
- // Process items
- for (int i = 0; i < _dragActorType.Objects.Count; i++)
- {
- var item = _dragActorType.Objects[i];
- Spawn(item, hit, ref hitLocation);
- }
- }
-
- return result;
+ return DragHandlers.DragDrop(ref location, data);
}
///
diff --git a/Source/Editor/Viewport/Previews/MaterialPreview.cs b/Source/Editor/Viewport/Previews/MaterialPreview.cs
index 5520d6c4c..46aac1cdf 100644
--- a/Source/Editor/Viewport/Previews/MaterialPreview.cs
+++ b/Source/Editor/Viewport/Previews/MaterialPreview.cs
@@ -7,6 +7,8 @@ using FlaxEngine.GUI;
using FlaxEditor.Viewport.Widgets;
using FlaxEditor.GUI.ContextMenu;
using Object = FlaxEngine.Object;
+using FlaxEditor.GUI;
+using FlaxEditor.Scripting;
namespace FlaxEditor.Viewport.Previews
{
@@ -49,6 +51,8 @@ namespace FlaxEditor.Viewport.Previews
private Image _guiMaterialControl;
private readonly MaterialBase[] _postFxMaterialsCache = new MaterialBase[1];
private ContextMenu _modelWidgetButtonMenu;
+ private AssetPicker _customModelPicker;
+ private Model _customModel;
///
/// Gets or sets the material asset to preview. It can be or .
@@ -74,15 +78,66 @@ namespace FlaxEditor.Viewport.Previews
get => _selectedModelIndex;
set
{
+ if (value == -1) // Using Custom Model
+ return;
if (value < 0 || value > Models.Length)
throw new ArgumentOutOfRangeException();
+ if (_customModelPicker != null)
+ _customModelPicker.Validator.SelectedAsset = null;
_selectedModelIndex = value;
_previewModel.Model = FlaxEngine.Content.LoadAsyncInternal("Editor/Primitives/" + Models[value]);
_previewModel.Transform = Transforms[value];
}
}
+ // Used to automatically update which entry is checked.
+ // TODO: Maybe a better system with predicate bool checks could be used?
+ private void ResetModelContextMenu()
+ {
+ _modelWidgetButtonMenu.ItemsContainer.DisposeChildren();
+
+ // Fill out all models
+ for (int i = 0; i < Models.Length; i++)
+ {
+ var index = i;
+ var button = _modelWidgetButtonMenu.AddButton(Models[index]);
+ button.ButtonClicked += _ => SelectedModelIndex = index;
+ button.Checked = SelectedModelIndex == index && _customModel == null;
+ button.Tag = index;
+ }
+
+ _modelWidgetButtonMenu.AddSeparator();
+ _customModelPicker = new AssetPicker(new ScriptType(typeof(Model)), Float2.Zero);
+
+ // Label button
+ var customModelPickerLabel = _modelWidgetButtonMenu.AddButton("Custom Model:");
+ customModelPickerLabel.CloseMenuOnClick = false;
+ customModelPickerLabel.Checked = _customModel != null;
+
+ // Container button
+ var customModelPickerButton = _modelWidgetButtonMenu.AddButton("");
+ customModelPickerButton.Height = _customModelPicker.Height + 4;
+ customModelPickerButton.CloseMenuOnClick = false;
+ _customModelPicker.Parent = customModelPickerButton;
+ _customModelPicker.Validator.SelectedAsset = _customModel;
+ _customModelPicker.SelectedItemChanged += () =>
+ {
+ _customModel = _customModelPicker.Validator.SelectedAsset as Model;
+ if (_customModelPicker.Validator.SelectedAsset == null)
+ {
+ SelectedModelIndex = 0;
+ ResetModelContextMenu();
+ return;
+ }
+
+ _previewModel.Model = _customModel;
+ _previewModel.Transform = Transforms[0];
+ SelectedModelIndex = -1;
+ ResetModelContextMenu();
+ };
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -107,17 +162,7 @@ namespace FlaxEditor.Viewport.Previews
{
if (!control.Visible)
return;
- _modelWidgetButtonMenu.ItemsContainer.DisposeChildren();
-
- // Fill out all models
- for (int i = 0; i < Models.Length; i++)
- {
- var index = i;
- var button = _modelWidgetButtonMenu.AddButton(Models[index]);
- button.ButtonClicked += _ => SelectedModelIndex = index;
- button.Checked = SelectedModelIndex == index;
- button.Tag = index;
- }
+ ResetModelContextMenu();
};
new ViewportWidgetButton("Model", SpriteHandle.Invalid, _modelWidgetButtonMenu)
{
diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs
new file mode 100644
index 000000000..8a1b4f183
--- /dev/null
+++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs
@@ -0,0 +1,257 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using System;
+using System.Linq;
+using FlaxEditor.Content;
+using FlaxEditor.Gizmo;
+using FlaxEditor.GUI.Drag;
+using FlaxEditor.SceneGraph;
+using FlaxEditor.SceneGraph.Actors;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.Viewport
+{
+ ///
+ /// Utility to help managing dragging assets, actors and other objects into the editor viewport.
+ ///
+ public class ViewportDragHandlers : DragHandlers
+ {
+ ///
+ /// The custom drag drop event arguments.
+ ///
+ ///
+ public class DragDropEventArgs : DragEventArgs
+ {
+ ///
+ /// The hit.
+ ///
+ public SceneGraphNode Hit;
+
+ ///
+ /// The hit location.
+ ///
+ public Vector3 HitLocation;
+ }
+
+ private readonly IGizmoOwner _owner;
+ private readonly EditorViewport _viewport;
+ private readonly DragAssets _dragAssets;
+ private readonly DragActorType _dragActorType;
+
+ private StaticModel _previewStaticModel;
+ private int _previewModelEntryIndex;
+ private BrushSurface _previewBrushSurface;
+
+ internal ViewportDragHandlers(IGizmoOwner owner, EditorViewport viewport, Func validateAsset, Func validateDragActorType)
+ {
+ _owner = owner;
+ _viewport = viewport;
+ Add(_dragAssets = new DragAssets(validateAsset));
+ Add(_dragActorType = new DragActorType(validateDragActorType));
+ }
+
+ internal void ClearDragEffects()
+ {
+ _previewStaticModel = null;
+ _previewModelEntryIndex = -1;
+ _previewBrushSurface = new BrushSurface();
+ }
+
+ internal void CollectDrawCalls(ViewportDebugDrawData debugDrawData, ref RenderContext renderContext)
+ {
+ if (_previewStaticModel)
+ debugDrawData.HighlightModel(_previewStaticModel, _previewModelEntryIndex);
+ if (_previewBrushSurface.Brush != null)
+ debugDrawData.HighlightBrushSurface(_previewBrushSurface);
+ }
+
+ internal DragDropEffect DragEnter(ref Float2 location, DragData data)
+ {
+ var result = OnDragEnter(data);
+ SetDragEffects(ref location);
+ return result;
+ }
+
+ internal DragDropEffect DragMove(ref Float2 location, DragData data)
+ {
+ SetDragEffects(ref location);
+ return Effect;
+ }
+
+ internal DragDropEffect DragDrop(ref Float2 location, DragData data)
+ {
+ Vector3 hitLocation = _viewport.ViewPosition, hitNormal = -_viewport.ViewDirection;
+ SceneGraphNode hit = null;
+ if (HasValidDrag)
+ {
+ GetHitLocation(ref location, out hit, out hitLocation, out hitNormal);
+ }
+
+ var result = DragDropEffect.None;
+ if (_dragAssets.HasValidDrag)
+ {
+ result = _dragAssets.Effect;
+ foreach (var asset in _dragAssets.Objects)
+ Spawn(asset, hit, ref location, ref hitLocation, ref hitNormal);
+ }
+ else if (_dragActorType.HasValidDrag)
+ {
+ result = _dragActorType.Effect;
+ foreach (var actorType in _dragActorType.Objects)
+ Spawn(actorType, hit, ref location, ref hitLocation, ref hitNormal);
+ }
+
+ OnDragDrop(new DragDropEventArgs { Hit = hit, HitLocation = hitLocation });
+
+ return result;
+ }
+
+ private void SetDragEffects(ref Float2 location)
+ {
+ if (_dragAssets.HasValidDrag && _dragAssets.Objects[0].IsOfType())
+ {
+ GetHitLocation(ref location, out var hit, out _, out _);
+ ClearDragEffects();
+ var material = FlaxEngine.Content.LoadAsync(_dragAssets.Objects[0].ID);
+ if (material.IsDecal)
+ return;
+
+ if (hit is StaticModelNode staticModelNode)
+ {
+ _previewStaticModel = (StaticModel)staticModelNode.Actor;
+ var ray = _viewport.ConvertMouseToRay(ref location);
+ _previewStaticModel.IntersectsEntry(ref ray, out _, out _, out _previewModelEntryIndex);
+ }
+ else if (hit is BoxBrushNode.SideLinkNode brushSurfaceNode)
+ {
+ _previewBrushSurface = brushSurfaceNode.Surface;
+ }
+ }
+ }
+
+ private void GetHitLocation(ref Float2 location, out SceneGraphNode hit, out Vector3 hitLocation, out Vector3 hitNormal)
+ {
+ // Get mouse ray and try to hit any object
+ var ray = _viewport.ConvertMouseToRay(ref location);
+ var view = new Ray(_viewport.ViewPosition, _viewport.ViewDirection);
+ var gridPlane = new Plane(Vector3.Zero, Vector3.Up);
+ var flags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives;
+ hit = _owner.SceneGraphRoot.RayCast(ref ray, ref view, out var closest, out var normal, flags);
+ var girdGizmo = (GridGizmo)_owner.Gizmos.FirstOrDefault(x => x is GridGizmo);
+ if (hit != null)
+ {
+ // Use hit location
+ hitLocation = ray.Position + ray.Direction * closest;
+ hitNormal = normal;
+ }
+ else if (girdGizmo != null && girdGizmo.Enabled && CollisionsHelper.RayIntersectsPlane(ref ray, ref gridPlane, out closest) && closest < 4000.0f)
+ {
+ // Use grid location
+ hitLocation = ray.Position + ray.Direction * closest;
+ hitNormal = Vector3.Up;
+ }
+ else
+ {
+ // Use area in front of the viewport
+ hitLocation = view.GetPoint(100);
+ hitNormal = Vector3.Up;
+ }
+ }
+
+ private Vector3 PostProcessSpawnedActorLocation(Actor actor, ref Vector3 hitLocation)
+ {
+ // Refresh actor position to ensure that cached bounds are valid
+ actor.Position = Vector3.One;
+ actor.Position = Vector3.Zero;
+
+ // Place the object
+ //var location = hitLocation - (box.Size.Length * 0.5f) * ViewDirection;
+ var editorBounds = actor.EditorBoxChildren;
+ var bottomToCenter = actor.Position.Y - editorBounds.Minimum.Y;
+ var location = hitLocation + new Vector3(0, bottomToCenter, 0);
+
+ // Apply grid snapping if enabled
+ var transformGizmo = (TransformGizmo)_owner.Gizmos.FirstOrDefault(x => x is TransformGizmo);
+ if (transformGizmo != null && (_owner.UseSnapping || transformGizmo.TranslationSnapEnable))
+ {
+ float snapValue = transformGizmo.TranslationSnapValue;
+ location = new Vector3(
+ (int)(location.X / snapValue) * snapValue,
+ (int)(location.Y / snapValue) * snapValue,
+ (int)(location.Z / snapValue) * snapValue);
+ }
+
+ return location;
+ }
+
+ private void Spawn(Actor actor, ref Vector3 hitLocation, ref Vector3 hitNormal)
+ {
+ actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation);
+ _owner.Spawn(actor);
+ _viewport.Focus();
+ }
+
+ private void Spawn(ScriptType item, SceneGraphNode hit, ref Float2 location, ref Vector3 hitLocation, ref Vector3 hitNormal)
+ {
+ var actor = item.CreateInstance() as Actor;
+ if (actor == null)
+ {
+ Editor.LogWarning("Failed to spawn actor of type " + item.TypeName);
+ return;
+ }
+ actor.Name = item.Name;
+ Spawn(actor, ref hitLocation, ref hitNormal);
+ }
+
+ private void Spawn(AssetItem item, SceneGraphNode hit, ref Float2 location, ref Vector3 hitLocation, ref Vector3 hitNormal)
+ {
+ if (item.IsOfType())
+ {
+ var material = FlaxEngine.Content.LoadAsync(item.ID);
+ if (material && !material.WaitForLoaded(500) && material.IsDecal)
+ {
+ var actor = new Decal
+ {
+ Material = material,
+ LocalOrientation = RootNode.RaycastNormalRotation(ref hitNormal),
+ Name = item.ShortName
+ };
+ DebugDraw.DrawWireArrow(PostProcessSpawnedActorLocation(actor, ref hitNormal), actor.LocalOrientation, 1.0f, Color.Red, 1000000);
+ Spawn(actor, ref hitLocation, ref hitNormal);
+ }
+ else if (hit is StaticModelNode staticModelNode)
+ {
+ var staticModel = (StaticModel)staticModelNode.Actor;
+ var ray = _viewport.ConvertMouseToRay(ref location);
+ if (staticModel.IntersectsEntry(ref ray, out _, out _, out var entryIndex))
+ {
+ using (new UndoBlock(_owner.Undo, staticModel, "Change material"))
+ staticModel.SetMaterial(entryIndex, material);
+ }
+ }
+ else if (hit is BoxBrushNode.SideLinkNode brushSurfaceNode)
+ {
+ using (new UndoBlock(_owner.Undo, brushSurfaceNode.Brush, "Change material"))
+ {
+ var surface = brushSurfaceNode.Surface;
+ surface.Material = material;
+ brushSurfaceNode.Surface = surface;
+ }
+ }
+ return;
+ }
+ if (item.IsOfType())
+ {
+ Editor.Instance.Scene.OpenScene(item.ID, true);
+ return;
+ }
+ {
+ var actor = item.OnEditorDrop(this);
+ actor.Name = item.ShortName;
+ Spawn(actor, ref hitLocation, ref hitNormal);
+ }
+ }
+ }
+}
diff --git a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
index 481bc3f1b..77e94ae53 100644
--- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
+++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
@@ -19,6 +19,7 @@ namespace FlaxEditor.Viewport.Widgets
private bool _checked;
private bool _autoCheck;
private bool _isMosueDown;
+ private float _forcedTextWidth;
///
/// Event fired when user toggles checked state.
@@ -63,14 +64,16 @@ namespace FlaxEditor.Viewport.Widgets
/// The text.
/// The icon.
/// The context menu.
- /// if set to true will be automatic checked on mouse click.
- public ViewportWidgetButton(string text, SpriteHandle icon, ContextMenu contextMenu = null, bool autoCheck = false)
- : base(0, 0, CalculateButtonWidth(0, icon.IsValid), ViewportWidgetsContainer.WidgetsHeight)
+ /// If set to true will be automatic checked on mouse click.
+ /// Forces the text to be drawn with the specified width.
+ public ViewportWidgetButton(string text, SpriteHandle icon, ContextMenu contextMenu = null, bool autoCheck = false, float textWidth = 0.0f)
+ : base(0, 0, CalculateButtonWidth(textWidth, icon.IsValid), ViewportWidgetsContainer.WidgetsHeight)
{
_text = text;
Icon = icon;
_cm = contextMenu;
_autoCheck = autoCheck;
+ _forcedTextWidth = textWidth;
if (_cm != null)
_cm.VisibleChanged += CmOnVisibleChanged;
@@ -112,7 +115,7 @@ namespace FlaxEditor.Viewport.Widgets
if (Icon.IsValid)
{
// Draw icon
- Render2D.DrawSprite(Icon, iconRect, style.Foreground);
+ Render2D.DrawSprite(Icon, iconRect, style.ForegroundViewport);
// Update text rectangle
textRect.Location.X += iconSize;
@@ -120,7 +123,7 @@ namespace FlaxEditor.Viewport.Widgets
}
// Draw text
- Render2D.DrawText(style.FontMedium, _text, textRect, style.Foreground * (IsMouseOver ? 1.0f : 0.9f), TextAlignment.Center, TextAlignment.Center);
+ Render2D.DrawText(style.FontMedium, _text, textRect, style.ForegroundViewport * (IsMouseOver ? 1.0f : 0.9f), TextAlignment.Center, TextAlignment.Center);
}
///
@@ -160,7 +163,7 @@ namespace FlaxEditor.Viewport.Widgets
var style = Style.Current;
if (style != null && style.FontMedium)
- Width = CalculateButtonWidth(style.FontMedium.MeasureText(_text).X, Icon.IsValid);
+ Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.MeasureText(_text).X, Icon.IsValid);
}
}
}
diff --git a/Source/Editor/Windows/AssetReferencesGraphWindow.cs b/Source/Editor/Windows/AssetReferencesGraphWindow.cs
index d49896e2e..b9e0e7257 100644
--- a/Source/Editor/Windows/AssetReferencesGraphWindow.cs
+++ b/Source/Editor/Windows/AssetReferencesGraphWindow.cs
@@ -46,14 +46,14 @@ namespace FlaxEditor.Windows
if (asset != null)
{
var path = asset.Path;
- picker.SelectedAsset = asset;
+ picker.Validator.SelectedAsset = asset;
Title = System.IO.Path.GetFileNameWithoutExtension(path);
TooltipText = asset.TypeName + '\n' + path;
}
else
{
- picker.SelectedID = AssetId;
- var assetItem = picker.SelectedItem as AssetItem;
+ picker.Validator.SelectedID = AssetId;
+ var assetItem = picker.Validator.SelectedItem as AssetItem;
if (assetItem != null)
{
Title = assetItem.ShortName;
diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs
index a765c2faa..8f88d93f6 100644
--- a/Source/Editor/Windows/Assets/AnimationWindow.cs
+++ b/Source/Editor/Windows/Assets/AnimationWindow.cs
@@ -230,6 +230,8 @@ namespace FlaxEditor.Windows.Assets
public AnimationWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -265,8 +267,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _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)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/animation/index.html")).LinkTooltip("See documentation to learn more");
diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
index 8cbf6cf75..01ee9cb8a 100644
--- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
+++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
@@ -130,6 +130,8 @@ namespace FlaxEditor.Windows.Assets
public BehaviorTreeWindow(Editor editor, BinaryAssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -172,10 +174,10 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _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)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)");
+ _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
_toolstrip.AddButton(editor.Icons.CenterView64, _surface.ShowWholeGraph).LinkTooltip("Show whole graph");
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/ai/behavior-trees/index.html")).LinkTooltip("See documentation to learn more");
diff --git a/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs b/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs
index 623c4ef5b..a8121162a 100644
--- a/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs
+++ b/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs
@@ -395,6 +395,8 @@ namespace FlaxEditor.Windows.Assets
public GameplayGlobalsWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
_undo = new Undo();
_undo.ActionDone += OnUndo;
_undo.UndoDone += OnUndo;
@@ -411,10 +413,10 @@ namespace FlaxEditor.Windows.Assets
_proxy = new PropertiesProxy();
_propertiesEditor.Select(_proxy);
- _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save asset");
+ _saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip($"Save asset ({inputOptions.Save})");
_toolstrip.AddSeparator();
- _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)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_resetButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Rotate32, Reset).LinkTooltip("Resets the variables values to the default values");
diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs
index 097d4992a..a1178fb68 100644
--- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs
+++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs
@@ -34,6 +34,8 @@ namespace FlaxEditor.Windows.Assets
public JsonAssetWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -43,8 +45,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _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)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
// Panel
var panel = new Panel(ScrollBars.Vertical)
diff --git a/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs b/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs
index 0de5ce315..85f351fef 100644
--- a/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs
+++ b/Source/Editor/Windows/Assets/LocalizedStringTableWindow.cs
@@ -126,6 +126,8 @@ namespace FlaxEditor.Windows.Assets
public LocalizedStringTableWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -135,8 +137,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _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)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(Editor.Icons.Up64, OnExport).LinkTooltip("Export localization table entries for translation to .pot file");
diff --git a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
index 3e0525fb7..775e0c0dc 100644
--- a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
+++ b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
@@ -375,6 +375,8 @@ namespace FlaxEditor.Windows.Assets
public MaterialInstanceWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -384,8 +386,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _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)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(Editor.Icons.Rotate64, OnRevertAllParameters).LinkTooltip("Revert all the parameters to the default values");
_toolstrip.AddSeparator();
diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs
index 6fbe32d7e..5aa77dbc3 100644
--- a/Source/Editor/Windows/Assets/MaterialWindow.cs
+++ b/Source/Editor/Windows/Assets/MaterialWindow.cs
@@ -41,8 +41,6 @@ namespace FlaxEditor.Windows.Assets
new ScriptType(typeof(Vector3)),
new ScriptType(typeof(Vector4)),
new ScriptType(typeof(Color)),
- new ScriptType(typeof(Quaternion)),
- new ScriptType(typeof(Transform)),
new ScriptType(typeof(Matrix)),
};
diff --git a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs
index 17eda1358..4a5e02e6e 100644
--- a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs
+++ b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs
@@ -306,6 +306,8 @@ namespace FlaxEditor.Windows.Assets
public ParticleSystemWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -359,8 +361,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _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)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more");
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
index 70aa1dca3..a8d9ae1be 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
@@ -339,7 +339,7 @@ namespace FlaxEditor.Windows.Assets
{
if (selection.Count != 0)
Select(actor);
- actor.TreeNode.StartRenaming(this);
+ actor.TreeNode.StartRenaming(this, _treePanel);
}
}
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs
index 4face0dc0..f50a832a1 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.cs
@@ -94,6 +94,8 @@ namespace FlaxEditor.Windows.Assets
public PrefabWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoEvent;
@@ -149,6 +151,7 @@ namespace FlaxEditor.Windows.Assets
// Prefab structure tree
Graph = new LocalSceneGraph(new CustomRootNode(this));
+ Graph.Root.TreeNode.Expand(true);
_tree = new PrefabTree
{
Margin = new Margin(0.0f, 0.0f, -16.0f, _treePanel.ScrollBarsSize), // Hide root node
@@ -175,12 +178,12 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _toolStripUndo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
- _toolStripRedo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
+ _toolStripUndo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _toolStripRedo = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
- _toolStripTranslate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Translate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip("Change Gizmo tool mode to Translate (1)");
- _toolStripRotate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Rotate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)");
- _toolStripScale = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Scale32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)");
+ _toolStripTranslate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Translate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate).LinkTooltip($"Change Gizmo tool mode to Translate ({inputOptions.TranslateMode})");
+ _toolStripRotate = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Rotate32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip($"Change Gizmo tool mode to Rotate ({inputOptions.RotateMode})");
+ _toolStripScale = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Scale32, () => _viewport.TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})");
_toolstrip.AddSeparator();
_toolStripLiveReload = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Refresh64, () => LiveReload = !LiveReload).SetChecked(true).SetAutoCheck(true).LinkTooltip("Live changes preview (applies prefab changes on modification by auto)");
@@ -317,7 +320,7 @@ namespace FlaxEditor.Windows.Assets
Graph.MainActor = _viewport.Instance;
Selection.Clear();
Select(Graph.Main);
- Graph.Root.TreeNode.ExpandAll(true);
+ Graph.Root.TreeNode.Expand(true);
_undo.Clear();
ClearEditedFlag();
}
@@ -413,7 +416,7 @@ namespace FlaxEditor.Windows.Assets
_focusCamera = true;
Selection.Clear();
Select(Graph.Main);
- Graph.Root.TreeNode.ExpandAll(true);
+ Graph.Root.TreeNode.Expand(true);
_undo.Clear();
ClearEditedFlag();
diff --git a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs
index 162944144..05435cc77 100644
--- a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs
+++ b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs
@@ -627,6 +627,8 @@ namespace FlaxEditor.Windows.Assets
public SceneAnimationWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -652,8 +654,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _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)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
_previewButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Refresh64, OnPreviewButtonClicked).SetAutoCheck(true).LinkTooltip("If checked, enables live-preview of the animation on a scene while editing");
_renderButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Build64, OnRenderButtonClicked).LinkTooltip("Open the scene animation rendering utility...");
diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
index d8790172b..95827240c 100644
--- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
+++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
@@ -371,7 +371,10 @@ namespace FlaxEditor.Windows.Assets
private void OnTreeSelectedChanged(List before, List after)
{
if (after.Count != 0)
- ((SkeletonPropertiesProxy)Values[0]).Window._preview.ShowDebugDraw = true;
+ {
+ var proxy = (SkeletonPropertiesProxy)Values[0];
+ proxy.Window._preview.ShowDebugDraw = true;
+ }
}
private void OnTreeNodeCopyName(ContextMenuButton b)
@@ -837,7 +840,7 @@ namespace FlaxEditor.Windows.Assets
sourceAssetPicker.CheckValid = CheckSourceAssetValid;
sourceAssetPicker.SelectedItemChanged += () =>
{
- proxy.Setups.Add(sourceAssetPicker.SelectedAsset, new SetupProxy());
+ proxy.Setups.Add(sourceAssetPicker.Validator.SelectedAsset, new SetupProxy());
proxy.Window.MarkAsEdited();
RebuildLayout();
};
@@ -856,7 +859,7 @@ namespace FlaxEditor.Windows.Assets
// Source asset picker
var sourceAssetPicker = setupGroup.AddPropertyItem("Source Asset").Custom().CustomControl;
- sourceAssetPicker.SelectedAsset = sourceAsset;
+ sourceAssetPicker.Validator.SelectedAsset = sourceAsset;
sourceAssetPicker.CanEdit = false;
sourceAssetPicker.Height = 48;
@@ -916,12 +919,12 @@ namespace FlaxEditor.Windows.Assets
{
// Show skeleton asset picker
var sourceSkeletonPicker = setupGroup.AddPropertyItem("Skeleton", "Skinned model that contains a skeleton for this animation retargeting.").Custom().CustomControl;
- sourceSkeletonPicker.AssetType = new ScriptType(typeof(SkinnedModel));
- sourceSkeletonPicker.SelectedAsset = setup.Value.Skeleton;
+ sourceSkeletonPicker.Validator.AssetType = new ScriptType(typeof(SkinnedModel));
+ sourceSkeletonPicker.Validator.SelectedAsset = setup.Value.Skeleton;
sourceSkeletonPicker.Height = 48;
sourceSkeletonPicker.SelectedItemChanged += () =>
{
- setup.Value.Skeleton = (SkinnedModel)sourceSkeletonPicker.SelectedAsset;
+ setup.Value.Skeleton = (SkinnedModel)sourceSkeletonPicker.Validator.SelectedAsset;
proxy.Window.MarkAsEdited();
};
}
@@ -1045,6 +1048,7 @@ namespace FlaxEditor.Windows.Assets
{
Proxy = new SkeletonPropertiesProxy();
Presenter.Select(Proxy);
+ // Draw highlight on selected node
window._preview.CustomDebugDraw += OnDebugDraw;
}
@@ -1146,6 +1150,15 @@ namespace FlaxEditor.Windows.Assets
_tabs.AddTab(new RetargetTab(this));
_tabs.AddTab(new ImportTab(this));
+ // Automatically show nodes when switching to skeleton page
+ _tabs.SelectedTabChanged += (tabs) =>
+ {
+ if (tabs.SelectedTab is SkeletonTab)
+ {
+ _preview.ShowNodes = true;
+ }
+ };
+
// Highlight actor (used to highlight selected material slot, see UpdateEffectsOnAsset)
_highlightActor = new AnimatedModel
{
diff --git a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs
index 7ae6ae8be..e3575d0e8 100644
--- a/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs
+++ b/Source/Editor/Windows/Assets/VisjectFunctionSurfaceWindow.cs
@@ -61,6 +61,8 @@ namespace FlaxEditor.Windows.Assets
protected VisjectFunctionSurfaceWindow(Editor editor, AssetItem item)
: base(editor, item)
{
+ var inputOptions = Editor.Options.Options.Input;
+
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
@@ -70,10 +72,10 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _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)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)");
+ _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
_toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph");
// Panel
diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs
index 584f1c307..79157f850 100644
--- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs
+++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs
@@ -561,6 +561,7 @@ namespace FlaxEditor.Windows.Assets
: base(editor, item)
{
var isPlayMode = Editor.IsPlayMode;
+ var inputOptions = Editor.Options.Options.Input;
// Undo
_undo = new Undo();
@@ -598,21 +599,21 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
- _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)");
+ _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip($"Undo ({inputOptions.Undo})");
+ _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip($"Redo ({inputOptions.Redo})");
_toolstrip.AddSeparator();
- _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)");
+ _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip($"Open content search tool ({inputOptions.Search})");
_toolstrip.AddButton(editor.Icons.CenterView64, ShowWholeGraph).LinkTooltip("Show whole graph");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/scripting/visual/index.html")).LinkTooltip("See documentation to learn more");
_debugToolstripControls = new[]
{
_toolstrip.AddSeparator(),
- _toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip("Continue (F5)"),
+ _toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip($"Continue ({inputOptions.DebuggerContinue})"),
_toolstrip.AddButton(editor.Icons.Search64, OnDebuggerNavigateToCurrentNode).LinkTooltip("Navigate to the current stack trace node"),
- _toolstrip.AddButton(editor.Icons.Right64, OnDebuggerStepOver).LinkTooltip("Step Over (F10)"),
- _toolstrip.AddButton(editor.Icons.Down64, OnDebuggerStepInto).LinkTooltip("Step Into (F11)"),
- _toolstrip.AddButton(editor.Icons.Up64, OnDebuggerStepOut).LinkTooltip("Step Out (Shift+F11)"),
+ _toolstrip.AddButton(editor.Icons.Right64, OnDebuggerStepOver).LinkTooltip($"Step Over ({inputOptions.DebuggerStepOver})"),
+ _toolstrip.AddButton(editor.Icons.Down64, OnDebuggerStepInto).LinkTooltip($"Step Into ({inputOptions.DebuggerStepInto})"),
+ _toolstrip.AddButton(editor.Icons.Up64, OnDebuggerStepOut).LinkTooltip($"Step Out ({inputOptions.DebuggerStepOut})"),
_toolstrip.AddButton(editor.Icons.Stop64, OnDebuggerStop).LinkTooltip("Stop debugging"),
};
foreach (var control in _debugToolstripControls)
diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
index 56136cfbf..dda812f84 100644
--- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
@@ -114,18 +114,32 @@ namespace FlaxEditor.Windows
}
}
- cm.AddButton("Delete", () => Delete(item));
+ if (isFolder && folder.Node is MainContentTreeNode)
+ {
+ cm.AddSeparator();
+ }
+ else
+ {
+ cm.AddButton("Delete", () => Delete(item));
- cm.AddSeparator();
+ cm.AddSeparator();
- cm.AddButton("Duplicate", _view.Duplicate);
+ cm.AddButton("Duplicate", _view.Duplicate);
- cm.AddButton("Copy", _view.Copy);
+ cm.AddButton("Copy", _view.Copy);
+ }
b = cm.AddButton("Paste", _view.Paste);
b.Enabled = _view.CanPaste();
- cm.AddButton("Rename", () => Rename(item));
+ if (isFolder && folder.Node is MainContentTreeNode)
+ {
+ // Do nothing
+ }
+ else
+ {
+ cm.AddButton("Rename", () => Rename(item));
+ }
// Custom options
ContextMenuShow?.Invoke(cm, item);
@@ -187,12 +201,12 @@ namespace FlaxEditor.Windows
continue;
// Get context proxy
- ContentProxy p;
+ ContentProxy p = null;
if (type.Type.IsSubclassOf(typeof(ContentProxy)))
{
p = Editor.ContentDatabase.Proxy.Find(x => x.GetType() == type.Type);
}
- else
+ else if (type.CanCreateInstance)
{
// User can use attribute to put their own assets into the content context menu
var generic = typeof(SpawnableJsonAssetProxy<>).MakeGenericType(type.Type);
@@ -369,7 +383,7 @@ namespace FlaxEditor.Windows
}
var pluginPath = Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text);
- if (Directory.Exists(pluginPath))
+ if (!IsValidModuleName(nameTextBox.Text) || Directory.Exists(pluginPath))
{
nameTextBox.BorderColor = Color.Red;
nameTextBox.BorderSelectedColor = Color.Red;
@@ -429,6 +443,12 @@ namespace FlaxEditor.Windows
submitButton.Clicked += () =>
{
// TODO: Check all modules in project including plugins
+ if (!IsValidModuleName(nameTextBox.Text))
+ {
+ Editor.LogWarning("Invalid module name. Module names cannot contain spaces, start with a number or contain non-alphanumeric characters.");
+ return;
+ }
+
if (Directory.Exists(Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text)))
{
Editor.LogWarning("Cannot create module due to name conflict.");
@@ -460,5 +480,16 @@ namespace FlaxEditor.Windows
button.ParentContextMenu.Hide();
};
}
+
+ private static bool IsValidModuleName(string text)
+ {
+ if (text.Contains(' '))
+ return false;
+ if (char.IsDigit(text[0]))
+ return false;
+ if (text.Any(c => !char.IsLetterOrDigit(c) && c != '_'))
+ return false;
+ return true;
+ }
}
}
diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs
index 5ece067a0..2ef9c05cf 100644
--- a/Source/Editor/Windows/GameCookerWindow.cs
+++ b/Source/Editor/Windows/GameCookerWindow.cs
@@ -155,29 +155,42 @@ namespace FlaxEditor.Windows
public virtual void OnNotAvailableLayout(LayoutElementsContainer layout)
{
- layout.Label("Missing platform data tools for the target platform.", TextAlignment.Center);
+ string text = "Missing platform data tools for the target platform.";
if (FlaxEditor.Editor.IsOfficialBuild())
{
switch (BuildPlatform)
{
+#if PLATFORM_WINDOWS
case BuildPlatform.Windows32:
case BuildPlatform.Windows64:
case BuildPlatform.UWPx86:
case BuildPlatform.UWPx64:
case BuildPlatform.LinuxX64:
case BuildPlatform.AndroidARM64:
- layout.Label("Use Flax Launcher and download the required package.", TextAlignment.Center);
+ text += "\nUse Flax Launcher and download the required package.";
break;
+#endif
default:
- layout.Label("Engine source is required to target this platform.", TextAlignment.Center);
+ text += "\nEngine source is required to target this platform.";
break;
}
}
else
{
- var label = layout.Label("To target this platform separate engine source package is required.\nTo get access please contact via https://flaxengine.com/contact", TextAlignment.Center);
- label.Label.AutoHeight = true;
+ text += "\nTo target this platform separate engine source package is required.";
+ switch (BuildPlatform)
+ {
+ case BuildPlatform.XboxOne:
+ case BuildPlatform.XboxScarlett:
+ case BuildPlatform.PS4:
+ case BuildPlatform.PS5:
+ case BuildPlatform.Switch:
+ text += "\nTo get access please contact via https://flaxengine.com/contact";
+ break;
+ }
}
+ var label = layout.Label(text, TextAlignment.Center);
+ label.Label.AutoHeight = true;
}
public virtual void Build()
diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs
index fdedbb3c2..4a8c11176 100644
--- a/Source/Editor/Windows/GameWindow.cs
+++ b/Source/Editor/Windows/GameWindow.cs
@@ -271,8 +271,6 @@ namespace FlaxEditor.Windows
Title = "Game";
AutoFocus = true;
- FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
-
var task = MainRenderTask.Instance;
// Setup viewport
@@ -304,6 +302,12 @@ namespace FlaxEditor.Windows
// Link editor options
Editor.Options.OptionsChanged += OnOptionsChanged;
OnOptionsChanged(Editor.Options.Options);
+
+ InputActions.Add(options => options.TakeScreenshot, () => Screenshot.Capture(string.Empty));
+ InputActions.Add(options => options.DebuggerUnlockMouse, UnlockMouseInPlay);
+ InputActions.Add(options => options.ToggleFullscreen, () => { if (Editor.IsPlayMode) IsMaximized = !IsMaximized; });
+
+ FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
}
private void ChangeViewportRatio(ViewportScaleOptions v)
@@ -382,6 +386,7 @@ namespace FlaxEditor.Windows
{
_viewport.Bounds = new Rectangle(Width * (1 - scaleWidth) / 2, 0, Width * scaleWidth, Height);
}
+ _viewport.SyncBackbufferSize();
PerformLayout();
}
@@ -945,27 +950,6 @@ namespace FlaxEditor.Windows
///
public override bool OnKeyDown(KeyboardKeys key)
{
- switch (key)
- {
- case KeyboardKeys.F12:
- Screenshot.Capture(string.Empty);
- return true;
- case KeyboardKeys.F11:
- if (Root.GetKey(KeyboardKeys.Shift))
- {
- // Unlock mouse in game mode
- UnlockMouseInPlay();
- return true;
- }
- else if (Editor.IsPlayMode)
- {
- // Maximized game window toggle
- IsMaximized = !IsMaximized;
- return true;
- }
- break;
- }
-
// Prevent closing the game window tab during a play session
if (Editor.StateMachine.IsPlayMode && Editor.Options.Options.Input.CloseTab.Process(this, key))
{
diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs
index 3665b7073..6526d7c8a 100644
--- a/Source/Editor/Windows/OutputLogWindow.cs
+++ b/Source/Editor/Windows/OutputLogWindow.cs
@@ -467,6 +467,7 @@ namespace FlaxEditor.Windows
if (_isDirty)
{
_isDirty = false;
+ var wasEmpty = _output.TextLength == 0;
// Cache fonts
_output.DefaultStyle.Font.GetFont();
@@ -589,7 +590,7 @@ namespace FlaxEditor.Windows
// Update the output
var cachedScrollValue = _vScroll.Value;
var cachedSelection = _output.SelectionRange;
- var isBottomScroll = _vScroll.Value >= _vScroll.Maximum - 20.0f;
+ var isBottomScroll = _vScroll.Value >= _vScroll.Maximum - 20.0f || wasEmpty;
_output.Text = _textBuffer.ToString();
_textBufferCount = _entries.Count;
if (!_vScroll.IsThumbClicked)
diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs
index c5ec02b31..f43fb0342 100644
--- a/Source/Editor/Windows/PluginsWindow.cs
+++ b/Source/Editor/Windows/PluginsWindow.cs
@@ -190,7 +190,7 @@ namespace FlaxEditor.Windows
};
_addPluginProjectButton = new Button
{
- Text = "Create Plugin Project",
+ Text = "Create Project",
TooltipText = "Add new plugin project.",
AnchorPreset = AnchorPresets.TopLeft,
LocalLocation = new Float2(70, 18),
@@ -201,7 +201,7 @@ namespace FlaxEditor.Windows
_cloneProjectButton = new Button
{
- Text = "Clone Plugin Project",
+ Text = "Clone Project",
TooltipText = "Git Clone a plugin project.",
AnchorPreset = AnchorPresets.TopLeft,
LocalLocation = new Float2(70 + _addPluginProjectButton.Size.X + 8, 18),
@@ -392,25 +392,48 @@ namespace FlaxEditor.Windows
Editor.Log("Plugin project has been cloned.");
+ try
+ {
+ // Start git submodule clone
+ var settings = new CreateProcessSettings
+ {
+ FileName = "git",
+ WorkingDirectory = clonePath,
+ Arguments = "submodule update --init",
+ ShellExecute = false,
+ LogOutput = true,
+ };
+ Platform.CreateProcess(ref settings);
+ }
+ catch (Exception e)
+ {
+ Editor.LogError($"Failed Git submodule process. {e}");
+ return;
+ }
+
// Find project config file. Could be different then what the user named the folder.
- var files = Directory.GetFiles(clonePath);
string pluginProjectName = "";
- foreach (var file in files)
+ foreach (var file in Directory.GetFiles(clonePath))
{
if (file.Contains(".flaxproj", StringComparison.OrdinalIgnoreCase))
{
pluginProjectName = Path.GetFileNameWithoutExtension(file);
- Debug.Log(pluginProjectName);
+ break;
}
}
-
if (string.IsNullOrEmpty(pluginProjectName))
- Editor.LogError("Failed to find plugin project file to add to Project config. Please add manually.");
- else
{
- await AddReferenceToProject(pluginName, pluginProjectName);
- MessageBox.Show($"{pluginName} has been successfully cloned. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK);
+ Editor.LogError("Failed to find plugin project file to add to Project config. Please add manually.");
+ return;
}
+
+ await AddModuleReferencesInGameModule(clonePath);
+ await AddReferenceToProject(pluginName, pluginProjectName);
+
+ if (Editor.Options.Options.SourceCode.AutoGenerateScriptsProjectFiles)
+ Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync();
+
+ MessageBox.Show($"{pluginName} has been successfully cloned. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK);
}
private void OnAddButtonClicked()
@@ -730,6 +753,37 @@ namespace FlaxEditor.Windows
MessageBox.Show($"{pluginName} has been successfully created. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK);
}
+ private async Task AddModuleReferencesInGameModule(string pluginFolderPath)
+ {
+ // Common game build script location
+ var gameScript = Path.Combine(Globals.ProjectFolder, "Source/Game/Game.Build.cs");
+ if (File.Exists(gameScript))
+ {
+ var gameScriptContents = await File.ReadAllTextAsync(gameScript);
+ var insertLocation = gameScriptContents.IndexOf("base.Setup(options);", StringComparison.Ordinal);
+ if (insertLocation != -1)
+ {
+ insertLocation += 20;
+ var modifiedAny = false;
+
+ // Find all code modules in a plugin to auto-reference them in game build script
+ foreach (var subDir in Directory.GetDirectories(Path.Combine(pluginFolderPath, "Source")))
+ {
+ var pluginModuleName = Path.GetFileName(subDir);
+ var pluginModuleScriptPath = Path.Combine(subDir, pluginModuleName + ".Build.cs");
+ if (File.Exists(pluginModuleScriptPath))
+ {
+ gameScriptContents = gameScriptContents.Insert(insertLocation, $"\n options.PublicDependencies.Add(\"{pluginModuleName}\");");
+ modifiedAny = true;
+ }
+ }
+
+ if (modifiedAny)
+ await File.WriteAllTextAsync(gameScript, gameScriptContents, Encoding.UTF8);
+ }
+ }
+ }
+
private async Task AddReferenceToProject(string pluginFolderName, string pluginName)
{
// Project flax config file
diff --git a/Source/Editor/Windows/Profiler/Assets.cs b/Source/Editor/Windows/Profiler/Assets.cs
index 3ccf6e2eb..536d65a74 100644
--- a/Source/Editor/Windows/Profiler/Assets.cs
+++ b/Source/Editor/Windows/Profiler/Assets.cs
@@ -62,7 +62,9 @@ namespace FlaxEditor.Windows.Profiler
_memoryUsageChart.SelectedSampleChanged += OnSelectedSampleChanged;
// Table
- var headerColor = Style.Current.LightBackground;
+ var style = Style.Current;
+ var headerColor = style.LightBackground;
+ var textColor = style.Foreground;
_table = new Table
{
Columns = new[]
@@ -73,22 +75,26 @@ namespace FlaxEditor.Windows.Profiler
CellAlignment = TextAlignment.Near,
Title = "Resource",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Type",
CellAlignment = TextAlignment.Center,
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
},
new ColumnDefinition
{
Title = "References",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Memory Usage",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
FormatValue = v => Utilities.Utils.FormatBytesCount((ulong)v),
},
},
diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs
index fd4061276..0cbb3fa9b 100644
--- a/Source/Editor/Windows/Profiler/CPU.cs
+++ b/Source/Editor/Windows/Profiler/CPU.cs
@@ -92,7 +92,9 @@ namespace FlaxEditor.Windows.Profiler
};
// Table
- var headerColor = Style.Current.LightBackground;
+ var style = Style.Current;
+ var headerColor = style.LightBackground;
+ var textColor = style.Foreground;
_table = new Table
{
Columns = new[]
@@ -103,36 +105,42 @@ namespace FlaxEditor.Windows.Profiler
CellAlignment = TextAlignment.Near,
Title = "Event",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Total",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellPercentage,
+ TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Self",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellPercentage,
+ TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Time ms",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellMs,
+ TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Self ms",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellMs,
+ TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Memory",
TitleBackgroundColor = headerColor,
FormatValue = FormatCellBytes,
+ TitleColor = textColor,
},
},
Parent = layout,
diff --git a/Source/Editor/Windows/Profiler/GPU.cs b/Source/Editor/Windows/Profiler/GPU.cs
index 4ed18691a..d2c34d335 100644
--- a/Source/Editor/Windows/Profiler/GPU.cs
+++ b/Source/Editor/Windows/Profiler/GPU.cs
@@ -63,7 +63,9 @@ namespace FlaxEditor.Windows.Profiler
};
// Table
- var headerColor = Style.Current.LightBackground;
+ var style = Style.Current;
+ var headerColor = style.LightBackground;
+ var textColor = style.Foreground;
_table = new Table
{
Columns = new[]
@@ -74,35 +76,41 @@ namespace FlaxEditor.Windows.Profiler
CellAlignment = TextAlignment.Near,
Title = "Event",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Total",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
FormatValue = (x) => ((float)x).ToString("0.0") + '%',
},
new ColumnDefinition
{
Title = "GPU ms",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
FormatValue = (x) => ((float)x).ToString("0.000"),
},
new ColumnDefinition
{
Title = "Draw Calls",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
FormatValue = FormatCountLong,
},
new ColumnDefinition
{
Title = "Triangles",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
FormatValue = FormatCountLong,
},
new ColumnDefinition
{
Title = "Vertices",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
FormatValue = FormatCountLong,
},
},
diff --git a/Source/Editor/Windows/Profiler/MemoryGPU.cs b/Source/Editor/Windows/Profiler/MemoryGPU.cs
index 20a7898e0..e7c085362 100644
--- a/Source/Editor/Windows/Profiler/MemoryGPU.cs
+++ b/Source/Editor/Windows/Profiler/MemoryGPU.cs
@@ -63,7 +63,9 @@ namespace FlaxEditor.Windows.Profiler
_memoryUsageChart.SelectedSampleChanged += OnSelectedSampleChanged;
// Table
- var headerColor = Style.Current.LightBackground;
+ var style = Style.Current;
+ var headerColor = style.LightBackground;
+ var textColor = style.Foreground;
_table = new Table
{
Columns = new[]
@@ -74,18 +76,21 @@ namespace FlaxEditor.Windows.Profiler
CellAlignment = TextAlignment.Near,
Title = "Resource",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Type",
CellAlignment = TextAlignment.Center,
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Memory Usage",
TitleBackgroundColor = headerColor,
FormatValue = v => Utilities.Utils.FormatBytesCount((ulong)v),
+ TitleColor = textColor,
},
},
Parent = layout,
diff --git a/Source/Editor/Windows/Profiler/Network.cs b/Source/Editor/Windows/Profiler/Network.cs
index d29000d27..1ac9777c1 100644
--- a/Source/Editor/Windows/Profiler/Network.cs
+++ b/Source/Editor/Windows/Profiler/Network.cs
@@ -252,7 +252,9 @@ namespace FlaxEditor.Windows.Profiler
private static Table InitTable(ContainerControl parent, string name)
{
- var headerColor = Style.Current.LightBackground;
+ var style = Style.Current;
+ var headerColor = style.LightBackground;
+ var textColor = style.Foreground;
var table = new Table
{
Columns = new[]
@@ -263,28 +265,33 @@ namespace FlaxEditor.Windows.Profiler
CellAlignment = TextAlignment.Near,
Title = name,
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Count",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
},
new ColumnDefinition
{
Title = "Data Size",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
FormatValue = FormatCellBytes,
},
new ColumnDefinition
{
Title = "Message Size",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
FormatValue = FormatCellBytes,
},
new ColumnDefinition
{
Title = "Receivers",
TitleBackgroundColor = headerColor,
+ TitleColor = textColor,
},
},
Splits = new[]
diff --git a/Source/Editor/Windows/Profiler/SingleChart.cs b/Source/Editor/Windows/Profiler/SingleChart.cs
index e14480bee..4f36692e5 100644
--- a/Source/Editor/Windows/Profiler/SingleChart.cs
+++ b/Source/Editor/Windows/Profiler/SingleChart.cs
@@ -105,7 +105,7 @@ namespace FlaxEditor.Windows.Profiler
if (_selectedSampleIndex != -1)
{
float selectedX = Width - (_samples.Count - _selectedSampleIndex - 1) * PointsOffset;
- Render2D.DrawLine(new Float2(selectedX, 0), new Float2(selectedX, chartHeight), Color.White, 1.5f);
+ Render2D.DrawLine(new Float2(selectedX, 0), new Float2(selectedX, chartHeight), style.Foreground, 1.5f);
}
int samplesInViewCount = Math.Min((int)(Width / PointsOffset), _samples.Count) - 1;
@@ -138,8 +138,8 @@ namespace FlaxEditor.Windows.Profiler
var headerRect = new Rectangle(0, chartHeight, Width, TitleHeight);
var headerTextRect = new Rectangle(2, chartHeight, Width - 4, TitleHeight);
Render2D.FillRectangle(headerRect, style.BackgroundNormal);
- Render2D.DrawText(style.FontMedium, Title, headerTextRect, Color.White * 0.8f, TextAlignment.Near, TextAlignment.Center);
- Render2D.DrawText(style.FontMedium, _sample, headerTextRect, Color.White, TextAlignment.Far, TextAlignment.Center);
+ Render2D.DrawText(style.FontMedium, Title, headerTextRect, style.ForegroundGrey, TextAlignment.Near, TextAlignment.Center);
+ Render2D.DrawText(style.FontMedium, _sample, headerTextRect, style.Foreground, TextAlignment.Far, TextAlignment.Center);
}
private void OnClick(ref Float2 location)
diff --git a/Source/Editor/Windows/Profiler/Timeline.cs b/Source/Editor/Windows/Profiler/Timeline.cs
index a61ca1d05..59a7a0e26 100644
--- a/Source/Editor/Windows/Profiler/Timeline.cs
+++ b/Source/Editor/Windows/Profiler/Timeline.cs
@@ -90,7 +90,7 @@ namespace FlaxEditor.Windows.Profiler
if (_nameLength < bounds.Width + 4)
{
Render2D.PushClip(bounds);
- Render2D.DrawText(style.FontMedium, _name, bounds, Color.White, TextAlignment.Center, TextAlignment.Center);
+ Render2D.DrawText(style.FontMedium, _name, bounds, Style.Current.Foreground, TextAlignment.Center, TextAlignment.Center);
Render2D.PopClip();
}
}
@@ -115,7 +115,7 @@ namespace FlaxEditor.Windows.Profiler
var style = Style.Current;
var rect = new Rectangle(Float2.Zero, Size);
Render2D.PushClip(rect);
- Render2D.DrawText(style.FontMedium, Name, rect, Color.White, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapChars);
+ Render2D.DrawText(style.FontMedium, Name, rect, Style.Current.Foreground, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapChars);
Render2D.PopClip();
}
}
diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs
index cbaa27371..63ba7b960 100644
--- a/Source/Editor/Windows/SceneTreeWindow.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.cs
@@ -142,7 +142,7 @@ namespace FlaxEditor.Windows
{
if (selection.Count != 0)
Editor.SceneEditing.Select(actor);
- actor.TreeNode.StartRenaming(this);
+ actor.TreeNode.StartRenaming(this, _sceneTreePanel);
}
}
@@ -423,6 +423,7 @@ namespace FlaxEditor.Windows
var actor = item.OnEditorDrop(this);
actor.Name = item.ShortName;
Level.SpawnActor(actor);
+ Editor.Scene.MarkSceneEdited(actor.Scene);
}
result = DragDropEffect.Move;
}
@@ -440,6 +441,7 @@ namespace FlaxEditor.Windows
}
actor.Name = item.Name;
Level.SpawnActor(actor);
+ Editor.Scene.MarkSceneEdited(actor.Scene);
}
result = DragDropEffect.Move;
}
diff --git a/Source/Editor/Windows/Search/ContentSearchWindow.cs b/Source/Editor/Windows/Search/ContentSearchWindow.cs
index e6e407247..305fa16d6 100644
--- a/Source/Editor/Windows/Search/ContentSearchWindow.cs
+++ b/Source/Editor/Windows/Search/ContentSearchWindow.cs
@@ -72,6 +72,7 @@ namespace FlaxEngine.Windows.Search
///
/// The content searching window. Allows to search inside Visual Scripts, Materials, Particles and other assets.
///
+ [HideInEditor]
internal class ContentSearchWindow : EditorWindow
{
///
@@ -115,6 +116,7 @@ namespace FlaxEngine.Windows.Search
}
}
+ [HideInEditor]
private sealed class SearchResultTreeNode : TreeNode
{
public Action Navigate;
diff --git a/Source/Editor/Windows/VisualScriptDebuggerWindow.cs b/Source/Editor/Windows/VisualScriptDebuggerWindow.cs
index c84a9ba33..57f10f2f4 100644
--- a/Source/Editor/Windows/VisualScriptDebuggerWindow.cs
+++ b/Source/Editor/Windows/VisualScriptDebuggerWindow.cs
@@ -399,6 +399,8 @@ namespace FlaxEditor.Windows
{
Title = "Visual Script Debugger";
+ var inputOptions = editor.Options.Options.Input;
+
var toolstrip = new ToolStrip
{
Parent = this
@@ -407,7 +409,7 @@ namespace FlaxEditor.Windows
_debugToolstripControls = new[]
{
toolstrip.AddSeparator(),
- toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip("Continue (F5)"),
+ toolstrip.AddButton(editor.Icons.Play64, OnDebuggerContinue).LinkTooltip($"Continue ({inputOptions.DebuggerContinue})"),
toolstrip.AddButton(editor.Icons.Search64, OnDebuggerNavigateToCurrentNode).LinkTooltip("Navigate to the current stack trace node"),
toolstrip.AddButton(editor.Icons.Stop64, OnDebuggerStop).LinkTooltip("Stop debugging"),
};
diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp
index a5b24fc8b..98fb9ba22 100644
--- a/Source/Engine/AI/Behavior.cpp
+++ b/Source/Engine/AI/Behavior.cpp
@@ -114,14 +114,19 @@ void Behavior::UpdateAsync()
void Behavior::StartLogic()
{
+ if (_result == BehaviorUpdateResult::Running)
+ return;
PROFILE_CPU();
- // Ensure to have tree loaded on begin play
+ // Ensure to have tree loaded on play
CHECK(Tree && !Tree->WaitForLoaded());
BehaviorTree* tree = Tree.Get();
CHECK(tree->Graph.Root);
+ // Setup state
_result = BehaviorUpdateResult::Running;
+ _accumulatedTime = 0.0f;
+ _totalTime = 0;
// Init knowledge
_knowledge.InitMemory(tree);
@@ -135,6 +140,7 @@ void Behavior::StopLogic(BehaviorUpdateResult result)
_accumulatedTime = 0.0f;
_totalTime = 0;
_result = result;
+ _knowledge.FreeMemory();
}
void Behavior::ResetLogic()
@@ -170,7 +176,11 @@ void Behavior::OnDisable()
bool Behavior::GetNodeDebugRelevancy(const BehaviorTreeNode* node, const Behavior* behavior)
{
- return node && behavior && node->_executionIndex != -1 && behavior->_knowledge.RelevantNodes.Get(node->_executionIndex);
+ return node &&
+ behavior &&
+ node->_executionIndex >= 0 &&
+ node->_executionIndex < behavior->_knowledge.RelevantNodes.Count() &&
+ behavior->_knowledge.RelevantNodes.Get(node->_executionIndex);
}
String Behavior::GetNodeDebugInfo(const BehaviorTreeNode* node, Behavior* behavior)
@@ -179,7 +189,7 @@ String Behavior::GetNodeDebugInfo(const BehaviorTreeNode* node, Behavior* behavi
return String::Empty;
BehaviorUpdateContext context;
Platform::MemoryClear(&context, sizeof(context));
- if (behavior && node->_executionIndex != -1 && behavior->_knowledge.RelevantNodes.Get(node->_executionIndex))
+ if (GetNodeDebugRelevancy(node, behavior))
{
// Pass behavior and knowledge data only for relevant nodes to properly access it
context.Behavior = behavior;
diff --git a/Source/Engine/AI/BehaviorKnowledge.cpp b/Source/Engine/AI/BehaviorKnowledge.cpp
index a33738d31..a73a1cfd4 100644
--- a/Source/Engine/AI/BehaviorKnowledge.cpp
+++ b/Source/Engine/AI/BehaviorKnowledge.cpp
@@ -83,7 +83,7 @@ bool AccessVariant(Variant& instance, const StringAnsiView& member, Variant& val
}
}
#endif
- else
+ else if (typeName.HasChars())
{
LOG(Warning, "Missing scripting type \'{0}\'", String(typeName));
}
@@ -150,7 +150,13 @@ void BehaviorKnowledge::InitMemory(BehaviorTree* tree)
RelevantNodes.Resize(tree->Graph.NodesCount, false);
RelevantNodes.SetAll(false);
if (!Memory && tree->Graph.NodesStatesSize)
+ {
Memory = Allocator::Allocate(tree->Graph.NodesStatesSize);
+#if !BUILD_RELEASE
+ // Clear memory to make it easier to spot missing data issues (eg. zero GCHandle in C# BT node due to missing state init)
+ Platform::MemoryClear(Memory, tree->Graph.NodesStatesSize);
+#endif
+ }
}
void BehaviorKnowledge::FreeMemory()
diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.cs b/Source/Engine/AI/BehaviorKnowledgeSelector.cs
index 84e923ccf..5c642e92a 100644
--- a/Source/Engine/AI/BehaviorKnowledgeSelector.cs
+++ b/Source/Engine/AI/BehaviorKnowledgeSelector.cs
@@ -202,7 +202,7 @@ namespace FlaxEngine
public T Get(BehaviorKnowledge knowledge)
{
if (knowledge != null && knowledge.Get(Path, out var value))
- return (T)value;
+ return Utilities.VariantUtils.Cast(value);
return default;
}
@@ -218,7 +218,7 @@ namespace FlaxEngine
object tmp = null;
bool result = knowledge != null && knowledge.Get(Path, out tmp);
if (result)
- value = (T)tmp;
+ value = Utilities.VariantUtils.Cast(tmp);
return result;
}
diff --git a/Source/Engine/AI/BehaviorTree.cs b/Source/Engine/AI/BehaviorTree.cs
index 699c72976..a1c863e6b 100644
--- a/Source/Engine/AI/BehaviorTree.cs
+++ b/Source/Engine/AI/BehaviorTree.cs
@@ -95,12 +95,16 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T GetState(IntPtr memory) where T : struct
{
- var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer();
- var handle = GCHandle.FromIntPtr(Unsafe.Read(ptr));
+ var ptr = Unsafe.Read(IntPtr.Add(memory, _memoryOffset).ToPointer());
+#if !BUILD_RELEASE
+ if (ptr == IntPtr.Zero)
+ throw new Exception($"Missing state '{typeof(T).FullName}' for node '{GetType().FullName}'");
+#endif
+ var handle = GCHandle.FromIntPtr(ptr);
var state = handle.Target;
#if !BUILD_RELEASE
if (state == null)
- throw new NullReferenceException();
+ throw new Exception($"Missing state '{typeof(T).FullName}' for node '{GetType().FullName}'");
#endif
return ref Unsafe.Unbox(state);
}
@@ -111,8 +115,10 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FreeState(IntPtr memory)
{
- var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer();
- var handle = GCHandle.FromIntPtr(Unsafe.Read(ptr));
+ var ptr = Unsafe.Read(IntPtr.Add(memory, _memoryOffset).ToPointer());
+ if (ptr == IntPtr.Zero)
+ return;
+ var handle = GCHandle.FromIntPtr(ptr);
handle.Free();
}
}
diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp
index 4be336b35..fb49a007e 100644
--- a/Source/Engine/AI/BehaviorTreeNodes.cpp
+++ b/Source/Engine/AI/BehaviorTreeNodes.cpp
@@ -85,6 +85,8 @@ BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext&
result = BehaviorUpdateResult::Failed;
else
result = Update(context);
+ if ((int32)result < 0 || (int32)result > (int32)BehaviorUpdateResult::Failed)
+ result = BehaviorUpdateResult::Failed; // Invalid value is a failure
// Post-process result from decorators
for (BehaviorTreeDecorator* decorator : _decorators)
diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
index 0518fe248..6f0d7661b 100644
--- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
+++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
@@ -1342,7 +1342,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
{
const bool xAxis = Math::IsZero(v0.X) && Math::IsZero(v1.X);
const bool yAxis = Math::IsZero(v0.Y) && Math::IsZero(v1.Y);
- if (xAxis || yAxis)
+ if (xAxis && yAxis)
+ {
+ // Single animation
+ value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W);
+ }
+ else if (xAxis || yAxis)
{
if (yAxis)
{
@@ -2109,7 +2114,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
bucket.LoopsLeft--;
bucket.LoopsDone++;
}
- value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, slot.Speed);
+ // Speed is accounted for in the new time pos, so keep sample speed at 1
+ value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, 1);
bucket.TimePosition = newTimePos;
if (bucket.LoopsLeft == 0 && slot.BlendOutTime > 0.0f && length - slot.BlendOutTime < bucket.TimePosition)
{
diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp
index d04da7274..689f38d12 100644
--- a/Source/Engine/Audio/AudioClip.cpp
+++ b/Source/Engine/Audio/AudioClip.cpp
@@ -225,6 +225,7 @@ bool AudioClip::ExtractDataRaw(Array& resultData, AudioDataInfo& resultDat
void AudioClip::CancelStreaming()
{
+ Asset::CancelStreaming();
CancelStreamingTasks();
}
diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp
index 613c7c2c2..93d904d5e 100644
--- a/Source/Engine/Content/Asset.cpp
+++ b/Source/Engine/Content/Asset.cpp
@@ -16,11 +16,13 @@
AssetReferenceBase::~AssetReferenceBase()
{
- if (_asset)
+ Asset* asset = _asset;
+ if (asset)
{
- _asset->OnLoaded.Unbind(this);
- _asset->OnUnloaded.Unbind(this);
- _asset->RemoveReference();
+ _asset = nullptr;
+ asset->OnLoaded.Unbind(this);
+ asset->OnUnloaded.Unbind(this);
+ asset->RemoveReference();
}
}
@@ -70,8 +72,12 @@ void AssetReferenceBase::OnUnloaded(Asset* asset)
WeakAssetReferenceBase::~WeakAssetReferenceBase()
{
- if (_asset)
- _asset->OnUnloaded.Unbind(this);
+ Asset* asset = _asset;
+ if (asset)
+ {
+ _asset = nullptr;
+ asset->OnUnloaded.Unbind(this);
+ }
}
String WeakAssetReferenceBase::ToString() const
@@ -101,6 +107,20 @@ void WeakAssetReferenceBase::OnUnloaded(Asset* asset)
_asset = nullptr;
}
+SoftAssetReferenceBase::~SoftAssetReferenceBase()
+{
+ Asset* asset = _asset;
+ if (asset)
+ {
+ _asset = nullptr;
+ asset->OnUnloaded.Unbind(this);
+ asset->RemoveReference();
+ }
+#if !BUILD_RELEASE
+ _id = Guid::Empty;
+#endif
+}
+
String SoftAssetReferenceBase::ToString() const
{
return _asset ? _asset->ToString() : (_id.IsValid() ? _id.ToString() : TEXT(""));
@@ -502,6 +522,14 @@ void Asset::InitAsVirtual()
void Asset::CancelStreaming()
{
+ // Cancel loading task but go over asset locker to prevent case if other load threads still loads asset while it's reimported on other thread
+ Locker.Lock();
+ ContentLoadTask* loadTask = _loadingTask;
+ Locker.Unlock();
+ if (loadTask)
+ {
+ loadTask->Cancel();
+ }
}
#if USE_EDITOR
diff --git a/Source/Engine/Content/AssetReference.h b/Source/Engine/Content/AssetReference.h
index 6aee12246..b9d54f30a 100644
--- a/Source/Engine/Content/AssetReference.h
+++ b/Source/Engine/Content/AssetReference.h
@@ -9,9 +9,6 @@
///
class FLAXENGINE_API AssetReferenceBase
{
-public:
- typedef Delegate<> EventType;
-
protected:
Asset* _asset = nullptr;
@@ -19,17 +16,17 @@ public:
///
/// The asset loaded event (fired when asset gets loaded or is already loaded after change).
///
- EventType Loaded;
+ Action Loaded;
///
/// The asset unloading event (should cleanup refs to it).
///
- EventType Unload;
+ Action Unload;
///
/// Action fired when field gets changed (link a new asset or change to the another value).
///
- EventType Changed;
+ Action Changed;
public:
NON_COPYABLE(AssetReferenceBase);
diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp
index 5a008645d..691b00a50 100644
--- a/Source/Engine/Content/Assets/Model.cpp
+++ b/Source/Engine/Content/Assets/Model.cpp
@@ -783,6 +783,7 @@ void Model::InitAsVirtual()
void Model::CancelStreaming()
{
+ Asset::CancelStreaming();
CancelStreamingTasks();
}
diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp
index 5871087d9..b823db5a3 100644
--- a/Source/Engine/Content/Assets/SkinnedModel.cpp
+++ b/Source/Engine/Content/Assets/SkinnedModel.cpp
@@ -969,6 +969,7 @@ void SkinnedModel::InitAsVirtual()
void SkinnedModel::CancelStreaming()
{
+ Asset::CancelStreaming();
CancelStreamingTasks();
}
diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp
index 105d4ad2d..9748ba60c 100644
--- a/Source/Engine/Content/Assets/VisualScript.cpp
+++ b/Source/Engine/Content/Assets/VisualScript.cpp
@@ -1428,6 +1428,10 @@ Asset::LoadResult VisualScript::load()
#if USE_EDITOR
if (_instances.HasItems())
{
+ // Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use
+ _loadFailed = false;
+ _isLoaded = true;
+
// Setup scripting type
CacheScriptingType();
@@ -1512,7 +1516,7 @@ void VisualScript::unload(bool isReloading)
// Note: preserve the registered scripting type but invalidate the locally cached handle
if (_scriptingTypeHandle)
{
- VisualScriptingModule.Locker.Lock();
+ VisualScriptingBinaryModule::Locker.Lock();
auto& type = VisualScriptingModule.Types[_scriptingTypeHandle.TypeIndex];
if (type.Script.DefaultInstance)
{
@@ -1523,7 +1527,7 @@ void VisualScript::unload(bool isReloading)
VisualScriptingModule.Scripts[_scriptingTypeHandle.TypeIndex] = nullptr;
_scriptingTypeHandleCached = _scriptingTypeHandle;
_scriptingTypeHandle = ScriptingTypeHandle();
- VisualScriptingModule.Locker.Unlock();
+ VisualScriptingBinaryModule::Locker.Unlock();
}
}
@@ -1534,8 +1538,8 @@ AssetChunksFlag VisualScript::getChunksToPreload() const
void VisualScript::CacheScriptingType()
{
+ ScopeLock lock(VisualScriptingBinaryModule::Locker);
auto& binaryModule = VisualScriptingModule;
- ScopeLock lock(binaryModule.Locker);
// Find base type
const StringAnsi baseTypename(Meta.BaseTypename);
diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp
index b04603a96..9519f73df 100644
--- a/Source/Engine/Content/BinaryAsset.cpp
+++ b/Source/Engine/Content/BinaryAsset.cpp
@@ -323,26 +323,29 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool
{
// Ensure path is in a valid format
String pathNorm(path);
- FileSystem::NormalizePath(pathNorm);
+ ContentStorageManager::FormatPath(pathNorm);
+ const StringView filePath = pathNorm;
// Find target storage container and the asset
- auto storage = ContentStorageManager::TryGetStorage(pathNorm);
- auto asset = Content::GetAsset(pathNorm);
+ auto storage = ContentStorageManager::TryGetStorage(filePath);
+ auto asset = Content::GetAsset(filePath);
auto binaryAsset = dynamic_cast(asset);
if (asset && !binaryAsset)
{
LOG(Warning, "Cannot write to the non-binary asset location.");
return true;
}
+ if (!binaryAsset && !storage && FileSystem::FileExists(filePath))
+ {
+ // Force-resolve storage (asset at that path could be not yet loaded into registry)
+ storage = ContentStorageManager::GetStorage(filePath);
+ }
// Check if can perform write operation to the asset container
- if (storage)
+ if (storage && !storage->AllowDataModifications())
{
- if (!storage->AllowDataModifications())
- {
- LOG(Warning, "Cannot write to the asset storage container.");
- return true;
- }
+ LOG(Warning, "Cannot write to the asset storage container.");
+ return true;
}
// Initialize data container
@@ -352,6 +355,11 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool
// Use the same asset ID
data.Header.ID = binaryAsset->GetID();
}
+ else if (storage && storage->GetEntriesCount())
+ {
+ // Use the same file ID
+ data.Header.ID = storage->GetEntry(0).ID;
+ }
else
{
// Randomize ID
@@ -373,8 +381,8 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool
}
else
{
- ASSERT(pathNorm.HasChars());
- result = FlaxStorage::Create(pathNorm, data, silentMode);
+ ASSERT(filePath.HasChars());
+ result = FlaxStorage::Create(filePath, data, silentMode);
}
if (binaryAsset)
binaryAsset->_isSaving = false;
diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp
index bd27abc5a..982ae599f 100644
--- a/Source/Engine/Content/Content.cpp
+++ b/Source/Engine/Content/Content.cpp
@@ -54,8 +54,7 @@ namespace
// Assets
CriticalSection AssetsLocker;
Dictionary Assets(2048);
- CriticalSection LoadCallAssetsLocker;
- Array LoadCallAssets(64);
+ Array LoadCallAssets(PLATFORM_THREADS_LIMIT);
CriticalSection LoadedAssetsToInvokeLocker;
Array LoadedAssetsToInvoke(64);
Array ToUnload;
@@ -449,18 +448,19 @@ Asset* Content::LoadAsync(const StringView& path, const ScriptingTypeHandle& typ
{
// Ensure path is in a valid format
String pathNorm(path);
- StringUtils::PathRemoveRelativeParts(pathNorm);
+ ContentStorageManager::FormatPath(pathNorm);
+ const StringView filePath = pathNorm;
#if USE_EDITOR
- if (!FileSystem::FileExists(pathNorm))
+ if (!FileSystem::FileExists(filePath))
{
- LOG(Error, "Missing file \'{0}\'", pathNorm);
+ LOG(Error, "Missing file \'{0}\'", filePath);
return nullptr;
}
#endif
AssetInfo assetInfo;
- if (GetAssetInfo(pathNorm, assetInfo))
+ if (GetAssetInfo(filePath, assetInfo))
{
return LoadAsync(assetInfo.ID, type);
}
@@ -910,9 +910,13 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
return nullptr;
// Check if asset has been already loaded
- Asset* result = GetAsset(id);
+ Asset* result = nullptr;
+ AssetsLocker.Lock();
+ Assets.TryGet(id, result);
if (result)
{
+ AssetsLocker.Unlock();
+
// Validate type
if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type))
{
@@ -923,57 +927,41 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
}
// Check if that asset is during loading
- LoadCallAssetsLocker.Lock();
if (LoadCallAssets.Contains(id))
{
- LoadCallAssetsLocker.Unlock();
+ AssetsLocker.Unlock();
- // Wait for load end
- // TODO: dont use active waiting and prevent deadlocks if running on a main thread
- //while (!Engine::ShouldExit())
- while (true)
+ // Wait for loading end by other thread
+ bool contains = true;
+ while (contains)
{
- LoadCallAssetsLocker.Lock();
- const bool contains = LoadCallAssets.Contains(id);
- LoadCallAssetsLocker.Unlock();
- if (!contains)
- return GetAsset(id);
Platform::Sleep(1);
+ AssetsLocker.Lock();
+ contains = LoadCallAssets.Contains(id);
+ AssetsLocker.Unlock();
}
- }
- else
- {
- // Mark asset as loading
- LoadCallAssets.Add(id);
- LoadCallAssetsLocker.Unlock();
+ Assets.TryGet(id, result);
+ return result;
}
- // Load asset
- AssetInfo assetInfo;
- result = load(id, type, assetInfo);
+ // Mark asset as loading and release lock so other threads can load other assets
+ LoadCallAssets.Add(id);
+ AssetsLocker.Unlock();
- // End loading
- LoadCallAssetsLocker.Lock();
- LoadCallAssets.Remove(id);
- LoadCallAssetsLocker.Unlock();
+#define LOAD_FAILED() AssetsLocker.Lock(); LoadCallAssets.Remove(id); AssetsLocker.Unlock(); return nullptr
- return result;
-}
-
-Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& assetInfo)
-{
// Get cached asset info (from registry)
+ AssetInfo assetInfo;
if (!GetAssetInfo(id, assetInfo))
{
LOG(Warning, "Invalid or missing asset ({0}, {1}).", id, type.ToString());
- return nullptr;
+ LOAD_FAILED();
}
-
#if ASSETS_LOADING_EXTRA_VERIFICATION
if (!FileSystem::FileExists(assetInfo.Path))
{
LOG(Error, "Cannot find file '{0}'", assetInfo.Path);
- return nullptr;
+ LOAD_FAILED();
}
#endif
@@ -982,28 +970,27 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
if (factory == nullptr)
{
LOG(Error, "Cannot find asset factory. Info: {0}", assetInfo.ToString());
- return nullptr;
+ LOAD_FAILED();
}
// Create asset object
- auto result = factory->New(assetInfo);
+ result = factory->New(assetInfo);
if (result == nullptr)
{
LOG(Error, "Cannot create asset object. Info: {0}", assetInfo.ToString());
- return nullptr;
+ LOAD_FAILED();
}
-
+ ASSERT(result->GetID() == id);
#if ASSETS_LOADING_EXTRA_VERIFICATION
if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type))
{
- LOG(Error, "Different loaded asset type! Asset: '{0}'. Expected type: {1}", assetInfo.ToString(), type.ToString());
+ LOG(Warning, "Different loaded asset type! Asset: '{0}'. Expected type: {1}", assetInfo.ToString(), type.ToString());
result->DeleteObject();
- return nullptr;
+ LOAD_FAILED();
}
#endif
// Register asset
- ASSERT(result->GetID() == id);
AssetsLocker.Lock();
#if ASSETS_LOADING_EXTRA_VERIFICATION
ASSERT(!Assets.ContainsKey(id));
@@ -1011,11 +998,14 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
Assets.Add(id, result);
// Start asset loading
- // TODO: refactor this to create asset loading task-chain before AssetsLocker.Lock() to allow better parallelization
result->startLoading();
+ // Remove from the loading queue and release lock
+ LoadCallAssets.Remove(id);
AssetsLocker.Unlock();
+#undef LOAD_FAILED
+
return result;
}
diff --git a/Source/Engine/Content/Content.h b/Source/Engine/Content/Content.h
index 6393ce48b..cce57194c 100644
--- a/Source/Engine/Content/Content.h
+++ b/Source/Engine/Content/Content.h
@@ -366,7 +366,6 @@ private:
static void onAssetLoaded(Asset* asset);
static void onAssetUnload(Asset* asset);
static void onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId);
- static Asset* load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& assetInfo);
private:
static void deleteFileSafety(const StringView& path, const Guid& id);
diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h
index 23cf5f787..4a7bbb2bb 100644
--- a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h
+++ b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h
@@ -48,6 +48,8 @@ protected:
// [ContentLoadTask]
Result run() override
{
+ if (IsCancelRequested())
+ return Result::Ok;
PROFILE_CPU();
AssetReference ref = _asset.Get();
@@ -67,8 +69,6 @@ protected:
{
if (IsCancelRequested())
return Result::Ok;
-
- // Load it
#if TRACY_ENABLE
ZoneScoped;
ZoneName(*name, name.Length());
diff --git a/Source/Engine/Content/SoftAssetReference.h b/Source/Engine/Content/SoftAssetReference.h
index fe1cde8c2..d237b5fd7 100644
--- a/Source/Engine/Content/SoftAssetReference.h
+++ b/Source/Engine/Content/SoftAssetReference.h
@@ -30,9 +30,7 @@ public:
///
/// Finalizes an instance of the class.
///
- ~SoftAssetReferenceBase()
- {
- }
+ ~SoftAssetReferenceBase();
public:
///
diff --git a/Source/Engine/Content/Storage/ContentStorageManager.cpp b/Source/Engine/Content/Storage/ContentStorageManager.cpp
index d3e18e9d0..61e73a3f2 100644
--- a/Source/Engine/Content/Storage/ContentStorageManager.cpp
+++ b/Source/Engine/Content/Storage/ContentStorageManager.cpp
@@ -6,6 +6,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/EngineService.h"
+#include "Engine/Engine/Globals.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/TaskGraph.h"
@@ -185,6 +186,16 @@ void ContentStorageManager::EnsureUnlocked()
Locker.Unlock();
}
+void ContentStorageManager::FormatPath(String& path)
+{
+ StringUtils::PathRemoveRelativeParts(path);
+ if (FileSystem::IsRelative(path))
+ {
+ // Convert local-project paths into absolute format which is used by Content Storage system
+ path = Globals::ProjectFolder / path;
+ }
+}
+
bool ContentStorageManager::IsFlaxStoragePath(const String& path)
{
auto extension = FileSystem::GetExtension(path).ToLower();
diff --git a/Source/Engine/Content/Storage/ContentStorageManager.h b/Source/Engine/Content/Storage/ContentStorageManager.h
index c615632e9..84a6dc07e 100644
--- a/Source/Engine/Content/Storage/ContentStorageManager.h
+++ b/Source/Engine/Content/Storage/ContentStorageManager.h
@@ -75,6 +75,9 @@ public:
///
static void EnsureUnlocked();
+ // Formats path into valid format used by the storage system (normalized and absolute).
+ static void FormatPath(String& path);
+
public:
///
/// Determines whether the specified path can be a binary asset file (based on it's extension).
diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp
index d530e5456..a47e0bd0e 100644
--- a/Source/Engine/Content/Storage/FlaxStorage.cpp
+++ b/Source/Engine/Content/Storage/FlaxStorage.cpp
@@ -1302,15 +1302,15 @@ void FlaxStorage::CloseFileHandles()
// In those situations all the async tasks using this storage should be cancelled externally
// Ensure that no one is using this resource
- int32 waitTime = 10;
+ int32 waitTime = 100;
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
- Platform::Sleep(10);
+ Platform::Sleep(1);
if (Platform::AtomicRead(&_chunksLock) != 0)
{
// File can be locked by some streaming tasks (eg. AudioClip::StreamingTask or StreamModelLODTask)
+ Entry e;
for (int32 i = 0; i < GetEntriesCount(); i++)
{
- Entry e;
GetEntry(i, e);
Asset* asset = Content::GetAsset(e.ID);
if (asset)
@@ -1320,8 +1320,12 @@ void FlaxStorage::CloseFileHandles()
}
}
}
+ waitTime = 100;
+ while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
+ Platform::Sleep(1);
ASSERT(_chunksLock == 0);
+ // Close file handles (from all threads)
_file.DeleteAll();
}
diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h
index 58117cf0a..cf45ed060 100644
--- a/Source/Engine/Core/Collections/Array.h
+++ b/Source/Engine/Core/Collections/Array.h
@@ -25,6 +25,19 @@ private:
int32 _capacity;
AllocationData _allocation;
+ FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromCount, int32 fromCapacity)
+ {
+ if IF_CONSTEXPR (AllocationType::HasSwap)
+ to.Swap(from);
+ else
+ {
+ to.Allocate(fromCapacity);
+ Memory::MoveItems(to.Get(), from.Get(), fromCount);
+ Memory::DestructItems(from.Get(), fromCount);
+ from.Free();
+ }
+ }
+
public:
///
/// Initializes a new instance of the class.
@@ -134,7 +147,7 @@ public:
_capacity = other._capacity;
other._count = 0;
other._capacity = 0;
- _allocation.Swap(other._allocation);
+ MoveToEmpty(_allocation, other._allocation, _count, _capacity);
}
///
@@ -191,7 +204,7 @@ public:
_capacity = other._capacity;
other._count = 0;
other._capacity = 0;
- _allocation.Swap(other._allocation);
+ MoveToEmpty(_allocation, other._allocation, _count, _capacity);
}
return *this;
}
@@ -713,9 +726,18 @@ public:
/// The other collection.
void Swap(Array& other)
{
- ::Swap(_count, other._count);
- ::Swap(_capacity, other._capacity);
- _allocation.Swap(other._allocation);
+ if IF_CONSTEXPR (AllocationType::HasSwap)
+ {
+ _allocation.Swap(other._allocation);
+ ::Swap(_count, other._count);
+ ::Swap(_capacity, other._capacity);
+ }
+ else
+ {
+ Array tmp = MoveTemp(other);
+ other = *this;
+ *this = MoveTemp(tmp);
+ }
}
///
@@ -726,9 +748,7 @@ public:
T* data = _allocation.Get();
const int32 count = _count / 2;
for (int32 i = 0; i < count; i++)
- {
::Swap(data[i], data[_count - i - 1]);
- }
}
public:
diff --git a/Source/Engine/Core/Collections/BitArray.h b/Source/Engine/Core/Collections/BitArray.h
index 01238d434..eeadc82e9 100644
--- a/Source/Engine/Core/Collections/BitArray.h
+++ b/Source/Engine/Core/Collections/BitArray.h
@@ -22,6 +22,16 @@ private:
int32 _capacity;
AllocationData _allocation;
+ FORCE_INLINE static int32 ToItemCount(int32 size)
+ {
+ return Math::DivideAndRoundUp(size, sizeof(ItemType));
+ }
+
+ FORCE_INLINE static int32 ToItemCapacity(int32 size)
+ {
+ return Math::Max(Math::DivideAndRoundUp(size, sizeof(ItemType)), 1);
+ }
+
public:
///
/// Initializes a new instance of the class.
@@ -41,7 +51,7 @@ public:
, _capacity(capacity)
{
if (capacity > 0)
- _allocation.Allocate(Math::Max(capacity / sizeof(ItemType), 1));
+ _allocation.Allocate(ToItemCapacity(capacity));
}
///
@@ -53,7 +63,7 @@ public:
_count = _capacity = other.Count();
if (_capacity > 0)
{
- const uint64 itemsCapacity = Math::Max(_capacity / sizeof(ItemType), 1);
+ const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -69,7 +79,7 @@ public:
_count = _capacity = other.Count();
if (_capacity > 0)
{
- const uint64 itemsCapacity = Math::Max(_capacity / sizeof(ItemType), 1);
+ const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -101,7 +111,7 @@ public:
{
_allocation.Free();
_capacity = other._count;
- const uint64 itemsCapacity = Math::Max(_capacity / sizeof(ItemType), 1);
+ const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -246,7 +256,7 @@ public:
return;
ASSERT(capacity >= 0);
const int32 count = preserveContents ? (_count < capacity ? _count : capacity) : 0;
- _allocation.Relocate(Math::Max(capacity / sizeof(ItemType), 1), _count / sizeof(ItemType), count / sizeof(ItemType));
+ _allocation.Relocate(ToItemCapacity(capacity), ToItemCount(_count), ToItemCount(count));
_capacity = capacity;
_count = count;
}
@@ -272,7 +282,7 @@ public:
{
if (_capacity < minCapacity)
{
- const int32 capacity = _allocation.CalculateCapacityGrow(Math::Max(_capacity / sizeof(ItemType), 1), minCapacity);
+ const int32 capacity = _allocation.CalculateCapacityGrow(ToItemCapacity(_capacity), minCapacity);
SetCapacity(capacity, preserveContents);
}
}
@@ -284,7 +294,7 @@ public:
void SetAll(const bool value)
{
if (_count != 0)
- Platform::MemorySet(_allocation.Get(), Math::Max(_count / sizeof(ItemType), 1), value ? MAX_int32 : 0);
+ Platform::MemorySet(_allocation.Get(), ToItemCount(_count) * sizeof(ItemType), value ? MAX_uint32 : 0);
}
///
diff --git a/Source/Engine/Core/Collections/Config.h b/Source/Engine/Core/Collections/Config.h
index 792ae57c8..ce7656dcd 100644
--- a/Source/Engine/Core/Collections/Config.h
+++ b/Source/Engine/Core/Collections/Config.h
@@ -2,13 +2,26 @@
#pragma once
-///
-/// Default capacity for the dictionaries (amount of space for the elements)
-///
-#define DICTIONARY_DEFAULT_CAPACITY 256
+#include "Engine/Platform/Defines.h"
///
-/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size)
+/// Default capacity for the dictionaries (amount of space for the elements).
+///
+#ifndef DICTIONARY_DEFAULT_CAPACITY
+#if PLATFORM_DESKTOP
+#define DICTIONARY_DEFAULT_CAPACITY 256
+#else
+#define DICTIONARY_DEFAULT_CAPACITY 64
+#endif
+#endif
+
+///
+/// Default slack space divider for the dictionaries.
+///
+#define DICTIONARY_DEFAULT_SLACK_SCALE 3
+
+///
+/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size).
///
#define DICTIONARY_PROB_FUNC(size, numChecks) (numChecks)
//#define DICTIONARY_PROB_FUNC(size, numChecks) (1)
diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h
index 575863dc9..dd73be390 100644
--- a/Source/Engine/Core/Collections/Dictionary.h
+++ b/Source/Engine/Core/Collections/Dictionary.h
@@ -40,7 +40,7 @@ public:
private:
State _state;
- void Free()
+ FORCE_INLINE void Free()
{
if (_state == Occupied)
{
@@ -50,7 +50,7 @@ public:
_state = Empty;
}
- void Delete()
+ FORCE_INLINE void Delete()
{
_state = Deleted;
Memory::DestructItem(&Key);
@@ -58,7 +58,7 @@ public:
}
template
- void Occupy(const KeyComparableType& key)
+ FORCE_INLINE void Occupy(const KeyComparableType& key)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::ConstructItem(&Value);
@@ -66,7 +66,7 @@ public:
}
template
- void Occupy(const KeyComparableType& key, const ValueType& value)
+ FORCE_INLINE void Occupy(const KeyComparableType& key, const ValueType& value)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::ConstructItems(&Value, &value, 1);
@@ -74,7 +74,7 @@ public:
}
template
- void Occupy(const KeyComparableType& key, ValueType&& value)
+ FORCE_INLINE void Occupy(const KeyComparableType& key, ValueType&& value)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::MoveItems(&Value, &value, 1);
@@ -110,6 +110,33 @@ private:
int32 _size = 0;
AllocationData _allocation;
+ FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromSize)
+ {
+ if IF_CONSTEXPR (AllocationType::HasSwap)
+ to.Swap(from);
+ else
+ {
+ to.Allocate(fromSize);
+ Bucket* toData = to.Get();
+ Bucket* fromData = from.Get();
+ for (int32 i = 0; i < fromSize; i++)
+ {
+ Bucket& fromBucket = fromData[i];
+ if (fromBucket.IsOccupied())
+ {
+ Bucket& toBucket = toData[i];
+ Memory::MoveItems(&toBucket.Key, &fromBucket.Key, 1);
+ Memory::MoveItems(&toBucket.Value, &fromBucket.Value, 1);
+ toBucket._state = Bucket::Occupied;
+ Memory::DestructItem(&fromBucket.Key);
+ Memory::DestructItem(&fromBucket.Value);
+ fromBucket._state = Bucket::Empty;
+ }
+ }
+ from.Free();
+ }
+ }
+
public:
///
/// Initializes a new instance of the class.
@@ -132,9 +159,6 @@ public:
///
/// The other collection to move.
Dictionary(Dictionary&& other) noexcept
- : _elementsCount(other._elementsCount)
- , _deletedCount(other._deletedCount)
- , _size(other._size)
{
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
@@ -142,7 +166,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
- _allocation.Swap(other._allocation);
+ MoveToEmpty(_allocation, other._allocation, _size);
}
///
@@ -183,7 +207,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
- _allocation.Swap(other._allocation);
+ MoveToEmpty(_allocation, other._allocation, _size);
}
return *this;
}
@@ -375,8 +399,12 @@ public:
template
ValueType& At(const KeyComparableType& key)
{
+ // Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
+ if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
+ Compact();
+
// Ensure to have enough memory for the next item (in case of new element insertion)
- EnsureCapacity(_elementsCount + _deletedCount + 1);
+ EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
// Find location of the item or place to insert it
FindPositionResult pos;
@@ -388,9 +416,9 @@ public:
// Insert
ASSERT(pos.FreeSlotIndex != -1);
+ _elementsCount++;
Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex];
bucket.Occupy(key);
- _elementsCount++;
return bucket.Value;
}
@@ -493,7 +521,7 @@ public:
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
{
if (i->Value)
- Delete(i->Value);
+ ::Delete(i->Value);
}
Clear();
}
@@ -509,7 +537,7 @@ public:
return;
ASSERT(capacity >= 0);
AllocationData oldAllocation;
- oldAllocation.Swap(_allocation);
+ MoveToEmpty(oldAllocation, _allocation, _size);
const int32 oldSize = _size;
const int32 oldElementsCount = _elementsCount;
_deletedCount = _elementsCount = 0;
@@ -533,13 +561,22 @@ public:
}
_size = capacity;
Bucket* oldData = oldAllocation.Get();
- if (oldElementsCount != 0 && preserveContents)
+ if (oldElementsCount != 0 && capacity != 0 && preserveContents)
{
- // TODO; move keys and values on realloc
+ FindPositionResult pos;
for (int32 i = 0; i < oldSize; i++)
{
- if (oldData[i].IsOccupied())
- Add(oldData[i].Key, MoveTemp(oldData[i].Value));
+ Bucket& oldBucket = oldData[i];
+ if (oldBucket.IsOccupied())
+ {
+ FindPosition(oldBucket.Key, pos);
+ ASSERT(pos.FreeSlotIndex != -1);
+ Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
+ Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
+ bucket->_state = Bucket::Occupied;
+ _elementsCount++;
+ }
}
}
if (oldElementsCount != 0)
@@ -558,9 +595,9 @@ public:
{
if (_size >= minCapacity)
return;
- if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
- minCapacity = DICTIONARY_DEFAULT_CAPACITY;
- const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
+ int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
+ if (capacity < DICTIONARY_DEFAULT_CAPACITY)
+ capacity = DICTIONARY_DEFAULT_CAPACITY;
SetCapacity(capacity, preserveContents);
}
@@ -570,10 +607,19 @@ public:
/// The other collection.
void Swap(Dictionary& other)
{
- ::Swap(_elementsCount, other._elementsCount);
- ::Swap(_deletedCount, other._deletedCount);
- ::Swap(_size, other._size);
- _allocation.Swap(other._allocation);
+ if IF_CONSTEXPR (AllocationType::HasSwap)
+ {
+ ::Swap(_elementsCount, other._elementsCount);
+ ::Swap(_deletedCount, other._deletedCount);
+ ::Swap(_size, other._size);
+ _allocation.Swap(other._allocation);
+ }
+ else
+ {
+ Dictionary tmp = MoveTemp(other);
+ other = *this;
+ *this = MoveTemp(tmp);
+ }
}
public:
@@ -584,24 +630,10 @@ public:
/// The value.
/// Weak reference to the stored bucket.
template
- Bucket* Add(const KeyComparableType& key, const ValueType& value)
+ FORCE_INLINE Bucket* Add(const KeyComparableType& key, const ValueType& value)
{
- // Ensure to have enough memory for the next item (in case of new element insertion)
- EnsureCapacity(_elementsCount + _deletedCount + 1);
-
- // Find location of the item or place to insert it
- FindPositionResult pos;
- FindPosition(key, pos);
-
- // Ensure key is unknown
- ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
-
- // Insert
- ASSERT(pos.FreeSlotIndex != -1);
- Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Bucket* bucket = OnAdd(key);
bucket->Occupy(key, value);
- _elementsCount++;
-
return bucket;
}
@@ -612,24 +644,10 @@ public:
/// The value.
/// Weak reference to the stored bucket.
template
- Bucket* Add(const KeyComparableType& key, ValueType&& value)
+ FORCE_INLINE Bucket* Add(const KeyComparableType& key, ValueType&& value)
{
- // Ensure to have enough memory for the next item (in case of new element insertion)
- EnsureCapacity(_elementsCount + _deletedCount + 1);
-
- // Find location of the item or place to insert it
- FindPositionResult pos;
- FindPosition(key, pos);
-
- // Ensure key is unknown
- ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
-
- // Insert
- ASSERT(pos.FreeSlotIndex != -1);
- Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Bucket* bucket = OnAdd(key);
bucket->Occupy(key, MoveTemp(value));
- _elementsCount++;
-
return bucket;
}
@@ -851,7 +869,7 @@ public:
return Iterator(this, _size);
}
-protected:
+private:
///
/// The result container of the dictionary item lookup searching.
///
@@ -911,4 +929,66 @@ protected:
result.ObjectIndex = -1;
result.FreeSlotIndex = insertPos;
}
+
+ template
+ Bucket* OnAdd(const KeyComparableType& key)
+ {
+ // Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
+ if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
+ Compact();
+
+ // Ensure to have enough memory for the next item (in case of new element insertion)
+ EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
+
+ // Find location of the item or place to insert it
+ FindPositionResult pos;
+ FindPosition(key, pos);
+
+ // Ensure key is unknown
+ ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
+
+ // Insert
+ ASSERT(pos.FreeSlotIndex != -1);
+ _elementsCount++;
+ return &_allocation.Get()[pos.FreeSlotIndex];
+ }
+
+ void Compact()
+ {
+ if (_elementsCount == 0)
+ {
+ // Fast path if it's empty
+ Bucket* data = _allocation.Get();
+ for (int32 i = 0; i < _size; i++)
+ data[i]._state = Bucket::Empty;
+ }
+ else
+ {
+ // Rebuild entire table completely
+ AllocationData oldAllocation;
+ MoveToEmpty(oldAllocation, _allocation, _size);
+ _allocation.Allocate(_size);
+ Bucket* data = _allocation.Get();
+ for (int32 i = 0; i < _size; i++)
+ data[i]._state = Bucket::Empty;
+ Bucket* oldData = oldAllocation.Get();
+ FindPositionResult pos;
+ for (int32 i = 0; i < _size; i++)
+ {
+ Bucket& oldBucket = oldData[i];
+ if (oldBucket.IsOccupied())
+ {
+ FindPosition(oldBucket.Key, pos);
+ ASSERT(pos.FreeSlotIndex != -1);
+ Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
+ Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
+ bucket->_state = Bucket::Occupied;
+ }
+ }
+ for (int32 i = 0; i < _size; i++)
+ oldData[i].Free();
+ }
+ _deletedCount = 0;
+ }
};
diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h
index 107e42e65..a683edf15 100644
--- a/Source/Engine/Core/Collections/HashSet.h
+++ b/Source/Engine/Core/Collections/HashSet.h
@@ -37,26 +37,33 @@ public:
private:
State _state;
- void Free()
+ FORCE_INLINE void Free()
{
if (_state == Occupied)
Memory::DestructItem(&Item);
_state = Empty;
}
- void Delete()
+ FORCE_INLINE void Delete()
{
_state = Deleted;
Memory::DestructItem(&Item);
}
template
- void Occupy(const ItemType& item)
+ FORCE_INLINE void Occupy(const ItemType& item)
{
Memory::ConstructItems(&Item, &item, 1);
_state = Occupied;
}
+ template
+ FORCE_INLINE void Occupy(ItemType& item)
+ {
+ Memory::MoveItems(&Item, &item, 1);
+ _state = Occupied;
+ }
+
FORCE_INLINE bool IsEmpty() const
{
return _state == Empty;
@@ -86,6 +93,31 @@ private:
int32 _size = 0;
AllocationData _allocation;
+ FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromSize)
+ {
+ if IF_CONSTEXPR (AllocationType::HasSwap)
+ to.Swap(from);
+ else
+ {
+ to.Allocate(fromSize);
+ Bucket* toData = to.Get();
+ Bucket* fromData = from.Get();
+ for (int32 i = 0; i < fromSize; i++)
+ {
+ Bucket& fromBucket = fromData[i];
+ if (fromBucket.IsOccupied())
+ {
+ Bucket& toBucket = toData[i];
+ Memory::MoveItems(&toBucket.Item, &fromBucket.Item, 1);
+ toBucket._state = Bucket::Occupied;
+ Memory::DestructItem(&fromBucket.Item);
+ fromBucket._state = Bucket::Empty;
+ }
+ }
+ from.Free();
+ }
+ }
+
public:
///
/// Initializes a new instance of the class.
@@ -108,9 +140,6 @@ public:
///
/// The other collection to move.
HashSet(HashSet&& other) noexcept
- : _elementsCount(other._elementsCount)
- , _deletedCount(other._deletedCount)
- , _size(other._size)
{
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
@@ -118,7 +147,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
- _allocation.Swap(other._allocation);
+ MoveToEmpty(_allocation, other._allocation, _size);
}
///
@@ -159,7 +188,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
- _allocation.Swap(other._allocation);
+ MoveToEmpty(_allocation, other._allocation, _size);
}
return *this;
}
@@ -169,7 +198,7 @@ public:
///
~HashSet()
{
- SetCapacity(0, false);
+ Clear();
}
public:
@@ -216,6 +245,7 @@ public:
HashSet* _collection;
int32 _index;
+ public:
Iterator(HashSet* collection, const int32 index)
: _collection(collection)
, _index(index)
@@ -228,7 +258,12 @@ public:
{
}
- public:
+ Iterator()
+ : _collection(nullptr)
+ , _index(-1)
+ {
+ }
+
Iterator(const Iterator& i)
: _collection(i._collection)
, _index(i._index)
@@ -242,6 +277,11 @@ public:
}
public:
+ FORCE_INLINE int32 Index() const
+ {
+ return _index;
+ }
+
FORCE_INLINE bool IsEnd() const
{
return _index == _collection->_size;
@@ -374,7 +414,7 @@ public:
return;
ASSERT(capacity >= 0);
AllocationData oldAllocation;
- oldAllocation.Swap(_allocation);
+ MoveToEmpty(oldAllocation, _allocation, _size);
const int32 oldSize = _size;
const int32 oldElementsCount = _elementsCount;
_deletedCount = _elementsCount = 0;
@@ -398,13 +438,21 @@ public:
}
_size = capacity;
Bucket* oldData = oldAllocation.Get();
- if (oldElementsCount != 0 && preserveContents)
+ if (oldElementsCount != 0 && capacity != 0 && preserveContents)
{
- // TODO; move keys and values on realloc
+ FindPositionResult pos;
for (int32 i = 0; i < oldSize; i++)
{
- if (oldData[i].IsOccupied())
- Add(oldData[i].Item);
+ Bucket& oldBucket = oldData[i];
+ if (oldBucket.IsOccupied())
+ {
+ FindPosition(oldBucket.Item, pos);
+ ASSERT(pos.FreeSlotIndex != -1);
+ Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
+ bucket->_state = Bucket::Occupied;
+ _elementsCount++;
+ }
}
}
if (oldElementsCount != 0)
@@ -421,14 +469,35 @@ public:
/// True if preserve collection data when changing its size, otherwise collection after resize will be empty.
void EnsureCapacity(int32 minCapacity, bool preserveContents = true)
{
- if (Capacity() >= minCapacity)
+ if (_size >= minCapacity)
return;
- if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
- minCapacity = DICTIONARY_DEFAULT_CAPACITY;
- const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
+ int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
+ if (capacity < DICTIONARY_DEFAULT_CAPACITY)
+ capacity = DICTIONARY_DEFAULT_CAPACITY;
SetCapacity(capacity, preserveContents);
}
+ ///
+ /// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange.
+ ///
+ /// The other collection.
+ void Swap(HashSet& other)
+ {
+ if IF_CONSTEXPR (AllocationType::HasSwap)
+ {
+ ::Swap(_elementsCount, other._elementsCount);
+ ::Swap(_deletedCount, other._deletedCount);
+ ::Swap(_size, other._size);
+ _allocation.Swap(other._allocation);
+ }
+ else
+ {
+ HashSet tmp = MoveTemp(other);
+ other = *this;
+ *this = MoveTemp(tmp);
+ }
+ }
+
public:
///
/// Add element to the collection.
@@ -438,24 +507,23 @@ public:
template
bool Add(const ItemType& item)
{
- // Ensure to have enough memory for the next item (in case of new element insertion)
- EnsureCapacity(_elementsCount + _deletedCount + 1);
+ Bucket* bucket = OnAdd(item);
+ if (bucket)
+ bucket->Occupy(item);
+ return bucket != nullptr;
+ }
- // Find location of the item or place to insert it
- FindPositionResult pos;
- FindPosition(item, pos);
-
- // Check if object has been already added
- if (pos.ObjectIndex != -1)
- return false;
-
- // Insert
- ASSERT(pos.FreeSlotIndex != -1);
- Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
- bucket->Occupy(item);
- _elementsCount++;
-
- return true;
+ ///
+ /// Add element to the collection.
+ ///
+ /// The element to add to the set.
+ /// True if element has been added to the collection, otherwise false if the element is already present.
+ bool Add(T&& item)
+ {
+ Bucket* bucket = OnAdd(item);
+ if (bucket)
+ bucket->Occupy(MoveTemp(item));
+ return bucket != nullptr;
}
///
@@ -593,7 +661,7 @@ public:
return Iterator(this, _size);
}
-protected:
+private:
///
/// The result container of the set item lookup searching.
///
@@ -654,4 +722,66 @@ protected:
result.ObjectIndex = -1;
result.FreeSlotIndex = insertPos;
}
+
+ template
+ Bucket* OnAdd(const ItemType& key)
+ {
+ // Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
+ if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
+ Compact();
+
+ // Ensure to have enough memory for the next item (in case of new element insertion)
+ EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
+
+ // Find location of the item or place to insert it
+ FindPositionResult pos;
+ FindPosition(key, pos);
+
+ // Check if object has been already added
+ if (pos.ObjectIndex != -1)
+ return nullptr;
+
+ // Insert
+ ASSERT(pos.FreeSlotIndex != -1);
+ _elementsCount++;
+ return &_allocation.Get()[pos.FreeSlotIndex];
+ }
+
+ void Compact()
+ {
+ if (_elementsCount == 0)
+ {
+ // Fast path if it's empty
+ Bucket* data = _allocation.Get();
+ for (int32 i = 0; i < _size; i++)
+ data[i]._state = Bucket::Empty;
+ }
+ else
+ {
+ // Rebuild entire table completely
+ AllocationData oldAllocation;
+ MoveToEmpty(oldAllocation, _allocation, _size);
+ _allocation.Allocate(_size);
+ Bucket* data = _allocation.Get();
+ for (int32 i = 0; i < _size; i++)
+ data[i]._state = Bucket::Empty;
+ Bucket* oldData = oldAllocation.Get();
+ FindPositionResult pos;
+ for (int32 i = 0; i < _size; i++)
+ {
+ Bucket& oldBucket = oldData[i];
+ if (oldBucket.IsOccupied())
+ {
+ FindPosition(oldBucket.Item, pos);
+ ASSERT(pos.FreeSlotIndex != -1);
+ Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
+ bucket->_state = Bucket::Occupied;
+ }
+ }
+ for (int32 i = 0; i < _size; i++)
+ oldData[i].Free();
+ }
+ _deletedCount = 0;
+ }
};
diff --git a/Source/Engine/Core/Compiler.h b/Source/Engine/Core/Compiler.h
index 9a33b8758..4ea246077 100644
--- a/Source/Engine/Core/Compiler.h
+++ b/Source/Engine/Core/Compiler.h
@@ -93,3 +93,10 @@
#endif
#define PACK_STRUCT(__Declaration__) PACK_BEGIN() __Declaration__ PACK_END()
+
+// C++ 17
+#if __cplusplus >= 201703L
+#define IF_CONSTEXPR constexpr
+#else
+#define IF_CONSTEXPR
+#endif
diff --git a/Source/Engine/Core/Config/PlatformSettingsBase.h b/Source/Engine/Core/Config/PlatformSettingsBase.h
index 6d7e8601e..b372e3dd5 100644
--- a/Source/Engine/Core/Config/PlatformSettingsBase.h
+++ b/Source/Engine/Core/Config/PlatformSettingsBase.h
@@ -3,7 +3,7 @@
#pragma once
#include "Engine/Core/Config/Settings.h"
-#include "Engine/Serialization/Serialization.h"
+#include "Engine/Serialization/SerializationFwd.h"
///
/// Specifies the display mode of a game window.
diff --git a/Source/Engine/Core/Delegate.h b/Source/Engine/Core/Delegate.h
index 4e081faef..f0f8fdef0 100644
--- a/Source/Engine/Core/Delegate.h
+++ b/Source/Engine/Core/Delegate.h
@@ -226,7 +226,7 @@ public:
/// Function result
FORCE_INLINE ReturnType operator()(Params... params) const
{
- ASSERT(_function);
+ ASSERT_LOW_LAYER(_function);
return _function(_callee, Forward(params)...);
}
@@ -289,8 +289,13 @@ protected:
intptr volatile _ptr = 0;
intptr volatile _size = 0;
#else
- HashSet* _functions = nullptr;
- CriticalSection* _locker = nullptr;
+ struct Data
+ {
+ HashSet Functions;
+ CriticalSection Locker;
+ };
+ // Holds pointer to Data with Functions and Locker. Thread-safe access via atomic operations.
+ intptr volatile _data = 0;
#endif
typedef void (*StubSignature)(void*, Params...);
@@ -314,15 +319,12 @@ public:
_ptr = (intptr)newBindings;
_size = newSize;
#else
- if (other._functions == nullptr)
+ Data* otherData = (Data*)Platform::AtomicRead(&_data);
+ if (otherData == nullptr)
return;
- _functions = New>(*other._functions);
- for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
- {
- if (i->Item._function && i->Item._lambda)
- i->Item.LambdaCtor();
- }
- _locker = other._locker;
+ ScopeLock lock(otherData->Locker);
+ for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i)
+ Bind(i->Item);
#endif
}
@@ -334,10 +336,8 @@ public:
other._ptr = 0;
other._size = 0;
#else
- _functions = other._functions;
- _locker = other._locker;
- other._functions = nullptr;
- other._locker = nullptr;
+ _data = other._data;
+ other._data = 0;
#endif
}
@@ -356,20 +356,11 @@ public:
Allocator::Free((void*)_ptr);
}
#else
- if (_locker != nullptr)
+ Data* data = (Data*)_data;
+ if (data)
{
- Allocator::Free(_locker);
- _locker = nullptr;
- }
- if (_functions != nullptr)
- {
- for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
- {
- if (i->Item._lambda)
- i->Item.LambdaCtor();
- }
- Allocator::Free(_functions);
- _functions = nullptr;
+ _data = 0;
+ Delete(data);
}
#endif
}
@@ -385,8 +376,13 @@ public:
for (intptr i = 0; i < size; i++)
Bind(bindings[i]);
#else
- for (auto i = other._functions->Begin(); i.IsNotEnd(); ++i)
- Bind(i->Item);
+ Data* otherData = (Data*)Platform::AtomicRead(&_data);
+ if (otherData != nullptr)
+ {
+ ScopeLock lock(otherData->Locker);
+ for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i)
+ Bind(i->Item);
+ }
#endif
}
return *this;
@@ -402,10 +398,8 @@ public:
other._ptr = 0;
other._size = 0;
#else
- _functions = other._functions;
- _locker = other._locker;
- other._functions = nullptr;
- other._locker = nullptr;
+ _data = other._data;
+ other._data = 0;
#endif
}
return *this;
@@ -507,12 +501,20 @@ public:
Allocator::Free(bindings);
}
#else
- if (_locker == nullptr)
- _locker = New();
- ScopeLock lock(*_locker);
- if (_functions == nullptr)
- _functions = New>(32);
- _functions->Add(f);
+ Data* data = (Data*)Platform::AtomicRead(&_data);
+ while (!data)
+ {
+ Data* newData = New();
+ Data* oldData = (Data*)Platform::InterlockedCompareExchange(&_data, (intptr)newData, (intptr)data);
+ if (oldData != data)
+ {
+ // Other thread already set the new data so free it and try again
+ Delete(newData);
+ }
+ data = (Data*)Platform::AtomicRead(&_data);
+ }
+ ScopeLock lock(data->Locker);
+ data->Functions.Add(f);
#endif
}
@@ -568,13 +570,22 @@ public:
}
}
#else
- if (_locker == nullptr)
- _locker = New();
- ScopeLock lock(*_locker);
- if (_functions && _functions->Contains(f))
- return;
+ Data* data = (Data*)Platform::AtomicRead(&_data);
+ if (data)
+ {
+ data->Locker.Lock();
+ if (data->Functions.Contains(f))
+ {
+ data->Locker.Unlock();
+ return;
+ }
+ }
#endif
Bind(f);
+#if !DELEGATE_USE_ATOMIC
+ if (data)
+ data->Locker.Unlock();
+#endif
}
///
@@ -583,18 +594,9 @@ public:
template
void Unbind()
{
-#if DELEGATE_USE_ATOMIC
FunctionType f;
f.template Bind();
Unbind(f);
-#else
- if (_functions == nullptr)
- return;
- FunctionType f;
- f.template Bind();
- ScopeLock lock(*_locker);
- _functions->Remove(f);
-#endif
}
///
@@ -604,18 +606,9 @@ public:
template
void Unbind(T* callee)
{
-#if DELEGATE_USE_ATOMIC
FunctionType f;
f.template Bind(callee);
Unbind(f);
-#else
- if (_functions == nullptr)
- return;
- FunctionType f;
- f.template Bind(callee);
- ScopeLock lock(*_locker);
- _functions->Remove(f);
-#endif
}
///
@@ -624,16 +617,8 @@ public:
/// The method.
void Unbind(Signature method)
{
-#if DELEGATE_USE_ATOMIC
FunctionType f(method);
Unbind(f);
-#else
- if (_functions == nullptr)
- return;
- FunctionType f(method);
- ScopeLock lock(*_locker);
- _functions->Remove(f);
-#endif
}
///
@@ -666,10 +651,11 @@ public:
Unbind(f);
}
#else
- if (_functions == nullptr)
+ Data* data = (Data*)Platform::AtomicRead(&_data);
+ if (!data)
return;
- ScopeLock lock(*_locker);
- _functions->Remove(f);
+ ScopeLock lock(data->Locker);
+ data->Functions.Remove(f);
#endif
}
@@ -692,15 +678,11 @@ public:
Platform::AtomicStore((intptr volatile*)&bindings[i]._callee, 0);
}
#else
- if (_functions == nullptr)
+ Data* data = (Data*)Platform::AtomicRead(&_data);
+ if (!data)
return;
- ScopeLock lock(*_locker);
- for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
- {
- if (i->Item._lambda)
- i->Item.LambdaDtor();
- }
- _functions->Clear();
+ ScopeLock lock(data->Locker);
+ data->Functions.Clear();
#endif
}
@@ -710,22 +692,24 @@ public:
/// The bound functions count.
int32 Count() const
{
+ int32 result = 0;
#if DELEGATE_USE_ATOMIC
- int32 count = 0;
const intptr size = Platform::AtomicRead((intptr volatile*)&_size);
FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr);
for (intptr i = 0; i < size; i++)
{
if (Platform::AtomicRead((intptr volatile*)&bindings[i]._function) != 0)
- count++;
+ result++;
}
- return count;
#else
- if (_functions == nullptr)
- return 0;
- ScopeLock lock(*_locker);
- return _functions->Count();
+ Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
+ if (data)
+ {
+ ScopeLock lock(data->Locker);
+ result = data->Functions.Count();
+ }
#endif
+ return result;
}
///
@@ -736,10 +720,14 @@ public:
#if DELEGATE_USE_ATOMIC
return (int32)Platform::AtomicRead((intptr volatile*)&_size);
#else
- if (_functions == nullptr)
- return 0;
- ScopeLock lock(*_locker);
- return _functions->Capacity();
+ int32 result = 0;
+ Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
+ if (data)
+ {
+ ScopeLock lock(data->Locker);
+ result = data->Functions.Capacity();
+ }
+ return result;
#endif
}
@@ -759,10 +747,14 @@ public:
}
return false;
#else
- if (_functions == nullptr)
- return false;
- ScopeLock lock(*_locker);
- return _functions->Count() > 0;
+ bool result = false;
+ Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
+ if (data)
+ {
+ ScopeLock lock(data->Locker);
+ result = data->Functions.Count() != 0;
+ }
+ return result;
#endif
}
@@ -791,18 +783,13 @@ public:
}
}
#else
- if (_functions == nullptr)
- return 0;
- ScopeLock lock(*_locker);
- for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
+ Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
+ if (data)
{
- if (i->Item._function != nullptr)
+ ScopeLock lock(data->Locker);
+ for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i)
{
- buffer[count]._function = (StubSignature)i->Item._function;
- buffer[count]._callee = (void*)i->Item._callee;
- buffer[count]._lambda = (typename FunctionType::Lambda*)i->Item._lambda;
- if (buffer[count]._lambda)
- buffer[count].LambdaCtor();
+ new(buffer + count) FunctionType((const FunctionType&)i->Item);
count++;
}
}
@@ -828,15 +815,15 @@ public:
++bindings;
}
#else
- if (_functions == nullptr)
+ Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
+ if (!data)
return;
- ScopeLock lock(*_locker);
- for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
+ ScopeLock lock(data->Locker);
+ for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i)
{
- auto function = (StubSignature)(i->Item._function);
- auto callee = (void*)(i->Item._callee);
- if (function != nullptr)
- function(callee, Forward(params)...);
+ const FunctionType& item = i->Item;
+ ASSERT_LOW_LAYER(item._function);
+ item._function(item._callee, Forward(params)...);
}
#endif
}
diff --git a/Source/Engine/Core/Math/Math.h b/Source/Engine/Core/Math/Math.h
index 276f64884..a3c05fce4 100644
--- a/Source/Engine/Core/Math/Math.h
+++ b/Source/Engine/Core/Math/Math.h
@@ -728,9 +728,7 @@ namespace Math
///
/// Returns value based on comparand. The main purpose of this function is to avoid branching based on floating point comparison which can be avoided via compiler intrinsics.
///
- ///
- /// Please note that this doesn't define what happens in the case of NaNs as there might be platform specific differences.
- ///
+ /// Please note that this doesn't define what happens in the case of NaNs as there might be platform specific differences.
/// Comparand the results are based on.
/// The result value if comparand >= 0.
/// The result value if comparand < 0.
@@ -891,6 +889,18 @@ namespace Math
return Lerp(a, b, alpha < 0.5f ? InterpCircularIn(0.f, 1.f, alpha * 2.f) * 0.5f : InterpCircularOut(0.f, 1.f, alpha * 2.f - 1.f) * 0.5f + 0.5f);
}
+ ///
+ /// Ping pongs the value , so that it is never larger than and never smaller than 0.
+ ///
+ ///
+ ///
+ ///
+ template
+ static FORCE_INLINE T PingPong(const T& t, T length)
+ {
+ return length - Abs(Repeat(t, length * 2.0f) - length);
+ }
+
// Rotates position about the given axis by the given angle, in radians, and returns the offset to position
Vector3 FLAXENGINE_API RotateAboutAxis(const Vector3& normalizedRotationAxis, float angle, const Vector3& positionOnAxis, const Vector3& position);
diff --git a/Source/Engine/Core/Math/Quaternion.cpp b/Source/Engine/Core/Math/Quaternion.cpp
index bdbe3037d..1ca05a9c3 100644
--- a/Source/Engine/Core/Math/Quaternion.cpp
+++ b/Source/Engine/Core/Math/Quaternion.cpp
@@ -382,9 +382,8 @@ void Quaternion::GetRotationFromTo(const Float3& from, const Float3& to, Quatern
v0.Normalize();
v1.Normalize();
- const float d = Float3::Dot(v0, v1);
-
// If dot == 1, vectors are the same
+ const float d = Float3::Dot(v0, v1);
if (d >= 1.0f)
{
result = Identity;
diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs
index ff50a98bf..34ee160f0 100644
--- a/Source/Engine/Core/Math/Quaternion.cs
+++ b/Source/Engine/Core/Math/Quaternion.cs
@@ -1077,6 +1077,115 @@ namespace FlaxEngine
}
}
+ ///
+ /// Gets the shortest arc quaternion to rotate this vector to the destination vector.
+ ///
+ /// The source vector.
+ /// The destination vector.
+ /// The result.
+ /// The fallback axis.
+ public static void GetRotationFromTo(ref Float3 from, ref Float3 to, out Quaternion result, ref Float3 fallbackAxis)
+ {
+ // Based on Stan Melax's article in Game Programming Gems
+
+ Float3 v0 = from;
+ Float3 v1 = to;
+ v0.Normalize();
+ v1.Normalize();
+
+ // If dot == 1, vectors are the same
+ float d = Float3.Dot(ref v0, ref v1);
+ if (d >= 1.0f)
+ {
+ result = Identity;
+ return;
+ }
+
+ if (d < 1e-6f - 1.0f)
+ {
+ if (fallbackAxis != Float3.Zero)
+ {
+ // Rotate 180 degrees about the fallback axis
+ RotationAxis(ref fallbackAxis, Mathf.Pi, out result);
+ }
+ else
+ {
+ // Generate an axis
+ Float3 axis = Float3.Cross(Float3.UnitX, from);
+ if (axis.LengthSquared < Mathf.Epsilon) // Pick another if colinear
+ axis = Float3.Cross(Float3.UnitY, from);
+ axis.Normalize();
+ RotationAxis(ref axis, Mathf.Pi, out result);
+ }
+ }
+ else
+ {
+ float s = Mathf.Sqrt((1 + d) * 2);
+ float invS = 1 / s;
+ Float3.Cross(ref v0, ref v1, out var c);
+ result.X = c.X * invS;
+ result.Y = c.Y * invS;
+ result.Z = c.Z * invS;
+ result.W = s * 0.5f;
+ result.Normalize();
+ }
+ }
+
+ ///
+ /// Gets the shortest arc quaternion to rotate this vector to the destination vector.
+ ///
+ /// The source vector.
+ /// The destination vector.
+ /// The fallback axis.
+ /// The rotation.
+ public static Quaternion GetRotationFromTo(Float3 from, Float3 to, Float3 fallbackAxis)
+ {
+ GetRotationFromTo(ref from, ref to, out var result, ref fallbackAxis);
+ return result;
+ }
+
+ ///
+ /// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
+ ///
+ /// The source vector.
+ /// The destination vector.
+ /// The result.
+ public static void FindBetween(ref Float3 from, ref Float3 to, out Quaternion result)
+ {
+ // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final
+ float normFromNormTo = Mathf.Sqrt(from.LengthSquared * to.LengthSquared);
+ if (normFromNormTo < Mathf.Epsilon)
+ {
+ result = Identity;
+ return;
+ }
+ float w = normFromNormTo + Float3.Dot(from, to);
+ if (w < 1.0-6f * normFromNormTo)
+ {
+ result = Mathf.Abs(from.X) > Mathf.Abs(from.Z)
+ ? new Quaternion(-from.Y, from.X, 0.0f, 0.0f)
+ : new Quaternion(0.0f, -from.Z, from.Y, 0.0f);
+ }
+ else
+ {
+ Float3 cross = Float3.Cross(from, to);
+ result = new Quaternion(cross.X, cross.Y, cross.Z, w);
+ }
+ result.Normalize();
+ }
+
+ ///
+ /// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
+ ///
+ /// The source vector.
+ /// The destination vector.
+ /// The rotation.
+ public static Quaternion FindBetween(Float3 from, Float3 to)
+ {
+ FindBetween(ref from, ref to, out var result);
+ return result;
+ }
+
///
/// Creates a left-handed spherical billboard that rotates around a specified object position.
///
diff --git a/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs b/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs
index afd34bbfd..65377acaa 100644
--- a/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs
@@ -13,9 +13,7 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
- {
return true;
- }
return base.CanConvertFrom(context, sourceType);
}
@@ -23,9 +21,7 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
- {
return false;
- }
return base.CanConvertTo(context, destinationType);
}
diff --git a/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs
index 4eebbfce4..e0670df05 100644
--- a/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Double2Converter : TypeConverter
+ internal class Double2Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Double2(double.Parse(v[0], culture), double.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs
index 420e0016c..a66892ecb 100644
--- a/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Double3Converter : TypeConverter
+ internal class Double3Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Double3(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs
index fc1d9a7fe..d085217ef 100644
--- a/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Double4Converter : TypeConverter
+ internal class Double4Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Double4(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture), double.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs
index a41a0f4d5..4b2ffadf5 100644
--- a/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Float2Converter : TypeConverter
+ internal class Float2Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Float2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs
index aded4117e..3739c44ef 100644
--- a/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Float3Converter : TypeConverter
+ internal class Float3Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Float3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs
index 58c76ac65..620f2c838 100644
--- a/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs
@@ -7,15 +7,13 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Float4Converter : TypeConverter
+ internal class VectorConverter : TypeConverter
{
///
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
- {
return true;
- }
return base.CanConvertFrom(context, sourceType);
}
@@ -23,18 +21,32 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
- {
return false;
- }
return base.CanConvertTo(context, destinationType);
}
+ internal static string[] GetParts(string str)
+ {
+ string[] v = str.Split(',');
+ if (v.Length == 1)
+ {
+ // When converting from ToString()
+ v = str.Split(' ');
+ for (int i = 0; i < v.Length; i++)
+ v[i] = v[i].Substring(v[i].IndexOf(':') + 1);
+ }
+ return v;
+ }
+ }
+
+ internal class Float4Converter : VectorConverter
+ {
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Float4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs
index c4989c085..f528aa46b 100644
--- a/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Int2Converter : TypeConverter
+ internal class Int2Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Int2(int.Parse(v[0], culture), int.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs
index fe01f91fd..520f806d0 100644
--- a/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Int3Converter : TypeConverter
+ internal class Int3Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Int3(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs
index 2ce0fc202..e9a27dfda 100644
--- a/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Int4Converter : TypeConverter
+ internal class Int4Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Int4(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture), int.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs b/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs
index 23bb901be..5d9aa206b 100644
--- a/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class QuaternionConverter : TypeConverter
+ internal class QuaternionConverter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Quaternion(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs
index 96d6beadc..acb5b5817 100644
--- a/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Vector2Converter : TypeConverter
+ internal class Vector2Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Vector2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs
index 23ee4df11..66ec831f0 100644
--- a/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Vector3Converter : TypeConverter
+ internal class Vector3Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Vector3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs
index c3b4d074b..f4781f45b 100644
--- a/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Vector4Converter : TypeConverter
+ internal class Vector4Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Vector4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/Vector2.h b/Source/Engine/Core/Math/Vector2.h
index 83deb009a..cea03ec10 100644
--- a/Source/Engine/Core/Math/Vector2.h
+++ b/Source/Engine/Core/Math/Vector2.h
@@ -646,6 +646,12 @@ inline Vector2Base operator/(typename TOtherFloat::Type a, const Vector2Ba
return Vector2Base(a) / b;
}
+template
+inline uint32 GetHash(const Vector2Base& key)
+{
+ return (*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y;
+}
+
namespace Math
{
template
diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h
index c0ebdf01d..01b55e9bd 100644
--- a/Source/Engine/Core/Math/Vector3.h
+++ b/Source/Engine/Core/Math/Vector3.h
@@ -977,6 +977,12 @@ inline Vector3Base operator/(typename TOtherFloat::Type a, const Vector3Ba
return Vector3Base(a) / b;
}
+template
+inline uint32 GetHash(const Vector3Base& key)
+{
+ return (((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z;
+}
+
namespace Math
{
template
diff --git a/Source/Engine/Core/Math/Vector4.h b/Source/Engine/Core/Math/Vector4.h
index 5c7b24c4a..1cc6d4db8 100644
--- a/Source/Engine/Core/Math/Vector4.h
+++ b/Source/Engine/Core/Math/Vector4.h
@@ -552,6 +552,12 @@ inline Vector4Base operator/(typename TOtherFloat::Type a, const Vector4Ba
return Vector4Base(a) / b;
}
+template
+inline uint32 GetHash(const Vector4Base& key)
+{
+ return (((((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z) * 397) ^*(uint32*)&key.W;
+}
+
namespace Math
{
template
diff --git a/Source/Engine/Core/Memory/Allocation.h b/Source/Engine/Core/Memory/Allocation.h
index 89d2f2003..fb6b4555b 100644
--- a/Source/Engine/Core/Memory/Allocation.h
+++ b/Source/Engine/Core/Memory/Allocation.h
@@ -12,6 +12,8 @@ template
class FixedAllocation
{
public:
+ enum { HasSwap = false };
+
template
class Data
{
@@ -43,14 +45,14 @@ public:
return Capacity;
}
- FORCE_INLINE void Allocate(uint64 capacity)
+ FORCE_INLINE void Allocate(int32 capacity)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(capacity <= Capacity);
#endif
}
- FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
+ FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(capacity <= Capacity);
@@ -61,12 +63,9 @@ public:
{
}
- FORCE_INLINE void Swap(Data& other)
+ void Swap(Data& other)
{
- byte tmp[Capacity * sizeof(T)];
- Platform::MemoryCopy(tmp, _data, Capacity * sizeof(T));
- Platform::MemoryCopy(_data, other._data, Capacity * sizeof(T));
- Platform::MemoryCopy(other._data, tmp, Capacity * sizeof(T));
+ // Not supported
}
};
};
@@ -77,6 +76,8 @@ public:
class HeapAllocation
{
public:
+ enum { HasSwap = true };
+
template
class Data
{
@@ -120,12 +121,15 @@ public:
capacity |= capacity >> 4;
capacity |= capacity >> 8;
capacity |= capacity >> 16;
- capacity = (capacity + 1) * 2;
+ uint64 capacity64 = (uint64)(capacity + 1) * 2;
+ if (capacity64 > MAX_int32)
+ capacity64 = MAX_int32;
+ capacity = (int32)capacity64;
}
return capacity;
}
- FORCE_INLINE void Allocate(uint64 capacity)
+ FORCE_INLINE void Allocate(int32 capacity)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(!_data);
@@ -137,7 +141,7 @@ public:
#endif
}
- FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
+ FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
T* newData = capacity != 0 ? (T*)Allocator::Allocate(capacity * sizeof(T)) : nullptr;
#if !BUILD_RELEASE
@@ -176,6 +180,8 @@ template
class InlinedAllocation
{
public:
+ enum { HasSwap = false };
+
template
class Data
{
@@ -210,7 +216,7 @@ public:
return minCapacity <= Capacity ? Capacity : _other.CalculateCapacityGrow(capacity, minCapacity);
}
- FORCE_INLINE void Allocate(uint64 capacity)
+ FORCE_INLINE void Allocate(int32 capacity)
{
if (capacity > Capacity)
{
@@ -219,7 +225,7 @@ public:
}
}
- FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
+ FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
// Check if the new allocation will fit into inlined storage
if (capacity <= Capacity)
@@ -264,14 +270,9 @@ public:
}
}
- FORCE_INLINE void Swap(Data& other)
+ void Swap(Data& other)
{
- byte tmp[Capacity * sizeof(T)];
- Platform::MemoryCopy(tmp, _data, Capacity * sizeof(T));
- Platform::MemoryCopy(_data, other._data, Capacity * sizeof(T));
- Platform::MemoryCopy(other._data, tmp, Capacity * sizeof(T));
- ::Swap(_useOther, other._useOther);
- _other.Swap(other._other);
+ // Not supported
}
};
};
diff --git a/Source/Engine/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp
index a020f7844..a185ce8b3 100644
--- a/Source/Engine/Core/ObjectsRemovalService.cpp
+++ b/Source/Engine/Core/ObjectsRemovalService.cpp
@@ -5,6 +5,7 @@
#include "Collections/Dictionary.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h"
+#include "Engine/Platform/CriticalSection.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ScriptingObject.h"
diff --git a/Source/Engine/Core/Types/StringBuilder.h b/Source/Engine/Core/Types/StringBuilder.h
index 051554a23..068f245d0 100644
--- a/Source/Engine/Core/Types/StringBuilder.h
+++ b/Source/Engine/Core/Types/StringBuilder.h
@@ -3,6 +3,7 @@
#pragma once
#include "String.h"
+#include "StringView.h"
#include "Engine/Core/Collections/Array.h"
///
@@ -138,6 +139,11 @@ public:
_data.Add(*str, str.Length());
return *this;
}
+ StringBuilder& Append(const StringView& str)
+ {
+ _data.Add(*str, str.Length());
+ return *this;
+ }
// Append int to the string
// @param val Value to append
diff --git a/Source/Engine/Core/Types/StringView.h b/Source/Engine/Core/Types/StringView.h
index 27c63c999..3e2bbd5f3 100644
--- a/Source/Engine/Core/Types/StringView.h
+++ b/Source/Engine/Core/Types/StringView.h
@@ -327,6 +327,18 @@ public:
bool operator!=(const String& other) const;
public:
+ using StringViewBase::StartsWith;
+ FORCE_INLINE bool StartsWith(const StringView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
+ {
+ return StringViewBase::StartsWith(prefix, searchCase);
+ }
+
+ using StringViewBase::EndsWith;
+ FORCE_INLINE bool EndsWith(const StringView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
+ {
+ return StringViewBase::EndsWith(suffix, searchCase);
+ }
+
///
/// Gets the left most given number of characters.
///
@@ -511,6 +523,18 @@ public:
bool operator!=(const StringAnsi& other) const;
public:
+ using StringViewBase::StartsWith;
+ FORCE_INLINE bool StartsWith(const StringAnsiView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
+ {
+ return StringViewBase::StartsWith(prefix, searchCase);
+ }
+
+ using StringViewBase::EndsWith;
+ FORCE_INLINE bool EndsWith(const StringAnsiView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
+ {
+ return StringViewBase::EndsWith(suffix, searchCase);
+ }
+
///
/// Retrieves substring created from characters starting from startIndex to the String end.
///
diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp
index df044b299..952e648e6 100644
--- a/Source/Engine/Core/Types/Variant.cpp
+++ b/Source/Engine/Core/Types/Variant.cpp
@@ -2821,7 +2821,10 @@ void Variant::Inline()
type = VariantType::Types::Vector4;
}
if (type != VariantType::Null)
+ {
+ ASSERT(sizeof(data) >= AsBlob.Length);
Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
+ }
}
if (type != VariantType::Null)
{
@@ -2912,6 +2915,60 @@ void Variant::Inline()
}
}
+void Variant::InvertInline()
+{
+ byte data[sizeof(Matrix)];
+ switch (Type.Type)
+ {
+ case VariantType::Bool:
+ case VariantType::Int:
+ case VariantType::Uint:
+ case VariantType::Int64:
+ case VariantType::Uint64:
+ case VariantType::Float:
+ case VariantType::Double:
+ case VariantType::Pointer:
+ case VariantType::String:
+ case VariantType::Float2:
+ case VariantType::Float3:
+ case VariantType::Float4:
+ case VariantType::Color:
+#if !USE_LARGE_WORLDS
+ case VariantType::BoundingSphere:
+ case VariantType::BoundingBox:
+ case VariantType::Ray:
+#endif
+ case VariantType::Guid:
+ case VariantType::Quaternion:
+ case VariantType::Rectangle:
+ case VariantType::Int2:
+ case VariantType::Int3:
+ case VariantType::Int4:
+ case VariantType::Int16:
+ case VariantType::Uint16:
+ case VariantType::Double2:
+ case VariantType::Double3:
+ case VariantType::Double4:
+ static_assert(sizeof(data) >= sizeof(AsData), "Invalid memory size.");
+ Platform::MemoryCopy(data, AsData, sizeof(AsData));
+ break;
+#if USE_LARGE_WORLDS
+ case VariantType::BoundingSphere:
+ case VariantType::BoundingBox:
+ case VariantType::Ray:
+#endif
+ case VariantType::Transform:
+ case VariantType::Matrix:
+ ASSERT(sizeof(data) >= AsBlob.Length);
+ Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
+ break;
+ default:
+ return; // Not used
+ }
+ SetType(VariantType(VariantType::Structure, InBuiltTypesTypeNames[Type.Type]));
+ CopyStructure(data);
+}
+
Variant Variant::NewValue(const StringAnsiView& typeName)
{
Variant v;
diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h
index 8cc1e133b..adb49249f 100644
--- a/Source/Engine/Core/Types/Variant.h
+++ b/Source/Engine/Core/Types/Variant.h
@@ -372,6 +372,9 @@ public:
// Inlines potential value type into in-built format (eg. Vector3 stored as Structure, or String stored as ManagedObject).
void Inline();
+ // Inverts the inlined value from in-built format into generic storage (eg. Float3 from inlined format into Structure).
+ void InvertInline();
+
// Allocates the Variant of the specific type (eg. structure or object or value).
static Variant NewValue(const StringAnsiView& typeName);
diff --git a/Source/Engine/Core/Types/Version.cpp b/Source/Engine/Core/Types/Version.cpp
index 4a11a0af6..c0410d1cb 100644
--- a/Source/Engine/Core/Types/Version.cpp
+++ b/Source/Engine/Core/Types/Version.cpp
@@ -7,15 +7,15 @@ Version::Version(int32 major, int32 minor, int32 build, int32 revision)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
- _build = Math::Max(build, 0);
- _revision = Math::Max(revision, 0);
+ _build = Math::Max(build, -1);
+ _revision = Math::Max(revision, -1);
}
Version::Version(int32 major, int32 minor, int32 build)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
- _build = Math::Max(build, 0);
+ _build = Math::Max(build, -1);
_revision = -1;
}
diff --git a/Source/Engine/Core/Utilities.h b/Source/Engine/Core/Utilities.h
index 36339baf5..737e423b2 100644
--- a/Source/Engine/Core/Utilities.h
+++ b/Source/Engine/Core/Utilities.h
@@ -51,10 +51,14 @@ namespace Utilities
int32 i = 0;
double dblSUnits = static_cast(units);
for (; static_cast(units / static_cast(divider)) > 0; i++, units /= divider)
- dblSUnits = units / static_cast(divider);
+ dblSUnits = (double)units / (double)divider;
if (i >= sizes.Length())
i = 0;
- return String::Format(TEXT("{0} {1}"), RoundTo2DecimalPlaces(dblSUnits), sizes[i]);
+ String text = String::Format(TEXT("{}"), RoundTo2DecimalPlaces(dblSUnits));
+ const int32 dot = text.FindLast('.');
+ if (dot != -1)
+ text = text.Left(dot + 3);
+ return String::Format(TEXT("{0} {1}"), text, sizes[i]);
}
// Converts size of the file (in bytes) to the best fitting string
diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp
index 48fa31c91..059ebbd5d 100644
--- a/Source/Engine/Debug/DebugDraw.cpp
+++ b/Source/Engine/Debug/DebugDraw.cpp
@@ -696,12 +696,15 @@ void* DebugDraw::AllocateContext()
void DebugDraw::FreeContext(void* context)
{
+ ASSERT(context);
Memory::DestructItem((DebugDrawContext*)context);
Allocator::Free(context);
}
void DebugDraw::UpdateContext(void* context, float deltaTime)
{
+ if (!context)
+ context = &GlobalContext;
((DebugDrawContext*)context)->DebugDrawDefault.Update(deltaTime);
((DebugDrawContext*)context)->DebugDrawDepthTest.Update(deltaTime);
}
diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp
index 1a57c4f10..259649062 100644
--- a/Source/Engine/Engine/Engine.cpp
+++ b/Source/Engine/Engine/Engine.cpp
@@ -20,7 +20,6 @@
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Threading/ThreadRegistry.h"
#include "Engine/Graphics/GPUDevice.h"
-#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/JsonAsset.h"
@@ -327,14 +326,6 @@ void Engine::OnUpdate()
// Update services
EngineService::OnUpdate();
-
-#ifdef USE_NETCORE
- // Force GC to run in background periodically to avoid large blocking collections causing hitches
- if (Time::Update.TicksCount % 60 == 0)
- {
- MCore::GC::Collect(MCore::GC::MaxGeneration(), MGCCollectionMode::Forced, false, false);
- }
-#endif
}
void Engine::OnLateUpdate()
@@ -531,7 +522,13 @@ void EngineImpl::InitLog()
LOG(Info, "Compiled for Dev Environment");
#endif
LOG(Info, "Version " FLAXENGINE_VERSION_TEXT);
- LOG(Info, "Compiled: {0} {1}", TEXT(__DATE__), TEXT(__TIME__));
+ const Char* cpp = TEXT("?");
+ if (__cplusplus == 202101L) cpp = TEXT("C++23");
+ else if (__cplusplus == 202002L) cpp = TEXT("C++20");
+ else if (__cplusplus == 201703L) cpp = TEXT("C++17");
+ else if (__cplusplus == 201402L) cpp = TEXT("C++14");
+ else if (__cplusplus == 201103L) cpp = TEXT("C++11");
+ LOG(Info, "Compiled: {0} {1} {2}", TEXT(__DATE__), TEXT(__TIME__), cpp);
#ifdef _MSC_VER
const String mcsVer = StringUtils::ToString(_MSC_FULL_VER);
LOG(Info, "Compiled with Visual C++ {0}.{1}.{2}.{3:0^2d}", mcsVer.Substring(0, 2), mcsVer.Substring(2, 2), mcsVer.Substring(4, 5), _MSC_BUILD);
diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
index 0391974b6..e99276a4e 100644
--- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs
+++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
@@ -1022,6 +1022,8 @@ namespace FlaxEngine.Interop
pair.Value.Free();
classAttributesCacheCollectible.Clear();
+ FlaxEngine.Json.JsonSerializer.ResetCache();
+
// Unload the ALC
bool unloading = true;
scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; };
@@ -1269,6 +1271,9 @@ namespace FlaxEngine.Interop
case Type _ when type == typeof(IntPtr):
monoType = MTypes.Ptr;
break;
+ case Type _ when type.IsPointer:
+ monoType = MTypes.Ptr;
+ break;
case Type _ when type.IsEnum:
monoType = MTypes.Enum;
break;
diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs
index 2f74e421c..fc04d9668 100644
--- a/Source/Engine/Engine/NativeInterop.cs
+++ b/Source/Engine/Engine/NativeInterop.cs
@@ -278,44 +278,70 @@ namespace FlaxEngine.Interop
if (typeCache.TryGetValue(typeName, out Type type))
return type;
- type = Type.GetType(typeName, ResolveAssemblyByName, null);
+ type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null)
- {
- foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
- {
- type = assembly.GetType(typeName);
- if (type != null)
- break;
- }
- }
+ type = ResolveSlow(typeName);
if (type == null)
{
- string oldTypeName = typeName;
+ string fullTypeName = typeName;
typeName = typeName.Substring(0, typeName.IndexOf(','));
- type = Type.GetType(typeName, ResolveAssemblyByName, null);
+ type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null)
- {
- foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
- {
- type = assembly.GetType(typeName);
- if (type != null)
- break;
- }
- }
- typeName = oldTypeName;
+ type = ResolveSlow(typeName);
+
+ typeName = fullTypeName;
}
typeCache.Add(typeName, type);
return type;
+
+ static Type ResolveSlow(string typeName)
+ {
+ foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
+ {
+ var type = assembly.GetType(typeName);
+ if (type != null)
+ return type;
+ }
+ return null;
+ }
+
+ static Assembly ResolveAssembly(AssemblyName name) => ResolveScriptingAssemblyByName(name, allowPartial: false);
}
- private static Assembly ResolveAssemblyByName(AssemblyName assemblyName)
+ /// Find among the scripting assemblies.
+ /// The name to find
+ /// If true, partial names should be allowed to be resolved.
+ /// The resolved assembly, or null if none could be found.
+ internal static Assembly ResolveScriptingAssemblyByName(AssemblyName assemblyName, bool allowPartial = false)
{
- foreach (Assembly assembly in scriptingAssemblyLoadContext.Assemblies)
- if (assembly.GetName() == assemblyName)
+ var lc = scriptingAssemblyLoadContext;
+
+ if (lc is null)
+ return null;
+
+ foreach (Assembly assembly in lc.Assemblies)
+ {
+ var curName = assembly.GetName();
+
+ if (curName == assemblyName)
return assembly;
+ }
+
+ if (allowPartial) // Check partial names if full name isn't found
+ {
+ string partialName = assemblyName.Name;
+
+ foreach (Assembly assembly in lc.Assemblies)
+ {
+ var curName = assembly.GetName();
+
+ if (curName.Name == partialName)
+ return assembly;
+ }
+ }
return null;
}
@@ -1109,7 +1135,7 @@ namespace FlaxEngine.Interop
marshallers[i](fields[i], offsets[i], ref managedValue, fieldPtr, out int fieldSize);
fieldPtr += fieldSize;
}
- Assert.IsTrue((fieldPtr - nativePtr) <= Unsafe.SizeOf());
+ //Assert.IsTrue((fieldPtr - nativePtr) <= GetTypeSize(typeof(T)));
}
internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef)
@@ -1156,7 +1182,7 @@ namespace FlaxEngine.Interop
marshallers[i](fields[i], offsets[i], ref managedValue, nativePtr, out int fieldSize);
nativePtr += fieldSize;
}
- Assert.IsTrue((nativePtr - fieldPtr) <= Unsafe.SizeOf());
+ //Assert.IsTrue((nativePtr - fieldPtr) <= GetTypeSize(typeof(T)));
}
internal static void ToNative(ref T managedValue, IntPtr nativePtr)
@@ -1302,7 +1328,8 @@ namespace FlaxEngine.Interop
#if !USE_AOT
internal bool TryGetDelegate(out Invoker.MarshalAndInvokeDelegate outDeleg, out object outDelegInvoke)
{
- if (invokeDelegate == null)
+ // Skip using in-built delegate for value types (eg. Transform) to properly handle instance value passing to method
+ if (invokeDelegate == null && !method.DeclaringType.IsValueType)
{
List methodTypes = new List();
if (!method.IsStatic)
@@ -1553,7 +1580,7 @@ namespace FlaxEngine.Interop
private static IntPtr PinValue(T value) where T : struct
{
// Store the converted value in unmanaged memory so it will not be relocated by the garbage collector.
- int size = Unsafe.SizeOf();
+ int size = GetTypeSize(typeof(T));
uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length;
ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index];
if (alloc.size < size)
diff --git a/Source/Engine/Foliage/FoliageType.h b/Source/Engine/Foliage/FoliageType.h
index 0bc556fc9..4251b6924 100644
--- a/Source/Engine/Foliage/FoliageType.h
+++ b/Source/Engine/Foliage/FoliageType.h
@@ -46,7 +46,7 @@ API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FoliageType : public ScriptingOb
DECLARE_SCRIPTING_TYPE_NO_SPAWN(FoliageType);
friend Foliage;
private:
- int8 _isReady : 1;
+ uint8 _isReady : 1;
public:
///
diff --git a/Source/Engine/Graphics/Async/Tasks/GPUCopyResourceTask.h b/Source/Engine/Graphics/Async/Tasks/GPUCopyResourceTask.h
index 86c723530..f57327fa4 100644
--- a/Source/Engine/Graphics/Async/Tasks/GPUCopyResourceTask.h
+++ b/Source/Engine/Graphics/Async/Tasks/GPUCopyResourceTask.h
@@ -26,12 +26,12 @@ public:
, _srcResource(src)
, _dstResource(dst)
{
- _srcResource.OnUnload.Bind(this);
- _dstResource.OnUnload.Bind(this);
+ _srcResource.Released.Bind(this);
+ _dstResource.Released.Bind(this);
}
private:
- void OnResourceUnload(GPUResourceReference* ref)
+ void OnResourceReleased()
{
Cancel();
}
@@ -47,14 +47,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
- if (_srcResource.IsMissing() || _dstResource.IsMissing())
+ if (!_srcResource || !_dstResource)
return Result::MissingResources;
-
context->GPU->CopyResource(_dstResource, _srcResource);
-
return Result::Ok;
}
-
void OnEnd() override
{
_srcResource.Unlink();
diff --git a/Source/Engine/Graphics/Async/Tasks/GPUCopySubresourceTask.h b/Source/Engine/Graphics/Async/Tasks/GPUCopySubresourceTask.h
index ab8f1ffad..193eb965d 100644
--- a/Source/Engine/Graphics/Async/Tasks/GPUCopySubresourceTask.h
+++ b/Source/Engine/Graphics/Async/Tasks/GPUCopySubresourceTask.h
@@ -31,12 +31,12 @@ public:
, _srcSubresource(srcSubresource)
, _dstSubresource(dstSubresource)
{
- _srcResource.OnUnload.Bind(this);
- _dstResource.OnUnload.Bind(this);
+ _srcResource.Released.Bind(this);
+ _dstResource.Released.Bind(this);
}
private:
- void OnResourceUnload(GPUResourceReference* ref)
+ void OnResourceReleased()
{
Cancel();
}
@@ -52,14 +52,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
- if (_srcResource.IsMissing() || _dstResource.IsMissing())
+ if (!_srcResource || !_dstResource)
return Result::MissingResources;
-
context->GPU->CopySubresource(_dstResource, _dstSubresource, _srcResource, _srcSubresource);
-
return Result::Ok;
}
-
void OnEnd() override
{
_srcResource.Unlink();
diff --git a/Source/Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h b/Source/Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h
index d2f20449c..3d38ce58b 100644
--- a/Source/Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h
+++ b/Source/Engine/Graphics/Async/Tasks/GPUUploadBufferTask.h
@@ -31,7 +31,7 @@ public:
, _buffer(buffer)
, _offset(offset)
{
- _buffer.OnUnload.Bind(this);
+ _buffer.Released.Bind(this);
if (copyData)
_data.Copy(data);
@@ -40,7 +40,7 @@ public:
}
private:
- void OnResourceUnload(BufferReference* ref)
+ void OnResourceReleased()
{
Cancel();
}
@@ -56,14 +56,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
- if (_buffer.IsMissing())
+ if (!_buffer)
return Result::MissingResources;
-
context->GPU->UpdateBuffer(_buffer, _data.Get(), _data.Length(), _offset);
-
return Result::Ok;
}
-
void OnEnd() override
{
_buffer.Unlink();
diff --git a/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h b/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h
index 6e9cca7fd..2aff3511b 100644
--- a/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h
+++ b/Source/Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h
@@ -35,7 +35,7 @@ public:
, _rowPitch(rowPitch)
, _slicePitch(slicePitch)
{
- _texture.OnUnload.Bind(this);
+ _texture.Released.Bind(this);
if (copyData)
_data.Copy(data);
@@ -44,7 +44,7 @@ public:
}
private:
- void OnResourceUnload(GPUTextureReference* ref)
+ void OnResourceReleased()
{
Cancel();
}
diff --git a/Source/Engine/Graphics/Enums.h b/Source/Engine/Graphics/Enums.h
index 4d1422f96..bbf9c0de8 100644
--- a/Source/Engine/Graphics/Enums.h
+++ b/Source/Engine/Graphics/Enums.h
@@ -591,37 +591,37 @@ API_ENUM() enum class Quality : byte
API_ENUM() enum class MaterialPostFxLocation : byte
{
///
- /// The 'after' post processing pass using LDR input frame.
+ /// Render the material after the post processing pass using *LDR* input frame.
///
AfterPostProcessingPass = 0,
///
- /// The 'before' post processing pass using HDR input frame.
+ /// Render the material before the post processing pass using *HDR* input frame.
///
BeforePostProcessingPass = 1,
///
- /// The 'before' forward pass but after GBuffer with HDR input frame.
+ /// Render the material before the forward pass but after *GBuffer* with *HDR* input frame.
///
BeforeForwardPass = 2,
///
- /// The 'after' custom post effects.
+ /// Render the material after custom post effects (scripted).
///
AfterCustomPostEffects = 3,
///
- /// The 'before' Reflections pass. After the Light pass. Can be used to implement a custom light types that accumulate lighting to the light buffer.
+ /// Render the material before the reflections pass but after the lighting pass using *HDR* input frame. It can be used to implement a custom light types that accumulate lighting to the light buffer.
///
BeforeReflectionsPass = 4,
///
- /// The 'after' AA filter pass. Rendering is done to the output backbuffer.
+ /// Render the material after anti-aliasing into the output backbuffer.
///
AfterAntiAliasingPass = 5,
///
- /// The 'after' forward pass but before any post processing.
+ /// Render the material after the forward pass but before any post processing.
///
AfterForwardPass = 6,
diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp
index 34d8e7661..2a54c7c4d 100644
--- a/Source/Engine/Graphics/GPUDevice.cpp
+++ b/Source/Engine/Graphics/GPUDevice.cpp
@@ -3,6 +3,7 @@
#include "GPUDevice.h"
#include "RenderTargetPool.h"
#include "GPUPipelineState.h"
+#include "GPUResourceProperty.h"
#include "GPUSwapChain.h"
#include "RenderTask.h"
#include "RenderTools.h"
@@ -25,6 +26,39 @@
#include "Engine/Renderer/RenderList.h"
#include "Engine/Scripting/Enums.h"
+GPUResourcePropertyBase::~GPUResourcePropertyBase()
+{
+ const auto e = _resource;
+ if (e)
+ {
+ _resource = nullptr;
+ e->Releasing.Unbind(this);
+ }
+}
+
+void GPUResourcePropertyBase::OnSet(GPUResource* resource)
+{
+ auto e = _resource;
+ if (e != resource)
+ {
+ if (e)
+ e->Releasing.Unbind(this);
+ _resource = e = resource;
+ if (e)
+ e->Releasing.Bind(this);
+ }
+}
+
+void GPUResourcePropertyBase::OnReleased()
+{
+ auto e = _resource;
+ if (e)
+ {
+ _resource = nullptr;
+ e->Releasing.Unbind(this);
+ }
+}
+
GPUPipelineState* GPUPipelineState::Spawn(const SpawnParams& params)
{
return GPUDevice::Instance->CreatePipelineState();
@@ -313,6 +347,8 @@ bool GPUDevice::Init()
_res->TasksManager.SetExecutor(CreateTasksExecutor());
LOG(Info, "Total graphics memory: {0}", Utilities::BytesToText(TotalGraphicsMemory));
+ if (!Limits.HasCompute)
+ LOG(Warning, "Compute Shaders are not supported");
return false;
}
diff --git a/Source/Engine/Graphics/GPUResourceProperty.h b/Source/Engine/Graphics/GPUResourceProperty.h
index b3c56007d..0b5d73c40 100644
--- a/Source/Engine/Graphics/GPUResourceProperty.h
+++ b/Source/Engine/Graphics/GPUResourceProperty.h
@@ -8,28 +8,39 @@
///
/// GPU Resource container utility object.
///
-template
-class GPUResourceProperty
+class FLAXENGINE_API GPUResourcePropertyBase
{
-private:
- T* _resource;
+protected:
+ GPUResource* _resource = nullptr;
-private:
- // Disable copy actions
- GPUResourceProperty(const GPUResourceProperty& other) = delete;
+public:
+ NON_COPYABLE(GPUResourcePropertyBase);
+
+ GPUResourcePropertyBase() = default;
+ ~GPUResourcePropertyBase();
public:
///
- /// Action fired when resource gets unloaded (reference gets cleared bu async tasks should stop execution).
+ /// Action fired when resource gets released (reference gets cleared bu async tasks should stop execution).
///
- Delegate OnUnload;
+ Action Released;
+protected:
+ void OnSet(GPUResource* resource);
+ void OnReleased();
+};
+
+///
+/// GPU Resource container utility object.
+///
+template
+class GPUResourceProperty : public GPUResourcePropertyBase
+{
public:
///
/// Initializes a new instance of the class.
///
GPUResourceProperty()
- : _resource(nullptr)
{
}
@@ -38,9 +49,37 @@ public:
///
/// The resource.
GPUResourceProperty(T* resource)
- : _resource(nullptr)
{
- Set(resource);
+ OnSet(resource);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The other value.
+ GPUResourceProperty(const GPUResourceProperty& other)
+ {
+ OnSet(other.Get());
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The other value.
+ GPUResourceProperty(GPUResourceProperty&& other)
+ {
+ OnSet(other.Get());
+ other.OnSet(nullptr);
+ }
+
+ GPUResourceProperty& operator=(GPUResourceProperty&& other)
+ {
+ if (&other != this)
+ {
+ OnSet(other._resource);
+ other.OnSet(nullptr);
+ }
+ return *this;
}
///
@@ -48,13 +87,6 @@ public:
///
~GPUResourceProperty()
{
- // Check if object has been binded
- if (_resource)
- {
- // Unlink
- _resource->Releasing.template Unbind(this);
- _resource = nullptr;
- }
}
public:
@@ -63,43 +95,34 @@ public:
return Get() == other;
}
- FORCE_INLINE bool operator==(GPUResourceProperty& other) const
+ FORCE_INLINE bool operator==(const GPUResourceProperty& other) const
{
return Get() == other.Get();
}
- GPUResourceProperty& operator=(const GPUResourceProperty& other)
- {
- if (this != &other)
- Set(other.Get());
- return *this;
- }
-
FORCE_INLINE GPUResourceProperty& operator=(T& other)
{
- Set(&other);
+ OnSet(&other);
return *this;
}
FORCE_INLINE GPUResourceProperty& operator=(T* other)
{
- Set(other);
+ OnSet(other);
return *this;
}
///
/// Implicit conversion to GPU Resource
///
- /// Resource
FORCE_INLINE operator T*() const
{
- return _resource;
+ return (T*)_resource;
}
///
/// Implicit conversion to resource
///
- /// True if resource has been binded, otherwise false
FORCE_INLINE operator bool() const
{
return _resource != nullptr;
@@ -108,37 +131,17 @@ public:
///
/// Implicit conversion to resource
///
- /// Resource
FORCE_INLINE T* operator->() const
{
- return _resource;
+ return (T*)_resource;
}
///
/// Gets linked resource
///
- /// Resource
FORCE_INLINE T* Get() const
{
- return _resource;
- }
-
- ///
- /// Checks if resource has been binded
- ///
- /// True if resource has been binded, otherwise false
- FORCE_INLINE bool IsBinded() const
- {
- return _resource != nullptr;
- }
-
- ///
- /// Checks if resource is missing
- ///
- /// True if resource is missing, otherwise false
- FORCE_INLINE bool IsMissing() const
- {
- return _resource == nullptr;
+ return (T*)_resource;
}
public:
@@ -148,19 +151,7 @@ public:
/// Value to assign
void Set(T* value)
{
- if (_resource != value)
- {
- // Remove reference from the old one
- if (_resource)
- _resource->Releasing.template Unbind(this);
-
- // Change referenced object
- _resource = value;
-
- // Add reference to the new one
- if (_resource)
- _resource->Releasing.template Bind(this);
- }
+ OnSet(value);
}
///
@@ -168,22 +159,7 @@ public:
///
void Unlink()
{
- if (_resource)
- {
- // Remove reference from the old one
- _resource->Releasing.template Unbind(this);
- _resource = nullptr;
- }
- }
-
-private:
- void onResourceUnload()
- {
- if (_resource)
- {
- _resource = nullptr;
- OnUnload(this);
- }
+ OnSet(nullptr);
}
};
diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp
index fa03fb7cd..f91c58cea 100644
--- a/Source/Engine/Graphics/Graphics.cpp
+++ b/Source/Engine/Graphics/Graphics.cpp
@@ -67,7 +67,8 @@ void GraphicsSettings::Apply()
Graphics::AllowCSMBlending = AllowCSMBlending;
Graphics::GlobalSDFQuality = GlobalSDFQuality;
Graphics::GIQuality = GIQuality;
- Graphics::PostProcessSettings = PostProcessSettings;
+ Graphics::PostProcessSettings = ::PostProcessSettings();
+ Graphics::PostProcessSettings.BlendWith(PostProcessSettings, 1.0f);
}
void Graphics::DisposeDevice()
diff --git a/Source/Engine/Graphics/Materials/MaterialInfo.h b/Source/Engine/Graphics/Materials/MaterialInfo.h
index 86fab4548..afa23a6cc 100644
--- a/Source/Engine/Graphics/Materials/MaterialInfo.h
+++ b/Source/Engine/Graphics/Materials/MaterialInfo.h
@@ -3,7 +3,6 @@
#pragma once
#include "../Enums.h"
-#include "Engine/Core/Math/Math.h"
///
/// Material domain type. Material domain defines the target usage of the material shader.
@@ -86,7 +85,7 @@ API_ENUM() enum class MaterialBlendMode : byte
API_ENUM() enum class MaterialShadingModel : byte
{
///
- /// The unlit material. Emissive channel is used as an output color. Can perform custom lighting operations or just glow. Won't be affected by the lighting pipeline.
+ /// The unlit material. The emissive channel is used as an output color. Can perform custom lighting operations or just glow. Won't be affected by the lighting pipeline.
///
Unlit = 0,
@@ -96,7 +95,7 @@ API_ENUM() enum class MaterialShadingModel : byte
Lit = 1,
///
- /// The subsurface material. Intended for materials like vax or skin that need light scattering to transport simulation through the object.
+ /// The subsurface material. Intended for materials like wax or skin that need light scattering to transport simulation through the object.
///
Subsurface = 2,
@@ -366,12 +365,12 @@ API_ENUM() enum class MaterialDecalBlendingMode : byte
API_ENUM() enum class MaterialTransparentLightingMode : byte
{
///
- /// Default directional lighting evaluated per-pixel at the material surface. Use it for semi-transparent surfaces - with both diffuse and specular lighting component active.
+ /// Default directional lighting evaluated per-pixel at the material surface. Use it for semi-transparent surfaces - with both diffuse and specular lighting components active.
///
Surface = 0,
///
- /// Non-directional lighting evaluated per-pixel at material surface. Use it for volumetric objects such as smoke, rain or dust - only diffuse lighting term is active (no specular highlights).
+ /// Non-directional lighting evaluated per-pixel at material surface. Use it for volumetric objects such as smoke, rain or dust - only the diffuse lighting term is active (no specular highlights).
///
SurfaceNonDirectional = 1,
};
diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h
index 32caa2d01..d7359d905 100644
--- a/Source/Engine/Graphics/Models/ModelData.h
+++ b/Source/Engine/Graphics/Models/ModelData.h
@@ -90,6 +90,21 @@ public:
///
Array BlendShapes;
+ ///
+ /// Global translation for this mesh to be at it's local origin.
+ ///
+ Vector3 OriginTranslation = Vector3::Zero;
+
+ ///
+ /// Orientation for this mesh at it's local origin.
+ ///
+ Quaternion OriginOrientation = Quaternion::Identity;
+
+ ///
+ /// Meshes scaling.
+ ///
+ Vector3 Scaling = Vector3::One;
+
public:
///
/// Determines whether this instance has any mesh data.
diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h
index f3440c4a5..1d695f353 100644
--- a/Source/Engine/Graphics/PostProcessSettings.h
+++ b/Source/Engine/Graphics/PostProcessSettings.h
@@ -5,6 +5,7 @@
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Core/Math/Vector4.h"
#include "Engine/Content/AssetReference.h"
+#include "Engine/Content/SoftAssetReference.h"
#include "Engine/Core/ISerializable.h"
#include "Engine/Content/Assets/Texture.h"
#include "Engine/Content/Assets/MaterialBase.h"
@@ -446,13 +447,13 @@ API_STRUCT() struct FLAXENGINE_API BloomSettings : ISerializable
bool Enabled = true;
///
- /// Bloom effect strength. Value 0 disabled is, while higher values increase the effect.
+ /// Bloom effect strength. Set a value of 0 to disabled it, while higher values increase the effect.
///
API_FIELD(Attributes="Limit(0, 20.0f, 0.01f), EditorOrder(1), PostProcessSetting((int)BloomSettingsOverride.Intensity)")
float Intensity = 1.0f;
///
- /// Minimum pixel brightness value to start blowing. Values below the threshold are skipped.
+ /// Minimum pixel brightness value to start blooming. Values below this threshold are skipped.
///
API_FIELD(Attributes="Limit(0, 15.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)BloomSettingsOverride.Threshold)")
float Threshold = 3.0f;
@@ -850,7 +851,7 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable
/// The Lookup Table (LUT) used to perform color correction.
///
API_FIELD(Attributes="DefaultValue(null), EditorOrder(22), PostProcessSetting((int)ColorGradingSettingsOverride.LutTexture)")
- AssetReference LutTexture;
+ SoftAssetReference LutTexture;
///
/// The LUT blending weight (normalized to range 0-1). Default is 1.0.
@@ -986,13 +987,13 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable
float MaxBrightness = 2.0f;
///
- /// The lower bound for the luminance histogram of the scene color. Value is in percent and limits the pixels below this brightness. Use values from range 60-80. Used only in AutomaticHistogram mode.
+ /// The lower bound for the luminance histogram of the scene color. This value is in percent and limits the pixels below this brightness. Use values in the range of 60-80. Used only in AutomaticHistogram mode.
///
API_FIELD(Attributes="Limit(1, 99, 0.001f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.HistogramLowPercent)")
float HistogramLowPercent = 70.0f;
///
- /// The upper bound for the luminance histogram of the scene color. Value is in percent and limits the pixels above this brightness. Use values from range 80-95. Used only in AutomaticHistogram mode.
+ /// The upper bound for the luminance histogram of the scene color. This value is in percent and limits the pixels above this brightness. Use values in the range of 80-95. Used only in AutomaticHistogram mode.
///
API_FIELD(Attributes="Limit(1, 99, 0.001f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.HistogramHighPercent)")
float HistogramHighPercent = 98.0f;
@@ -1090,13 +1091,13 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable
Float3 VignetteColor = Float3(0, 0, 0.001f);
///
- /// Controls shape of the vignette. Values near 0 produce rectangle shape. Higher values result in round shape. The default value is 0.125.
+ /// Controls the shape of the vignette. Values near 0 produce a rectangular shape. Higher values result in a rounder shape. The default value is 0.125.
///
API_FIELD(Attributes="Limit(0.0001f, 2.0f, 0.001f), EditorOrder(2), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteShapeFactor)")
float VignetteShapeFactor = 0.125f;
///
- /// Intensity of the grain filter. Value 0 hides it. The default value is 0.005.
+ /// Intensity of the grain filter. A value of 0 hides it. The default value is 0.005.
///
API_FIELD(Attributes="Limit(0.0f, 2.0f, 0.005f), EditorOrder(3), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainAmount)")
float GrainAmount = 0.006f;
@@ -1108,19 +1109,19 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable
float GrainParticleSize = 1.6f;
///
- /// Speed of the grain particles animation.
+ /// Speed of the grain particle animation.
///
API_FIELD(Attributes="Limit(0.0f, 10.0f, 0.01f), EditorOrder(5), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainSpeed)")
float GrainSpeed = 1.0f;
///
- /// Controls chromatic aberration effect strength. Value 0 hides it.
+ /// Controls the chromatic aberration effect strength. A value of 0 hides it.
///
API_FIELD(Attributes="Limit(0.0f, 1.0f, 0.01f), EditorOrder(6), PostProcessSetting((int)CameraArtifactsSettingsOverride.ChromaticDistortion)")
float ChromaticDistortion = 0.0f;
///
- /// Screen tint color (alpha channel defines the blending factor).
+ /// Screen tint color (the alpha channel defines the blending factor).
///
API_FIELD(Attributes="DefaultValue(typeof(Color), \"0,0,0,0\"), EditorOrder(7), PostProcessSetting((int)CameraArtifactsSettingsOverride.ScreenFadeColor)")
Color ScreenFadeColor = Color::Transparent;
@@ -1226,7 +1227,7 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
LensFlaresSettingsOverride OverrideFlags = Override::None;
///
- /// Strength of the effect. Value 0 disabled it.
+ /// Strength of the effect. A value of 0 disables it.
///
API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(0), PostProcessSetting((int)LensFlaresSettingsOverride.Intensity)")
float Intensity = 1.0f;
@@ -1277,10 +1278,10 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
/// Fullscreen lens dirt texture.
///
API_FIELD(Attributes="DefaultValue(null), EditorOrder(8), PostProcessSetting((int)LensFlaresSettingsOverride.LensDirt)")
- AssetReference LensDirt;
+ SoftAssetReference LensDirt;
///
- /// Fullscreen lens dirt intensity parameter. Allows to tune dirt visibility.
+ /// Fullscreen lens dirt intensity parameter. Allows tuning dirt visibility.
///
API_FIELD(Attributes="Limit(0, 100, 0.01f), EditorOrder(9), PostProcessSetting((int)LensFlaresSettingsOverride.LensDirtIntensity)")
float LensDirtIntensity = 1.0f;
@@ -1289,13 +1290,13 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
/// Custom lens color texture (1D) used for lens color spectrum.
///
API_FIELD(Attributes="DefaultValue(null), EditorOrder(10), PostProcessSetting((int)LensFlaresSettingsOverride.LensColor)")
- AssetReference LensColor;
+ SoftAssetReference LensColor;
///
/// Custom lens star texture sampled by lens flares.
///
API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)LensFlaresSettingsOverride.LensStar)")
- AssetReference LensStar;
+ SoftAssetReference LensStar;
public:
///
@@ -1418,13 +1419,13 @@ API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable
DepthOfFieldSettingsOverride OverrideFlags = Override::None;
///
- /// If checked, depth of field effect will be visible.
+ /// If checked, the depth of field effect will be visible.
///
API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)DepthOfFieldSettingsOverride.Enabled)")
bool Enabled = false;
///
- /// The blur intensity in the out-of-focus areas. Allows reducing blur amount by scaling down the Gaussian Blur radius. Normalized to range 0-1.
+ /// The blur intensity in the out-of-focus areas. Allows reducing the blur amount by scaling down the Gaussian Blur radius. Normalized to range 0-1.
///
API_FIELD(Attributes="Limit(0, 1, 0.01f), EditorOrder(1), PostProcessSetting((int)DepthOfFieldSettingsOverride.BlurStrength)")
float BlurStrength = 1.0f;
@@ -1478,7 +1479,7 @@ API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable
float BokehBrightness = 1.0f;
///
- /// Defines bokeh shapes type.
+ /// Defines the type of the bokeh shapes.
///
API_FIELD(Attributes="EditorOrder(10), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehShape)")
BokehShapeType BokehShape = BokehShapeType::Octagon;
@@ -1487,22 +1488,22 @@ API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable
/// If BokehShape is set to Custom, then this texture will be used for the bokeh shapes. For best performance, use small, compressed, grayscale textures (for instance 32px).
///
API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehShapeCustom)")
- AssetReference BokehShapeCustom;
+ SoftAssetReference BokehShapeCustom;
///
- /// The minimum pixel brightness to create bokeh. Pixels with lower brightness will be skipped.
+ /// The minimum pixel brightness to create the bokeh. Pixels with lower brightness will be skipped.
///
API_FIELD(Attributes="Limit(0, 10000.0f, 0.01f), EditorOrder(12), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehBrightnessThreshold)")
float BokehBrightnessThreshold = 3.0f;
///
- /// Depth of Field bokeh shapes blur threshold.
+ /// Depth of Field bokeh shape blur threshold.
///
API_FIELD(Attributes="Limit(0, 1.0f, 0.001f), EditorOrder(13), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehBlurThreshold)")
float BokehBlurThreshold = 0.05f;
///
- /// Controls bokeh shapes brightness falloff. Higher values reduce bokeh visibility.
+ /// Controls bokeh shape brightness falloff. Higher values reduce bokeh visibility.
///
API_FIELD(Attributes="Limit(0, 2.0f, 0.001f), EditorOrder(14), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehFalloff)")
float BokehFalloff = 0.5f;
@@ -1574,25 +1575,25 @@ API_STRUCT() struct FLAXENGINE_API MotionBlurSettings : ISerializable
MotionBlurSettingsOverride OverrideFlags = Override::None;
///
- /// If checked, motion blur effect will be rendered.
+ /// If checked, the motion blur effect will be rendered.
///
API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)MotionBlurSettingsOverride.Enabled)")
bool Enabled = true;
///
- /// The blur effect strength. Value 0 disabled is, while higher values increase the effect.
+ /// The blur effect strength. A value of 0 disables it, while higher values increase the effect.
///
API_FIELD(Attributes="Limit(0, 5, 0.01f), EditorOrder(1), PostProcessSetting((int)MotionBlurSettingsOverride.Scale)")
float Scale = 1.0f;
///
- /// The amount of sample points used during motion blur rendering. It affects quality and performance.
+ /// The amount of sample points used during motion blur rendering. It affects blur quality and performance.
///
API_FIELD(Attributes="Limit(4, 32, 0.1f), EditorOrder(2), PostProcessSetting((int)MotionBlurSettingsOverride.SampleCount)")
int32 SampleCount = 10;
///
- /// The motion vectors texture resolution. Motion blur uses per-pixel motion vectors buffer that contains objects movement information. Use lower resolution to improve performance.
+ /// The motion vectors texture resolution. Motion blur uses a per-pixel motion vector buffer that contains an objects movement information. Use a lower resolution to improve performance.
///
API_FIELD(Attributes="EditorOrder(3), PostProcessSetting((int)MotionBlurSettingsOverride.MotionVectorsResolution)")
ResolutionMode MotionVectorsResolution = ResolutionMode::Half;
@@ -1897,13 +1898,13 @@ API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable
float TAA_Sharpness = 0.0f;
///
- /// The blend coefficient for stationary fragments. Controls the percentage of history sample blended into final color for fragments with minimal active motion.
+ /// The blend coefficient for stationary fragments. Controls the percentage of history samples blended into the final color for fragments with minimal active motion.
///
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(3), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_StationaryBlending), EditorDisplay(null, \"TAA Stationary Blending\")")
float TAA_StationaryBlending = 0.95f;
///
- /// The blending coefficient for moving fragments. Controls the percentage of history sample blended into the final color for fragments with significant active motion.
+ /// The blending coefficient for moving fragments. Controls the percentage of history samples blended into the final color for fragments with significant active motion.
///
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\")")
float TAA_MotionBlending = 0.7f;
diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp
index 48e89f59e..8a10a0444 100644
--- a/Source/Engine/Graphics/RenderTask.cpp
+++ b/Source/Engine/Graphics/RenderTask.cpp
@@ -8,11 +8,12 @@
#include "Engine/Core/Collections/Sorting.h"
#include "Engine/Debug/DebugLog.h"
#include "Engine/Level/Level.h"
+#include "Engine/Level/Scene/Scene.h"
#include "Engine/Level/Actors/Camera.h"
+#include "Engine/Level/Actors/PostFxVolume.h"
#include "Engine/Renderer/Renderer.h"
#include "Engine/Render2D/Render2D.h"
#include "Engine/Engine/Engine.h"
-#include "Engine/Level/Actors/PostFxVolume.h"
#include "Engine/Profiler/Profiler.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Threading/Threading.h"
@@ -202,15 +203,21 @@ void SceneRenderTask::CollectPostFxVolumes(RenderContext& renderContext)
{
Level::CollectPostFxVolumes(renderContext);
}
- if (EnumHasAllFlags(ActorsSource , ActorsSources::CustomActors))
+ if (EnumHasAllFlags(ActorsSource, ActorsSources::CustomActors))
{
for (Actor* a : CustomActors)
{
auto* postFxVolume = dynamic_cast(a);
if (postFxVolume && a->GetIsActive())
- {
postFxVolume->Collect(renderContext);
- }
+ }
+ }
+ if (EnumHasAllFlags(ActorsSource, ActorsSources::CustomScenes))
+ {
+ for (Scene* scene : CustomScenes)
+ {
+ if (scene && scene->IsActiveInHierarchy())
+ scene->Rendering.CollectPostFxVolumes(renderContext);
}
}
}
@@ -282,6 +289,14 @@ void SceneRenderTask::OnCollectDrawCalls(RenderContextBatch& renderContextBatch,
ASSERT_LOW_LAYER(_customActorsScene);
_customActorsScene->Draw(renderContextBatch, (SceneRendering::DrawCategory)category);
}
+ if (EnumHasAllFlags(ActorsSource, ActorsSources::CustomScenes))
+ {
+ for (Scene* scene : CustomScenes)
+ {
+ if (scene && scene->IsActiveInHierarchy())
+ scene->Rendering.Draw(renderContextBatch, (SceneRendering::DrawCategory)category);
+ }
+ }
if (EnumHasAllFlags(ActorsSource, ActorsSources::Scenes))
{
Level::DrawActors(renderContextBatch, category);
diff --git a/Source/Engine/Graphics/RenderTask.h b/Source/Engine/Graphics/RenderTask.h
index 9123751bc..864d95214 100644
--- a/Source/Engine/Graphics/RenderTask.h
+++ b/Source/Engine/Graphics/RenderTask.h
@@ -21,6 +21,7 @@ class PostProcessEffect;
struct RenderContext;
class Camera;
class Actor;
+class Scene;
///
/// Allows to perform custom rendering using graphics pipeline.
@@ -174,6 +175,11 @@ API_ENUM(Attributes="Flags") enum class ActorsSources
///
CustomActors = 2,
+ ///
+ /// The scenes from the custom collection.
+ ///
+ CustomScenes = 4,
+
///
/// The actors from the loaded scenes and custom collection.
///
@@ -267,9 +273,14 @@ public:
public:
///
- /// The custom set of actors to render.
+ /// The custom set of actors to render. Used when ActorsSources::CustomActors flag is active.
///
- Array CustomActors;
+ API_FIELD() Array CustomActors;
+
+ ///
+ /// The custom set of scenes to render. Used when ActorsSources::CustomScenes flag is active.
+ ///
+ API_FIELD() Array CustomScenes;
///
/// Adds the custom actor to the rendering.
diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp
index e76ee9996..86d983fd8 100644
--- a/Source/Engine/Graphics/Shaders/GPUShader.cpp
+++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp
@@ -87,7 +87,11 @@ bool GPUShader::Create(MemoryReadStream& stream)
GPUShaderProgramInitializer initializer;
#if !BUILD_RELEASE
initializer.Owner = this;
+ const StringView name = GetName();
+#else
+ const StringView name;
#endif
+ const bool hasCompute = GPUDevice::Instance->Limits.HasCompute;
for (int32 i = 0; i < shadersCount; i++)
{
const ShaderStage type = static_cast(stream.ReadByte());
@@ -117,10 +121,15 @@ bool GPUShader::Create(MemoryReadStream& stream)
stream.ReadBytes(&initializer.Bindings, sizeof(ShaderBindings));
// Create shader program
+ if (type == ShaderStage::Compute && !hasCompute)
+ {
+ LOG(Warning, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
+ continue;
+ }
GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, cache, cacheSize, stream);
if (shader == nullptr)
{
- LOG(Error, "Failed to create {} Shader program '{}'.", ::ToString(type), String(initializer.Name));
+ LOG(Error, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
return true;
}
diff --git a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h
index 09c45506f..31f6638dc 100644
--- a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h
+++ b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h
@@ -122,6 +122,19 @@ public:
///
class GPUShaderProgramVS : public GPUShaderProgram
{
+public:
+ // Input element run-time data (see VertexShaderMeta::InputElement for compile-time data)
+ PACK_STRUCT(struct InputElement
+ {
+ byte Type; // VertexShaderMeta::InputType
+ byte Index;
+ byte Format; // PixelFormat
+ byte InputSlot;
+ uint32 AlignedByteOffset; // Fixed value or INPUT_LAYOUT_ELEMENT_ALIGN if auto
+ byte InputSlotClass; // INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA or INPUT_LAYOUT_ELEMENT_PER_INSTANCE_DATA
+ uint32 InstanceDataStepRate; // 0 if per-vertex
+ });
+
public:
///
/// Gets input layout description handle (platform dependent).
diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp
index 409125838..015386fff 100644
--- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp
+++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp
@@ -22,10 +22,10 @@ TextureHeader::TextureHeader()
TextureGroup = -1;
}
-TextureHeader::TextureHeader(TextureHeader_Deprecated& old)
+TextureHeader::TextureHeader(const TextureHeader_Deprecated& old)
{
Platform::MemoryClear(this, sizeof(*this));
- Width = old.Width;;
+ Width = old.Width;
Height = old.Height;
MipLevels = old.MipLevels;
Format = old.Format;
@@ -49,7 +49,7 @@ StreamingTexture::StreamingTexture(ITextureOwner* parent, const String& name)
, _texture(nullptr)
, _isBlockCompressed(false)
{
- ASSERT(_owner != nullptr);
+ ASSERT(parent != nullptr);
// Always have created texture object
ASSERT(GPUDevice::Instance);
@@ -63,7 +63,6 @@ StreamingTexture::~StreamingTexture()
{
UnloadTexture();
SAFE_DELETE(_texture);
- ASSERT(_streamingTasks.Count() == 0);
}
Float2 StreamingTexture::Size() const
@@ -134,11 +133,9 @@ bool StreamingTexture::Create(const TextureHeader& header)
void StreamingTexture::UnloadTexture()
{
ScopeLock lock(_owner->GetOwnerLocker());
-
- // Release
+ CancelStreamingTasks();
_texture->ReleaseGPU();
_header.MipLevels = 0;
-
ASSERT(_streamingTasks.Count() == 0);
}
@@ -329,11 +326,11 @@ public:
, _dataLock(_streamingTexture->GetOwner()->LockData())
{
_streamingTexture->_streamingTasks.Add(this);
- _texture.OnUnload.Bind(this);
+ _texture.Released.Bind(this);
}
private:
- void onResourceUnload2(GPUTextureReference* ref)
+ void OnResourceReleased2()
{
// Unlink texture
if (_streamingTexture)
diff --git a/Source/Engine/Graphics/Textures/TextureBase.cpp b/Source/Engine/Graphics/Textures/TextureBase.cpp
index 538b15a4a..181955fce 100644
--- a/Source/Engine/Graphics/Textures/TextureBase.cpp
+++ b/Source/Engine/Graphics/Textures/TextureBase.cpp
@@ -660,6 +660,7 @@ uint64 TextureBase::GetMemoryUsage() const
void TextureBase::CancelStreaming()
{
+ Asset::CancelStreaming();
_texture.CancelStreamingTasks();
}
diff --git a/Source/Engine/Graphics/Textures/Types.h b/Source/Engine/Graphics/Textures/Types.h
index 9593cbbc2..31e0b1afc 100644
--- a/Source/Engine/Graphics/Textures/Types.h
+++ b/Source/Engine/Graphics/Textures/Types.h
@@ -106,5 +106,5 @@ struct FLAXENGINE_API TextureHeader
byte CustomData[10];
TextureHeader();
- TextureHeader(TextureHeader_Deprecated& old);
+ TextureHeader(const TextureHeader_Deprecated& old);
};
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.h
index 6205a628d..c0e0d8fd5 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.h
+++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.h
@@ -4,6 +4,7 @@
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPUResource.h"
+#include "Engine/Core/Collections/Dictionary.h"
#include "../GPUDeviceDX.h"
#include "../IncludeDirectXHeaders.h"
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp
index b58684a4e..52192c634 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp
+++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp
@@ -15,32 +15,21 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
{
case ShaderStage::Vertex:
{
- D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
-
- // Temporary variables
- byte Type, Format, Index, InputSlot, InputSlotClass;
- uint32 AlignedByteOffset, InstanceDataStepRate;
-
- // Load Input Layout (it may be empty)
+ // Load Input Layout
byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize);
ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS);
+ D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
for (int32 a = 0; a < inputLayoutSize; a++)
{
// Read description
- // TODO: maybe use struct and load at once?
- stream.ReadByte(&Type);
- stream.ReadByte(&Index);
- stream.ReadByte(&Format);
- stream.ReadByte(&InputSlot);
- stream.ReadUint32(&AlignedByteOffset);
- stream.ReadByte(&InputSlotClass);
- stream.ReadUint32(&InstanceDataStepRate);
+ GPUShaderProgramVS::InputElement inputElement;
+ stream.Read(inputElement);
// Get semantic name
const char* semanticName = nullptr;
// TODO: maybe use enum+mapping ?
- switch (Type)
+ switch (inputElement.Type)
{
case 1:
semanticName = "POSITION";
@@ -70,7 +59,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
semanticName = "BLENDWEIGHT";
break;
default:
- LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type);
+ LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type);
break;
}
@@ -78,12 +67,12 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
inputLayoutDesc[a] =
{
semanticName,
- static_cast(Index),
- static_cast(Format),
- static_cast(InputSlot),
- static_cast(AlignedByteOffset),
- static_cast(InputSlotClass),
- static_cast(InstanceDataStepRate)
+ static_cast(inputElement.Index),
+ static_cast(inputElement.Format),
+ static_cast(inputElement.InputSlot),
+ static_cast(inputElement.AlignedByteOffset),
+ static_cast(inputElement.InputSlotClass),
+ static_cast(inputElement.InstanceDataStepRate)
};
}
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h
index 2764ce235..27aba0c4b 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h
+++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUPipelineStateDX12.h
@@ -7,6 +7,7 @@
#include "Engine/Graphics/GPUPipelineState.h"
#include "GPUDeviceDX12.h"
#include "Types.h"
+#include "Engine/Core/Collections/Dictionary.h"
#include "../IncludeDirectXHeaders.h"
class GPUTextureViewDX12;
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp
index e1e716853..07352b674 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp
+++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp
@@ -20,32 +20,21 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
{
case ShaderStage::Vertex:
{
- D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
-
- // Temporary variables
- byte Type, Format, Index, InputSlot, InputSlotClass;
- uint32 AlignedByteOffset, InstanceDataStepRate;
-
// Load Input Layout (it may be empty)
byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize);
ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS);
+ D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
for (int32 a = 0; a < inputLayoutSize; a++)
{
// Read description
- // TODO: maybe use struct and load at once?
- stream.ReadByte(&Type);
- stream.ReadByte(&Index);
- stream.ReadByte(&Format);
- stream.ReadByte(&InputSlot);
- stream.ReadUint32(&AlignedByteOffset);
- stream.ReadByte(&InputSlotClass);
- stream.ReadUint32(&InstanceDataStepRate);
+ GPUShaderProgramVS::InputElement inputElement;
+ stream.Read(inputElement);
// Get semantic name
const char* semanticName = nullptr;
// TODO: maybe use enum+mapping ?
- switch (Type)
+ switch (inputElement.Type)
{
case 1:
semanticName = "POSITION";
@@ -75,7 +64,7 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
semanticName = "BLENDWEIGHT";
break;
default:
- LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type);
+ LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type);
break;
}
@@ -83,12 +72,12 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
inputLayout[a] =
{
semanticName,
- static_cast(Index),
- static_cast(Format),
- static_cast(InputSlot),
- static_cast(AlignedByteOffset),
- static_cast(InputSlotClass),
- static_cast(InstanceDataStepRate)
+ static_cast(inputElement.Index),
+ static_cast(inputElement.Format),
+ static_cast(inputElement.InputSlot),
+ static_cast(inputElement.AlignedByteOffset),
+ static_cast(inputElement.InputSlotClass),
+ static_cast(inputElement.InstanceDataStepRate)
};
}
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp
index 5e583d713..852ce5bad 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp
@@ -14,9 +14,9 @@
#include "Engine/Graphics/PixelFormatExtensions.h"
#if PLATFORM_DESKTOP
-#define VULKAN_UNIFORM_RING_BUFFER_SIZE 24 * 1024 * 1024
+#define VULKAN_UNIFORM_RING_BUFFER_SIZE (24 * 1024 * 1024)
#else
-#define VULKAN_UNIFORM_RING_BUFFER_SIZE 8 * 1024 * 1024
+#define VULKAN_UNIFORM_RING_BUFFER_SIZE (8 * 1024 * 1024)
#endif
UniformBufferUploaderVulkan::UniformBufferUploaderVulkan(GPUDeviceVulkan* device)
@@ -153,10 +153,6 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons
vertexBindingDescriptions[i].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
}
- // Temporary variables
- byte Type, Format, Index, InputSlot, InputSlotClass;
- uint32 AlignedByteOffset, InstanceDataStepRate;
-
// Load Input Layout (it may be empty)
byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize);
@@ -167,32 +163,26 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons
for (int32 a = 0; a < inputLayoutSize; a++)
{
// Read description
- // TODO: maybe use struct and load at once?
- stream.ReadByte(&Type);
- stream.ReadByte(&Index);
- stream.ReadByte(&Format);
- stream.ReadByte(&InputSlot);
- stream.ReadUint32(&AlignedByteOffset);
- stream.ReadByte(&InputSlotClass);
- stream.ReadUint32(&InstanceDataStepRate);
+ GPUShaderProgramVS::InputElement inputElement;
+ stream.Read(inputElement);
- const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)Format);
- if (AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN)
- offset = AlignedByteOffset;
+ const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)inputElement.Format);
+ if (inputElement.AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN)
+ offset = inputElement.AlignedByteOffset;
- auto& vertexBindingDescription = vertexBindingDescriptions[InputSlot];
- vertexBindingDescription.binding = InputSlot;
+ auto& vertexBindingDescription = vertexBindingDescriptions[inputElement.InputSlot];
+ vertexBindingDescription.binding = inputElement.InputSlot;
vertexBindingDescription.stride = Math::Max(vertexBindingDescription.stride, (uint32_t)(offset + size));
- vertexBindingDescription.inputRate = InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
- ASSERT(InstanceDataStepRate == 0 || InstanceDataStepRate == 1);
+ vertexBindingDescription.inputRate = inputElement.InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
+ ASSERT(inputElement.InstanceDataStepRate == 0 || inputElement.InstanceDataStepRate == 1);
auto& vertexAttributeDescription = vertexAttributeDescriptions[a];
vertexAttributeDescription.location = a;
- vertexAttributeDescription.binding = InputSlot;
- vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)Format);
+ vertexAttributeDescription.binding = inputElement.InputSlot;
+ vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)inputElement.Format);
vertexAttributeDescription.offset = offset;
- bindingsCount = Math::Max(bindingsCount, (uint32)InputSlot + 1);
+ bindingsCount = Math::Max(bindingsCount, (uint32)inputElement.InputSlot + 1);
offset += size;
}
diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp
index 75e3c12d4..addb4861e 100644
--- a/Source/Engine/Level/Actor.cpp
+++ b/Source/Engine/Level/Actor.cpp
@@ -1406,7 +1406,7 @@ Script* Actor::FindScript(const MClass* type) const
CHECK_RETURN(type, nullptr);
for (auto script : Scripts)
{
- if (script->GetClass()->IsSubClassOf(type))
+ if (script->GetClass()->IsSubClassOf(type) || script->GetClass()->HasInterface(type))
return script;
}
for (auto child : Children)
diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h
index a1f2dc78f..0ce9a0dbc 100644
--- a/Source/Engine/Level/Actor.h
+++ b/Source/Engine/Level/Actor.h
@@ -33,12 +33,12 @@ API_CLASS(Abstract) class FLAXENGINE_API Actor : public SceneObject
friend Prefab;
friend PrefabInstanceData;
protected:
- int16 _isActive : 1;
- int16 _isActiveInHierarchy : 1;
- int16 _isPrefabRoot : 1;
- int16 _isEnabled : 1;
- int16 _drawNoCulling : 1;
- int16 _drawCategory : 4;
+ uint16 _isActive : 1;
+ uint16 _isActiveInHierarchy : 1;
+ uint16 _isPrefabRoot : 1;
+ uint16 _isEnabled : 1;
+ uint16 _drawNoCulling : 1;
+ uint16 _drawCategory : 4;
byte _layer;
StaticFlags _staticFlags;
Transform _localTransform;
diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp
index 88f7e6876..c1b5af398 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.cpp
+++ b/Source/Engine/Level/Actors/AnimatedModel.cpp
@@ -172,6 +172,28 @@ void AnimatedModel::GetNodeTransformation(const StringView& nodeName, Matrix& no
GetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace);
}
+void AnimatedModel::SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTransformation, bool worldSpace)
+{
+ if (GraphInstance.NodesPose.IsEmpty())
+ const_cast(this)->PreInitSkinningData(); // Ensure to have valid nodes pose to return
+ CHECK(nodeIndex >= 0 && nodeIndex < GraphInstance.NodesPose.Count());
+ GraphInstance.NodesPose[nodeIndex] = nodeTransformation;
+ if (worldSpace)
+ {
+ Matrix world;
+ _transform.GetWorld(world);
+ Matrix invWorld;
+ Matrix::Invert(world, invWorld);
+ GraphInstance.NodesPose[nodeIndex] = GraphInstance.NodesPose[nodeIndex] * invWorld;
+ }
+ OnAnimationUpdated();
+}
+
+void AnimatedModel::SetNodeTransformation(const StringView& nodeName, const Matrix& nodeTransformation, bool worldSpace)
+{
+ SetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace);
+}
+
int32 AnimatedModel::FindClosestNode(const Vector3& location, bool worldSpace) const
{
if (GraphInstance.NodesPose.IsEmpty())
diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h
index 0c5c4d73b..029e17b62 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.h
+++ b/Source/Engine/Level/Actors/AnimatedModel.h
@@ -229,6 +229,22 @@ 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;
+ ///
+ /// Sets the node final transformation. If multiple nodes are to be set within a frame, do not use set worldSpace to true, and do the conversion yourself to avoid recalculation of inv matrices.
+ ///
+ /// The index of the skinned model skeleton node.
+ /// The final node transformation matrix.
+ /// True if convert matrices from world-space, otherwise values will be in local-space of the actor.
+ API_FUNCTION() void SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTransformation, bool worldSpace = false);
+
+ ///
+ /// Sets the node final transformation. If multiple nodes are to be set within a frame, do not use set worldSpace to true, and do the conversion yourself to avoid recalculation of inv matrices.
+ ///
+ /// The name of the skinned model skeleton node.
+ /// The final node transformation matrix.
+ /// True if convert matrices from world-space, otherwise values will be in local-space of the actor.
+ API_FUNCTION() void SetNodeTransformation(const StringView& nodeName, const Matrix& nodeTransformation, bool worldSpace = false);
+
///
/// Finds the closest node to a given location.
///
diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h
index 22d950dd4..c63a5dcf6 100644
--- a/Source/Engine/Level/Actors/Camera.h
+++ b/Source/Engine/Level/Actors/Camera.h
@@ -66,7 +66,7 @@ public:
///
/// Gets the value indicating if camera should use perspective rendering mode, otherwise it will use orthographic projection.
///
- API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Camera\"), Tooltip(\"Enables perspective projection mode, otherwise uses orthographic.\")")
+ API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(true), EditorDisplay(\"Camera\")")
bool GetUsePerspective() const;
///
@@ -77,7 +77,7 @@ public:
///
/// Gets the camera's field of view (in degrees).
///
- API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), Tooltip(\"Field of view angle in degrees.\")")
+ API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective))")
float GetFieldOfView() const;
///
@@ -88,7 +88,7 @@ public:
///
/// Gets the custom aspect ratio. 0 if not use custom value.
///
- API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(0.0f), Limit(0, 10, 0.01f), EditorDisplay(\"Camera\"), Tooltip(\"Custom aspect ratio to use. Set to 0 to disable.\")")
+ API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(0.0f), Limit(0, 10, 0.01f), EditorDisplay(\"Camera\"), VisibleIf(nameof(UsePerspective))")
float GetCustomAspectRatio() const;
///
@@ -99,7 +99,7 @@ public:
///
/// Gets camera's near plane distance.
///
- API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\"), Tooltip(\"Near clipping plane distance\")")
+ API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\")")
float GetNearPlane() const;
///
@@ -110,7 +110,7 @@ public:
///
/// Gets camera's far plane distance.
///
- API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\"), Tooltip(\"Far clipping plane distance\")")
+ API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\")")
float GetFarPlane() const;
///
@@ -121,7 +121,7 @@ public:
///
/// Gets the orthographic projection scale.
///
- API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1.0f), Limit(0.0001f, 1000, 0.01f), EditorDisplay(\"Camera\"), Tooltip(\"Orthographic projection scale\")")
+ API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1.0f), Limit(0.0001f, 1000, 0.01f), EditorDisplay(\"Camera\"), VisibleIf(nameof(UsePerspective), true)")
float GetOrthographicScale() const;
///
diff --git a/Source/Engine/Level/Actors/DirectionalLight.h b/Source/Engine/Level/Actors/DirectionalLight.h
index 3f57e74bb..d5f31324a 100644
--- a/Source/Engine/Level/Actors/DirectionalLight.h
+++ b/Source/Engine/Level/Actors/DirectionalLight.h
@@ -13,7 +13,7 @@ class FLAXENGINE_API DirectionalLight : public LightWithShadow
DECLARE_SCENE_OBJECT(DirectionalLight);
public:
///
- /// The number of cascades used for slicing the range of depth covered by the light. Values are 1, 2 or 4 cascades; a typical scene uses 4 cascades.
+ /// The number of cascades used for slicing the range of depth covered by the light during shadow rendering. Values are 1, 2 or 4 cascades; a typical scene uses 4 cascades.
///
API_FIELD(Attributes="EditorOrder(65), DefaultValue(4), Limit(1, 4), EditorDisplay(\"Shadow\")")
int32 CascadeCount = 4;
diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.h b/Source/Engine/Level/Actors/ExponentialHeightFog.h
index b0e5751d9..7b400fe4e 100644
--- a/Source/Engine/Level/Actors/ExponentialHeightFog.h
+++ b/Source/Engine/Level/Actors/ExponentialHeightFog.h
@@ -29,7 +29,7 @@ public:
float FogDensity = 0.02f;
///
- /// The fog height density factor that controls how the density increases as height decreases. The smaller values produce more visible transition larger.
+ /// The fog height density factor that controls how the density increases as height decreases. Smaller values produce a more visible transition layer.
///
API_FIELD(Attributes="EditorOrder(20), DefaultValue(0.2f), Limit(0.0001f, 10.0f, 0.001f), EditorDisplay(\"Exponential Height Fog\")")
float FogHeightFalloff = 0.2f;
@@ -55,7 +55,7 @@ public:
float StartDistance = 0.0f;
///
- /// Scene elements past this distance will not have fog applied. This is useful for excluding skyboxes which already have fog baked in.
+ /// Scene elements past this distance will not have fog applied. This is useful for excluding skyboxes which already have fog baked in. Setting this value to 0 disables it.
///
API_FIELD(Attributes="EditorOrder(60), DefaultValue(0.0f), Limit(0), EditorDisplay(\"Exponential Height Fog\")")
float FogCutoffDistance = 0.0f;
@@ -111,7 +111,7 @@ public:
Color VolumetricFogAlbedo = Color::White;
///
- /// Light emitted by height fog. This is a density so more light is emitted the further you are looking through the fog.
+ /// Light emitted by height fog. This is a density value so more light is emitted the further you are looking through the fog.
/// In most cases using a Skylight is a better choice, however, it may be useful in certain scenarios.
///
API_FIELD(Attributes="EditorOrder(330), DefaultValue(typeof(Color), \"0,0,0,1\"), EditorDisplay(\"Volumetric Fog\", \"Emissive\")")
diff --git a/Source/Engine/Level/Actors/Light.h b/Source/Engine/Level/Actors/Light.h
index 86ca59ef5..efb137b60 100644
--- a/Source/Engine/Level/Actors/Light.h
+++ b/Source/Engine/Level/Actors/Light.h
@@ -29,7 +29,7 @@ public:
float Brightness = 3.14f;
///
- /// Controls light visibility range. The distance at which the light be completely faded. Use value 0 to always draw light.
+ /// Controls light visibility range. The distance at which the light becomes completely faded. Use a value of 0 to always draw light.
///
API_FIELD(Attributes="EditorOrder(35), Limit(0, float.MaxValue, 10.0f), EditorDisplay(\"Light\")")
float ViewDistance = 0.0f;
@@ -87,19 +87,19 @@ public:
float MinRoughness = 0.04f;
///
- /// The light shadows casting distance from view.
+ /// Shadows casting distance from view.
///
API_FIELD(Attributes="EditorOrder(80), EditorDisplay(\"Shadow\", \"Distance\"), Limit(0, 1000000)")
float ShadowsDistance = 5000.0f;
///
- /// The light shadows fade off distance
+ /// Shadows fade off distance.
///
API_FIELD(Attributes="EditorOrder(90), EditorDisplay(\"Shadow\", \"Fade Distance\"), Limit(0.0f, 10000.0f, 0.1f)")
float ShadowsFadeDistance = 500.0f;
///
- /// The light shadows edges sharpness
+ /// TheShadows edges sharpness.
///
API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Shadow\", \"Sharpness\"), Limit(1.0f, 10.0f, 0.001f)")
float ShadowsSharpness = 1.0f;
@@ -129,7 +129,7 @@ public:
float ContactShadowsLength = 0.0f;
///
- /// Shadows casting mode by this visual element
+ /// Describes how a visual element casts shadows.
///
API_FIELD(Attributes="EditorOrder(60), EditorDisplay(\"Shadow\", \"Mode\")")
ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All;
diff --git a/Source/Engine/Level/Actors/Sky.h b/Source/Engine/Level/Actors/Sky.h
index 81aca3653..6d0935f78 100644
--- a/Source/Engine/Level/Actors/Sky.h
+++ b/Source/Engine/Level/Actors/Sky.h
@@ -31,19 +31,19 @@ public:
///
/// Directional light that is used to simulate the sun.
///
- API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Sun\")")
+ API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Sky\")")
ScriptingObjectReference SunLight;
///
/// The sun disc scale.
///
- API_FIELD(Attributes="EditorOrder(20), DefaultValue(2.0f), EditorDisplay(\"Sun\"), Limit(0, 100, 0.01f)")
+ API_FIELD(Attributes="EditorOrder(20), DefaultValue(2.0f), EditorDisplay(\"Sky\"), Limit(0, 100, 0.01f)")
float SunDiscScale = 2.0f;
///
/// The sun power.
///
- API_FIELD(Attributes="EditorOrder(30), DefaultValue(8.0f), EditorDisplay(\"Sun\"), Limit(0, 1000, 0.01f)")
+ API_FIELD(Attributes="EditorOrder(30), DefaultValue(8.0f), EditorDisplay(\"Sky\"), Limit(0, 1000, 0.01f)")
float SunPower = 8.0f;
private:
diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp
index 194dbd9a9..dfe7c2be8 100644
--- a/Source/Engine/Level/Actors/Spline.cpp
+++ b/Source/Engine/Level/Actors/Spline.cpp
@@ -149,9 +149,14 @@ float Spline::GetSplineDuration() const
float Spline::GetSplineLength() const
{
float sum = 0.0f;
- const int32 slices = 20;
- const float step = 1.0f / (float)slices;
+ constexpr int32 slices = 20;
+ constexpr float step = 1.0f / (float)slices;
Vector3 prevPoint = Vector3::Zero;
+ if (Curve.GetKeyframes().Count() != 0)
+ {
+ const auto& a = Curve[0];
+ prevPoint = a.Value.Translation * _transform.Scale;
+ }
for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++)
{
const auto& a = Curve[i - 1];
@@ -176,6 +181,37 @@ float Spline::GetSplineLength() const
return Math::Sqrt(sum);
}
+float Spline::GetSplineSegmentLength(int32 index) const
+{
+ if (index == 0)
+ return 0.0f;
+ CHECK_RETURN(index > 0 && index < GetSplinePointsCount(), 0.0f);
+ float sum = 0.0f;
+ constexpr int32 slices = 20;
+ constexpr float step = 1.0f / (float)slices;
+ const auto& a = Curve[index - 1];
+ const auto& b = Curve[index];
+ Vector3 startPoint = a.Value.Translation * _transform.Scale;
+ {
+ const float length = Math::Abs(b.Time - a.Time);
+ Vector3 leftTangent, rightTangent;
+ AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent);
+ AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent);
+
+ // TODO: implement sth more analytical than brute-force solution
+ for (int32 slice = 0; slice < slices; slice++)
+ {
+ const float t = (float)slice * step;
+ Vector3 pos;
+ AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos);
+ pos *= _transform.Scale;
+ sum += (float)Vector3::DistanceSquared(pos, startPoint);
+ startPoint = pos;
+ }
+ }
+ return Math::Sqrt(sum);
+}
+
float Spline::GetSplineTime(int32 index) const
{
CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), 0.0f)
diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h
index cd022890b..4a81ee186 100644
--- a/Source/Engine/Level/Actors/Spline.h
+++ b/Source/Engine/Level/Actors/Spline.h
@@ -167,6 +167,13 @@ public:
///
API_PROPERTY() float GetSplineLength() const;
+ ///
+ /// Gets the length of the spline segment (distance between pair of two points).
+ ///
+ /// The index of the segment end index. Zero-based, smaller than GetSplinePointsCount().
+ /// The spline segment length.
+ API_FUNCTION() float GetSplineSegmentLength(int32 index) const;
+
///
/// Gets the time of the spline keyframe.
///
diff --git a/Source/Engine/Level/Actors/SpotLight.h b/Source/Engine/Level/Actors/SpotLight.h
index 37735692a..2c16d38e4 100644
--- a/Source/Engine/Level/Actors/SpotLight.h
+++ b/Source/Engine/Level/Actors/SpotLight.h
@@ -24,7 +24,7 @@ private:
public:
///
- /// Light source bulb radius
+ /// Light source bulb radius.
///
API_FIELD(Attributes="EditorOrder(2), DefaultValue(0.0f), EditorDisplay(\"Light\"), Limit(0, 1000, 0.01f)")
float SourceRadius = 0.0f;
@@ -42,54 +42,51 @@ public:
float FallOffExponent = 8.0f;
///
- /// IES texture (light profiles from real world measured data)
+ /// IES texture (light profiles from real world measured data).
///
API_FIELD(Attributes="EditorOrder(211), DefaultValue(null), EditorDisplay(\"IES Profile\", \"IES Texture\")")
AssetReference IESTexture;
///
- /// Enable/disable using light brightness from IES profile
+ /// Enable/disable using light brightness from IES profile.
///
API_FIELD(Attributes="EditorOrder(212), DefaultValue(false), EditorDisplay(\"IES Profile\", \"Use IES Brightness\")")
bool UseIESBrightness = false;
///
- /// Global scale for IES brightness contribution
+ /// Global scale for IES brightness contribution.
///
API_FIELD(Attributes="EditorOrder(213), DefaultValue(1.0f), Limit(0, 10000, 0.01f), EditorDisplay(\"IES Profile\", \"Brightness Scale\")")
float IESBrightnessScale = 1.0f;
public:
///
- /// Computes light brightness value
+ /// Computes light brightness value.
///
- /// Brightness
float ComputeBrightness() const;
///
- /// Gets scaled light radius
+ /// Gets scaled light radius.
///
float GetScaledRadius() const;
///
- /// Gets light radius
+ /// Gets light radius.
///
- API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(1000.0f), EditorDisplay(\"Light\"), Tooltip(\"Light radius\"), Limit(0, 10000, 0.1f)")
+ API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(1000.0f), EditorDisplay(\"Light\"), Limit(0, 10000, 0.1f)")
FORCE_INLINE float GetRadius() const
{
return _radius;
}
///
- /// Sets light radius
+ /// Sets light radius.
///
- /// New radius
API_PROPERTY() void SetRadius(float value);
///
- /// Gets the spot light's outer cone angle (in degrees)
+ /// Gets the spot light's outer cone angle (in degrees).
///
- /// Outer angle (in degrees)
API_PROPERTY(Attributes="EditorOrder(22), DefaultValue(43.0f), EditorDisplay(\"Light\"), Limit(1, 89, 0.1f)")
FORCE_INLINE float GetOuterConeAngle() const
{
@@ -97,15 +94,13 @@ public:
}
///
- /// Sets the spot light's outer cone angle (in degrees)
+ /// Sets the spot light's outer cone angle (in degrees).
///
- /// Value to assign
API_PROPERTY() void SetOuterConeAngle(float value);
///
- /// Sets the spot light's inner cone angle (in degrees)
+ /// Sets the spot light's inner cone angle (in degrees).
///
- /// Inner angle (in degrees)
API_PROPERTY(Attributes="EditorOrder(21), DefaultValue(10.0f), EditorDisplay(\"Light\"), Limit(1, 89, 0.1f)")
FORCE_INLINE float GetInnerConeAngle() const
{
@@ -113,9 +108,8 @@ public:
}
///
- /// Sets the spot light's inner cone angle (in degrees)
+ /// Sets the spot light's inner cone angle (in degrees).
///
- /// Value to assign
API_PROPERTY() void SetInnerConeAngle(float value);
private:
diff --git a/Source/Engine/Level/Components/MissingScript.h b/Source/Engine/Level/Components/MissingScript.h
index bfb73498f..7b351bd82 100644
--- a/Source/Engine/Level/Components/MissingScript.h
+++ b/Source/Engine/Level/Components/MissingScript.h
@@ -42,24 +42,7 @@ public:
///
/// Field for assigning new script to transfer data to.
///
- API_PROPERTY() void SetReferenceScript(const ScriptingObjectReference